diff --git a/CMakeLists.txt b/CMakeLists.txt index 9169371a2..c3fad167a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -380,7 +380,6 @@ set(Game-Source ${GAME_SRC_DIR}/monster/demon/demon.c ${GAME_SRC_DIR}/monster/dog/dog.c ${GAME_SRC_DIR}/monster/enforcer/enforcer.c - ${GAME_SRC_DIR}/monster/fish/fish.c ${GAME_SRC_DIR}/monster/fixbot/fixbot.c ${GAME_SRC_DIR}/monster/flipper/flipper.c ${GAME_SRC_DIR}/monster/float/float.c @@ -400,6 +399,7 @@ set(Game-Source ${GAME_SRC_DIR}/monster/mutant/mutant.c ${GAME_SRC_DIR}/monster/ogre/ogre.c ${GAME_SRC_DIR}/monster/parasite/parasite.c + ${GAME_SRC_DIR}/monster/rotfish/fish.c ${GAME_SRC_DIR}/monster/shalrath/shalrath.c ${GAME_SRC_DIR}/monster/shambler/shambler.c ${GAME_SRC_DIR}/monster/soldier/soldier.c diff --git a/doc/100_tested_maps.md b/doc/100_tested_maps.md index ef0f1cfcf..87157e7e5 100644 --- a/doc/100_tested_maps.md +++ b/doc/100_tested_maps.md @@ -241,7 +241,7 @@ Additionally supported models: | progs/demon.mdl | models/monsters/demon/tris.mdl | 4c73786e7cfb2083ca38cbc983cd6c4b | | progs/dog.mdl | models/monsters/dog/tris.mdl | e727fbc39acc652f812972612ce37565 | | progs/enforcer.mdl | models/monsters/enforcer/tris.mdl | 136c265f96d6077ee3312c52e134529f | - | progs/fish.mdl | models/monsters/fish/tris.mdl | d770d6ef92ae8b372926e6c3d49e8716 | + | progs/fish.mdl | models/monsters/rotfish/tris.mdl | d770d6ef92ae8b372926e6c3d49e8716 | | progs/hknight.mdl | models/monsters/hknight/tris.mdl | ed20e30be6fdb83efbaa6d0b23671a49 | | progs/knight.mdl | models/monsters/knight/tris.mdl | 5328915db5c53e85cf75d46e7b747fb9 | | progs/ogre.mdl | models/monsters/ogre/tris.mdl | fbb592ca3788a576dd2f31fcf8c80fab | diff --git a/src/game/monster/actor/actor.c b/src/game/monster/actor/actor.c new file mode 100644 index 000000000..579bd0311 --- /dev/null +++ b/src/game/monster/actor/actor.c @@ -0,0 +1,671 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + */ + +#include "../../header/local.h" +#include "actor.h" + +#define MAX_ACTOR_NAMES 8 +char *actor_names[MAX_ACTOR_NAMES] = +{ + "Hellrot", + "Tokay", + "Killme", + "Disruptor", + "Adrianator", + "Rambear", + "Titus", + "Bitterman" +}; + +static 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)); + } +} + + +static 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; +} + + +static 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; +} + + +static 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 +}; + +static 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 +}; + +static 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 +}; + +static 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 +}; + +static 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]; + gi.cprintf (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); +} + +static 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 +}; + +static 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; +} + +static 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 attack 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; + gi.cprintf (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); +} diff --git a/src/game/monster/actor/actor.h b/src/game/monster/actor/actor.h new file mode 100644 index 000000000..2703ae06d --- /dev/null +++ b/src/game/monster/actor/actor.h @@ -0,0 +1,505 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + */ + +#define FRAME_attak01 0 +#define FRAME_attak02 1 +#define FRAME_attak03 2 +#define FRAME_attak04 3 +#define FRAME_death101 4 +#define FRAME_death102 5 +#define FRAME_death103 6 +#define FRAME_death104 7 +#define FRAME_death105 8 +#define FRAME_death106 9 +#define FRAME_death107 10 +#define FRAME_death201 11 +#define FRAME_death202 12 +#define FRAME_death203 13 +#define FRAME_death204 14 +#define FRAME_death205 15 +#define FRAME_death206 16 +#define FRAME_death207 17 +#define FRAME_death208 18 +#define FRAME_death209 19 +#define FRAME_death210 20 +#define FRAME_death211 21 +#define FRAME_death212 22 +#define FRAME_death213 23 +#define FRAME_death301 24 +#define FRAME_death302 25 +#define FRAME_death303 26 +#define FRAME_death304 27 +#define FRAME_death305 28 +#define FRAME_death306 29 +#define FRAME_death307 30 +#define FRAME_death308 31 +#define FRAME_death309 32 +#define FRAME_death310 33 +#define FRAME_death311 34 +#define FRAME_death312 35 +#define FRAME_death313 36 +#define FRAME_death314 37 +#define FRAME_death315 38 +#define FRAME_flip01 39 +#define FRAME_flip02 40 +#define FRAME_flip03 41 +#define FRAME_flip04 42 +#define FRAME_flip05 43 +#define FRAME_flip06 44 +#define FRAME_flip07 45 +#define FRAME_flip08 46 +#define FRAME_flip09 47 +#define FRAME_flip10 48 +#define FRAME_flip11 49 +#define FRAME_flip12 50 +#define FRAME_flip13 51 +#define FRAME_flip14 52 +#define FRAME_grenad01 53 +#define FRAME_grenad02 54 +#define FRAME_grenad03 55 +#define FRAME_grenad04 56 +#define FRAME_grenad05 57 +#define FRAME_grenad06 58 +#define FRAME_grenad07 59 +#define FRAME_grenad08 60 +#define FRAME_grenad09 61 +#define FRAME_grenad10 62 +#define FRAME_grenad11 63 +#define FRAME_grenad12 64 +#define FRAME_grenad13 65 +#define FRAME_grenad14 66 +#define FRAME_grenad15 67 +#define FRAME_jump01 68 +#define FRAME_jump02 69 +#define FRAME_jump03 70 +#define FRAME_jump04 71 +#define FRAME_jump05 72 +#define FRAME_jump06 73 +#define FRAME_pain101 74 +#define FRAME_pain102 75 +#define FRAME_pain103 76 +#define FRAME_pain201 77 +#define FRAME_pain202 78 +#define FRAME_pain203 79 +#define FRAME_pain301 80 +#define FRAME_pain302 81 +#define FRAME_pain303 82 +#define FRAME_push01 83 +#define FRAME_push02 84 +#define FRAME_push03 85 +#define FRAME_push04 86 +#define FRAME_push05 87 +#define FRAME_push06 88 +#define FRAME_push07 89 +#define FRAME_push08 90 +#define FRAME_push09 91 +#define FRAME_run01 92 +#define FRAME_run02 93 +#define FRAME_run03 94 +#define FRAME_run04 95 +#define FRAME_run05 96 +#define FRAME_run06 97 +#define FRAME_run07 98 +#define FRAME_run08 99 +#define FRAME_run09 100 +#define FRAME_run10 101 +#define FRAME_run11 102 +#define FRAME_run12 103 +#define FRAME_runs01 104 +#define FRAME_runs02 105 +#define FRAME_runs03 106 +#define FRAME_runs04 107 +#define FRAME_runs05 108 +#define FRAME_runs06 109 +#define FRAME_runs07 110 +#define FRAME_runs08 111 +#define FRAME_runs09 112 +#define FRAME_runs10 113 +#define FRAME_runs11 114 +#define FRAME_runs12 115 +#define FRAME_salute01 116 +#define FRAME_salute02 117 +#define FRAME_salute03 118 +#define FRAME_salute04 119 +#define FRAME_salute05 120 +#define FRAME_salute06 121 +#define FRAME_salute07 122 +#define FRAME_salute08 123 +#define FRAME_salute09 124 +#define FRAME_salute10 125 +#define FRAME_salute11 126 +#define FRAME_salute12 127 +#define FRAME_stand101 128 +#define FRAME_stand102 129 +#define FRAME_stand103 130 +#define FRAME_stand104 131 +#define FRAME_stand105 132 +#define FRAME_stand106 133 +#define FRAME_stand107 134 +#define FRAME_stand108 135 +#define FRAME_stand109 136 +#define FRAME_stand110 137 +#define FRAME_stand111 138 +#define FRAME_stand112 139 +#define FRAME_stand113 140 +#define FRAME_stand114 141 +#define FRAME_stand115 142 +#define FRAME_stand116 143 +#define FRAME_stand117 144 +#define FRAME_stand118 145 +#define FRAME_stand119 146 +#define FRAME_stand120 147 +#define FRAME_stand121 148 +#define FRAME_stand122 149 +#define FRAME_stand123 150 +#define FRAME_stand124 151 +#define FRAME_stand125 152 +#define FRAME_stand126 153 +#define FRAME_stand127 154 +#define FRAME_stand128 155 +#define FRAME_stand129 156 +#define FRAME_stand130 157 +#define FRAME_stand131 158 +#define FRAME_stand132 159 +#define FRAME_stand133 160 +#define FRAME_stand134 161 +#define FRAME_stand135 162 +#define FRAME_stand136 163 +#define FRAME_stand137 164 +#define FRAME_stand138 165 +#define FRAME_stand139 166 +#define FRAME_stand140 167 +#define FRAME_stand201 168 +#define FRAME_stand202 169 +#define FRAME_stand203 170 +#define FRAME_stand204 171 +#define FRAME_stand205 172 +#define FRAME_stand206 173 +#define FRAME_stand207 174 +#define FRAME_stand208 175 +#define FRAME_stand209 176 +#define FRAME_stand210 177 +#define FRAME_stand211 178 +#define FRAME_stand212 179 +#define FRAME_stand213 180 +#define FRAME_stand214 181 +#define FRAME_stand215 182 +#define FRAME_stand216 183 +#define FRAME_stand217 184 +#define FRAME_stand218 185 +#define FRAME_stand219 186 +#define FRAME_stand220 187 +#define FRAME_stand221 188 +#define FRAME_stand222 189 +#define FRAME_stand223 190 +#define FRAME_swim01 191 +#define FRAME_swim02 192 +#define FRAME_swim03 193 +#define FRAME_swim04 194 +#define FRAME_swim05 195 +#define FRAME_swim06 196 +#define FRAME_swim07 197 +#define FRAME_swim08 198 +#define FRAME_swim09 199 +#define FRAME_swim10 200 +#define FRAME_swim11 201 +#define FRAME_swim12 202 +#define FRAME_sw_atk01 203 +#define FRAME_sw_atk02 204 +#define FRAME_sw_atk03 205 +#define FRAME_sw_atk04 206 +#define FRAME_sw_atk05 207 +#define FRAME_sw_atk06 208 +#define FRAME_sw_pan01 209 +#define FRAME_sw_pan02 210 +#define FRAME_sw_pan03 211 +#define FRAME_sw_pan04 212 +#define FRAME_sw_pan05 213 +#define FRAME_sw_std01 214 +#define FRAME_sw_std02 215 +#define FRAME_sw_std03 216 +#define FRAME_sw_std04 217 +#define FRAME_sw_std05 218 +#define FRAME_sw_std06 219 +#define FRAME_sw_std07 220 +#define FRAME_sw_std08 221 +#define FRAME_sw_std09 222 +#define FRAME_sw_std10 223 +#define FRAME_sw_std11 224 +#define FRAME_sw_std12 225 +#define FRAME_sw_std13 226 +#define FRAME_sw_std14 227 +#define FRAME_sw_std15 228 +#define FRAME_sw_std16 229 +#define FRAME_sw_std17 230 +#define FRAME_sw_std18 231 +#define FRAME_sw_std19 232 +#define FRAME_sw_std20 233 +#define FRAME_taunt01 234 +#define FRAME_taunt02 235 +#define FRAME_taunt03 236 +#define FRAME_taunt04 237 +#define FRAME_taunt05 238 +#define FRAME_taunt06 239 +#define FRAME_taunt07 240 +#define FRAME_taunt08 241 +#define FRAME_taunt09 242 +#define FRAME_taunt10 243 +#define FRAME_taunt11 244 +#define FRAME_taunt12 245 +#define FRAME_taunt13 246 +#define FRAME_taunt14 247 +#define FRAME_taunt15 248 +#define FRAME_taunt16 249 +#define FRAME_taunt17 250 +#define FRAME_walk01 251 +#define FRAME_walk02 252 +#define FRAME_walk03 253 +#define FRAME_walk04 254 +#define FRAME_walk05 255 +#define FRAME_walk06 256 +#define FRAME_walk07 257 +#define FRAME_walk08 258 +#define FRAME_walk09 259 +#define FRAME_walk10 260 +#define FRAME_walk11 261 +#define FRAME_wave01 262 +#define FRAME_wave02 263 +#define FRAME_wave03 264 +#define FRAME_wave04 265 +#define FRAME_wave05 266 +#define FRAME_wave06 267 +#define FRAME_wave07 268 +#define FRAME_wave08 269 +#define FRAME_wave09 270 +#define FRAME_wave10 271 +#define FRAME_wave11 272 +#define FRAME_wave12 273 +#define FRAME_wave13 274 +#define FRAME_wave14 275 +#define FRAME_wave15 276 +#define FRAME_wave16 277 +#define FRAME_wave17 278 +#define FRAME_wave18 279 +#define FRAME_wave19 280 +#define FRAME_wave20 281 +#define FRAME_wave21 282 +#define FRAME_bl_atk01 283 +#define FRAME_bl_atk02 284 +#define FRAME_bl_atk03 285 +#define FRAME_bl_atk04 286 +#define FRAME_bl_atk05 287 +#define FRAME_bl_atk06 288 +#define FRAME_bl_flp01 289 +#define FRAME_bl_flp02 290 +#define FRAME_bl_flp13 291 +#define FRAME_bl_flp14 292 +#define FRAME_bl_flp15 293 +#define FRAME_bl_jmp01 294 +#define FRAME_bl_jmp02 295 +#define FRAME_bl_jmp03 296 +#define FRAME_bl_jmp04 297 +#define FRAME_bl_jmp05 298 +#define FRAME_bl_jmp06 299 +#define FRAME_bl_pn101 300 +#define FRAME_bl_pn102 301 +#define FRAME_bl_pn103 302 +#define FRAME_bl_pn201 303 +#define FRAME_bl_pn202 304 +#define FRAME_bl_pn203 305 +#define FRAME_bl_pn301 306 +#define FRAME_bl_pn302 307 +#define FRAME_bl_pn303 308 +#define FRAME_bl_psh08 309 +#define FRAME_bl_psh09 310 +#define FRAME_bl_run01 311 +#define FRAME_bl_run02 312 +#define FRAME_bl_run03 313 +#define FRAME_bl_run04 314 +#define FRAME_bl_run05 315 +#define FRAME_bl_run06 316 +#define FRAME_bl_run07 317 +#define FRAME_bl_run08 318 +#define FRAME_bl_run09 319 +#define FRAME_bl_run10 320 +#define FRAME_bl_run11 321 +#define FRAME_bl_run12 322 +#define FRAME_bl_rns03 323 +#define FRAME_bl_rns04 324 +#define FRAME_bl_rns05 325 +#define FRAME_bl_rns06 326 +#define FRAME_bl_rns07 327 +#define FRAME_bl_rns08 328 +#define FRAME_bl_rns09 329 +#define FRAME_bl_sal10 330 +#define FRAME_bl_sal11 331 +#define FRAME_bl_sal12 332 +#define FRAME_bl_std01 333 +#define FRAME_bl_std02 334 +#define FRAME_bl_std03 335 +#define FRAME_bl_std04 336 +#define FRAME_bl_std05 337 +#define FRAME_bl_std06 338 +#define FRAME_bl_std07 339 +#define FRAME_bl_std08 340 +#define FRAME_bl_std09 341 +#define FRAME_bl_std10 342 +#define FRAME_bl_std11 343 +#define FRAME_bl_std12 344 +#define FRAME_bl_std13 345 +#define FRAME_bl_std14 346 +#define FRAME_bl_std15 347 +#define FRAME_bl_std16 348 +#define FRAME_bl_std17 349 +#define FRAME_bl_std18 350 +#define FRAME_bl_std19 351 +#define FRAME_bl_std20 352 +#define FRAME_bl_std21 353 +#define FRAME_bl_std22 354 +#define FRAME_bl_std23 355 +#define FRAME_bl_std24 356 +#define FRAME_bl_std25 357 +#define FRAME_bl_std26 358 +#define FRAME_bl_std27 359 +#define FRAME_bl_std28 360 +#define FRAME_bl_std29 361 +#define FRAME_bl_std30 362 +#define FRAME_bl_std31 363 +#define FRAME_bl_std32 364 +#define FRAME_bl_std33 365 +#define FRAME_bl_std34 366 +#define FRAME_bl_std35 367 +#define FRAME_bl_std36 368 +#define FRAME_bl_std37 369 +#define FRAME_bl_std38 370 +#define FRAME_bl_std39 371 +#define FRAME_bl_std40 372 +#define FRAME_bl_swm01 373 +#define FRAME_bl_swm02 374 +#define FRAME_bl_swm03 375 +#define FRAME_bl_swm04 376 +#define FRAME_bl_swm05 377 +#define FRAME_bl_swm06 378 +#define FRAME_bl_swm07 379 +#define FRAME_bl_swm08 380 +#define FRAME_bl_swm09 381 +#define FRAME_bl_swm10 382 +#define FRAME_bl_swm11 383 +#define FRAME_bl_swm12 384 +#define FRAME_bl_swk01 385 +#define FRAME_bl_swk02 386 +#define FRAME_bl_swk03 387 +#define FRAME_bl_swk04 388 +#define FRAME_bl_swk05 389 +#define FRAME_bl_swk06 390 +#define FRAME_bl_swp01 391 +#define FRAME_bl_swp02 392 +#define FRAME_bl_swp03 393 +#define FRAME_bl_swp04 394 +#define FRAME_bl_swp05 395 +#define FRAME_bl_sws01 396 +#define FRAME_bl_sws02 397 +#define FRAME_bl_sws03 398 +#define FRAME_bl_sws04 399 +#define FRAME_bl_sws05 400 +#define FRAME_bl_sws06 401 +#define FRAME_bl_sws07 402 +#define FRAME_bl_sws08 403 +#define FRAME_bl_sws09 404 +#define FRAME_bl_sws10 405 +#define FRAME_bl_sws11 406 +#define FRAME_bl_sws12 407 +#define FRAME_bl_sws13 408 +#define FRAME_bl_sws14 409 +#define FRAME_bl_tau14 410 +#define FRAME_bl_tau15 411 +#define FRAME_bl_tau16 412 +#define FRAME_bl_tau17 413 +#define FRAME_bl_wlk01 414 +#define FRAME_bl_wlk02 415 +#define FRAME_bl_wlk03 416 +#define FRAME_bl_wlk04 417 +#define FRAME_bl_wlk05 418 +#define FRAME_bl_wlk06 419 +#define FRAME_bl_wlk07 420 +#define FRAME_bl_wlk08 421 +#define FRAME_bl_wlk09 422 +#define FRAME_bl_wlk10 423 +#define FRAME_bl_wlk11 424 +#define FRAME_bl_wav19 425 +#define FRAME_bl_wav20 426 +#define FRAME_bl_wav21 427 +#define FRAME_cr_atk01 428 +#define FRAME_cr_atk02 429 +#define FRAME_cr_atk03 430 +#define FRAME_cr_atk04 431 +#define FRAME_cr_atk05 432 +#define FRAME_cr_atk06 433 +#define FRAME_cr_atk07 434 +#define FRAME_cr_atk08 435 +#define FRAME_cr_pan01 436 +#define FRAME_cr_pan02 437 +#define FRAME_cr_pan03 438 +#define FRAME_cr_pan04 439 +#define FRAME_cr_std01 440 +#define FRAME_cr_std02 441 +#define FRAME_cr_std03 442 +#define FRAME_cr_std04 443 +#define FRAME_cr_std05 444 +#define FRAME_cr_std06 445 +#define FRAME_cr_std07 446 +#define FRAME_cr_std08 447 +#define FRAME_cr_wlk01 448 +#define FRAME_cr_wlk02 449 +#define FRAME_cr_wlk03 450 +#define FRAME_cr_wlk04 451 +#define FRAME_cr_wlk05 452 +#define FRAME_cr_wlk06 453 +#define FRAME_cr_wlk07 454 +#define FRAME_crbl_a01 455 +#define FRAME_crbl_a02 456 +#define FRAME_crbl_a03 457 +#define FRAME_crbl_a04 458 +#define FRAME_crbl_a05 459 +#define FRAME_crbl_a06 460 +#define FRAME_crbl_a07 461 +#define FRAME_crbl_p01 462 +#define FRAME_crbl_p02 463 +#define FRAME_crbl_p03 464 +#define FRAME_crbl_p04 465 +#define FRAME_crbl_s01 466 +#define FRAME_crbl_s02 467 +#define FRAME_crbl_s03 468 +#define FRAME_crbl_s04 469 +#define FRAME_crbl_s05 470 +#define FRAME_crbl_s06 471 +#define FRAME_crbl_s07 472 +#define FRAME_crbl_s08 473 +#define FRAME_crbl_w01 474 +#define FRAME_crbl_w02 475 +#define FRAME_crbl_w03 476 +#define FRAME_crbl_w04 477 +#define FRAME_crbl_w05 478 +#define FRAME_crbl_w06 479 +#define FRAME_crbl_w07 480 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/arachnid/arachnid.c b/src/game/monster/arachnid/arachnid.c new file mode 100644 index 000000000..46ad3b5ac --- /dev/null +++ b/src/game/monster/arachnid/arachnid.c @@ -0,0 +1,580 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * The arachnid. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "arachnid.h" + +static int sound_pain; +static int sound_die; +static int sound_step; +static int sound_sight; +static int sound_charge; + +void +arachnid_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +arachnid_footstep(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_step, 0.5f, ATTN_IDLE, 0.0f); +} + +// +// stand +// + +static mframe_t arachnid_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} +}; + +mmove_t arachnid_move_stand = +{ + FRAME_idle1, + FRAME_idle13, + arachnid_frames_stand, + NULL +}; + +void +arachnid_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &arachnid_move_stand; +} + +// +// walk +// + +static mframe_t arachnid_frames_walk[] = +{ + {ai_walk, 8, arachnid_footstep}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, arachnid_footstep}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL} +}; + +mmove_t arachnid_move_walk = +{ + FRAME_walk1, + FRAME_walk10, + arachnid_frames_walk, + NULL +}; + +void +arachnid_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &arachnid_move_walk; +} + +// +// run +// + +static mframe_t arachnid_frames_run[] = +{ + {ai_run, 8, arachnid_footstep}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, arachnid_footstep}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL} +}; + +mmove_t arachnid_move_run = +{ + FRAME_walk1, + FRAME_walk10, + arachnid_frames_run, + NULL +}; + +void +arachnid_run(edict_t *self) +{ + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &arachnid_move_stand; + return; + } + + self->monsterinfo.currentmove = &arachnid_move_run; +} + +// +// pain +// + +static mframe_t arachnid_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t arachnid_move_pain1 = +{ + FRAME_pain11, + FRAME_pain15, + arachnid_frames_pain1, + arachnid_run +}; + +static mframe_t arachnid_frames_pain2[] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t arachnid_move_pain2 = +{ + FRAME_pain21, + FRAME_pain26, + arachnid_frames_pain2, + arachnid_run +}; + +void +arachnid_pain(edict_t *self, edict_t *other /* other */, + float kick /* other */, int damage) +{ + if (!self) + { + return; + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 3; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if ((rand() % 2) > 0) + { + self->monsterinfo.currentmove = &arachnid_move_pain1; + } + else + { + self->monsterinfo.currentmove = &arachnid_move_pain2; + } +} + +void +arachnid_charge_rail(edict_t *self) +{ + if (!self->enemy || !self->enemy->inuse) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_charge, 1.f, ATTN_NORM, 0.f); + + VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */ + self->pos1[2] += self->enemy->viewheight; +} + +void +arachnid_rail(edict_t *self) +{ + vec3_t start, dir, forward, right, offset = {0, 0, 0}; + int id = 0; + + switch (self->s.frame) + { + case FRAME_rails4: + default: + id = MZ2_WIDOW_RAIL; + /* MZ2_ARACHNID_RAIL1 */ + offset[0] = 58.f; + offset[1] = 20.f; + offset[2] = 17.2f; + break; + case FRAME_rails8: + id = MZ2_WIDOW_RAIL_LEFT; + /* MZ2_ARACHNID_RAIL2 */ + offset[0] = 64.f; + offset[1] = -22.f; + offset[2] = 24.f; + break; + case FRAME_rails_up7: + id = MZ2_WIDOW_RAIL_RIGHT; + /* MZ2_ARACHNID_RAIL_UP1 */ + offset[0] = 37.f; + offset[1] = 13.f; + offset[2] = 72.f; + break; + case FRAME_rails_up11: + id = MZ2_WIDOW_RAIL; + /* MZ2_ARACHNID_RAIL_UP2 */ + offset[0] = 58.f; + offset[1] = -25.f; + offset[2] = 72.f; + break; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, offset, forward, right, start); + + /* calc direction to where we targeted */ + VectorSubtract(self->pos1, start, dir); + VectorNormalize(dir); + + monster_fire_railgun(self, start, dir, 35, 100, id); + self->timestamp = level.time + 3; +} + +static mframe_t arachnid_frames_attack1[] = +{ + {ai_charge, 0, arachnid_charge_rail}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, arachnid_rail}, + {ai_charge, 0, arachnid_charge_rail}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, arachnid_rail}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t arachnid_attack1 = +{ + FRAME_rails1, + FRAME_rails11, + arachnid_frames_attack1, + arachnid_run +}; + +static mframe_t arachnid_frames_attack_up1[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, arachnid_charge_rail}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, arachnid_rail}, + {ai_charge, 0, arachnid_charge_rail}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, arachnid_rail}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, +}; + +mmove_t arachnid_attack_up1 = +{ + FRAME_rails_up1, + FRAME_rails_up16, + arachnid_frames_attack_up1, + arachnid_run +}; + +static int sound_melee, sound_melee_hit; + +void arachnid_melee_charge(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_melee, 1.f, ATTN_NORM, 0.f); +} + +void arachnid_melee_hit(edict_t *self) +{ + static vec3_t aim = {MELEE_DISTANCE, 0, 0}; + + if (!self) + { + return; + } + + fire_hit(self, aim, 15, 50); +} + +static mframe_t arachnid_frames_melee[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, arachnid_melee_charge}, + {ai_charge, 0, NULL}, + {ai_charge, 0, arachnid_melee_hit}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, arachnid_melee_charge}, + {ai_charge, 0, NULL}, + {ai_charge, 0, arachnid_melee_hit}, + {ai_charge, 0, NULL} +}; + +mmove_t arachnid_melee = +{ + FRAME_melee_atk1, + FRAME_melee_atk12, + arachnid_frames_melee, + arachnid_run +}; + +void +arachnid_attack(edict_t *self) +{ + float real_enemy_range; + + if (!self || !self->enemy || !self->enemy->inuse) + { + return; + } + + real_enemy_range = realrange(self, self->enemy); + + if (real_enemy_range <= MELEE_DISTANCE) + { + self->monsterinfo.currentmove = &arachnid_melee; + } + else if ((self->enemy->s.origin[2] - self->s.origin[2]) > 150.f) + { + self->monsterinfo.currentmove = &arachnid_attack_up1; + } + else + { + self->monsterinfo.currentmove = &arachnid_attack1; + } +} + +// +// death +// + +void +arachnid_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +static mframe_t arachnid_frames_death1[] = +{ + {ai_move, 0, NULL}, + {ai_move, -1.23f, NULL}, + {ai_move, -1.23f, NULL}, + {ai_move, -1.23f, NULL}, + {ai_move, -1.23f, NULL}, + {ai_move, -1.64f, NULL}, + {ai_move, -1.64f, NULL}, + {ai_move, -2.45f, NULL}, + {ai_move, -8.63f, NULL}, + {ai_move, -4.0f, NULL}, + {ai_move, -4.5f, NULL}, + {ai_move, -6.8f, NULL}, + {ai_move, -8.0f, NULL}, + {ai_move, -5.4f, NULL}, + {ai_move, -3.4f, NULL}, + {ai_move, -1.9f, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, +}; + +mmove_t arachnid_move_death = +{ + FRAME_death1, + FRAME_death20, + arachnid_frames_death1, + arachnid_dead +}; + +void +arachnid_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + int n; + + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 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, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &arachnid_move_death; +} + +/* + * QUAKED monster_arachnid (1 .5 0) (-48 -48 -20) (48 48 48) Ambush Trigger_Spawn Sight + */ +void +SP_monster_arachnid(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->s.modelindex = gi.modelindex("models/monsters/arachnid/tris.md2"); + VectorSet(self->mins, -48, -48, -20); + VectorSet(self->maxs, 48, 48, 48); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_step = gi.soundindex("insane/insane11.wav"); + sound_charge = gi.soundindex("gladiator/railgun.wav"); + sound_melee = gi.soundindex("gladiator/melee3.wav"); + sound_melee_hit = gi.soundindex("gladiator/melee2.wav"); + sound_pain = gi.soundindex("arachnid/pain.wav"); + sound_die = gi.soundindex("arachnid/death.wav"); + sound_sight = gi.soundindex("arachnid/sight.wav"); + + self->health = 1000; + self->gib_health = -200; + + self->mass = 450; + + self->pain = arachnid_pain; + self->die = arachnid_die; + self->monsterinfo.stand = arachnid_stand; + self->monsterinfo.walk = arachnid_walk; + self->monsterinfo.run = arachnid_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = arachnid_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = arachnid_sight; + self->monsterinfo.idle = NULL; + self->monsterinfo.blocked = NULL; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &arachnid_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/src/game/monster/arachnid/arachnid.h b/src/game/monster/arachnid/arachnid.h new file mode 100644 index 000000000..d1453dfd0 --- /dev/null +++ b/src/game/monster/arachnid/arachnid.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 1997-2001 Id Software Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Arachnid animations. + * + * ======================================================================= + */ + +#define FRAME_rails1 1 +#define FRAME_rails2 2 +#define FRAME_rails3 3 +#define FRAME_rails4 4 +#define FRAME_rails5 5 +#define FRAME_rails6 6 +#define FRAME_rails7 7 +#define FRAME_rails8 8 +#define FRAME_rails9 9 +#define FRAME_rails10 10 +#define FRAME_rails11 11 +#define FRAME_death1 12 +#define FRAME_death2 13 +#define FRAME_death3 14 +#define FRAME_death4 15 +#define FRAME_death5 16 +#define FRAME_death6 17 +#define FRAME_death7 18 +#define FRAME_death8 19 +#define FRAME_death9 20 +#define FRAME_death10 21 +#define FRAME_death11 22 +#define FRAME_death12 23 +#define FRAME_death13 24 +#define FRAME_death14 25 +#define FRAME_death15 26 +#define FRAME_death16 27 +#define FRAME_death17 28 +#define FRAME_death18 29 +#define FRAME_death19 30 +#define FRAME_death20 31 +#define FRAME_melee_atk1 32 +#define FRAME_melee_atk2 33 +#define FRAME_melee_atk3 34 +#define FRAME_melee_atk4 35 +#define FRAME_melee_atk5 36 +#define FRAME_melee_atk6 37 +#define FRAME_melee_atk7 38 +#define FRAME_melee_atk8 39 +#define FRAME_melee_atk9 40 +#define FRAME_melee_atk10 41 +#define FRAME_melee_atk11 42 +#define FRAME_melee_atk12 43 +#define FRAME_pain11 44 +#define FRAME_pain12 45 +#define FRAME_pain13 46 +#define FRAME_pain14 47 +#define FRAME_pain15 48 +#define FRAME_idle1 49 +#define FRAME_idle2 50 +#define FRAME_idle3 51 +#define FRAME_idle4 52 +#define FRAME_idle5 53 +#define FRAME_idle6 54 +#define FRAME_idle7 55 +#define FRAME_idle8 56 +#define FRAME_idle9 57 +#define FRAME_idle10 58 +#define FRAME_idle11 59 +#define FRAME_idle12 60 +#define FRAME_idle13 61 +#define FRAME_walk1 62 +#define FRAME_walk2 63 +#define FRAME_walk3 64 +#define FRAME_walk4 65 +#define FRAME_walk5 66 +#define FRAME_walk6 67 +#define FRAME_walk7 68 +#define FRAME_walk8 69 +#define FRAME_walk9 70 +#define FRAME_walk10 71 +#define FRAME_turn1 72 +#define FRAME_turn2 73 +#define FRAME_turn3 74 +#define FRAME_melee_out1 75 +#define FRAME_melee_out2 76 +#define FRAME_melee_out3 77 +#define FRAME_pain21 78 +#define FRAME_pain22 79 +#define FRAME_pain23 80 +#define FRAME_pain24 81 +#define FRAME_pain25 82 +#define FRAME_pain26 83 +#define FRAME_melee_pain1 84 +#define FRAME_melee_pain2 85 +#define FRAME_melee_pain3 86 +#define FRAME_melee_pain4 87 +#define FRAME_melee_pain5 88 +#define FRAME_melee_pain6 89 +#define FRAME_melee_pain7 90 +#define FRAME_melee_pain8 91 +#define FRAME_melee_pain9 92 +#define FRAME_melee_pain10 93 +#define FRAME_melee_pain11 94 +#define FRAME_melee_pain12 95 +#define FRAME_melee_pain13 96 +#define FRAME_melee_pain14 97 +#define FRAME_melee_pain15 98 +#define FRAME_melee_pain16 99 +#define FRAME_melee_in1 100 +#define FRAME_melee_in2 101 +#define FRAME_melee_in3 102 +#define FRAME_melee_in4 103 +#define FRAME_melee_in5 104 +#define FRAME_melee_in6 105 +#define FRAME_melee_in7 106 +#define FRAME_melee_in8 107 +#define FRAME_melee_in9 108 +#define FRAME_melee_in10 109 +#define FRAME_melee_in11 110 +#define FRAME_melee_in12 111 +#define FRAME_melee_in13 112 +#define FRAME_melee_in14 113 +#define FRAME_melee_in15 114 +#define FRAME_melee_in16 115 +#define FRAME_rails_up1 116 +#define FRAME_rails_up2 117 +#define FRAME_rails_up3 118 +#define FRAME_rails_up4 119 +#define FRAME_rails_up5 120 +#define FRAME_rails_up6 121 +#define FRAME_rails_up7 122 +#define FRAME_rails_up8 123 +#define FRAME_rails_up9 124 +#define FRAME_rails_up10 125 +#define FRAME_rails_up11 126 +#define FRAME_rails_up12 127 +#define FRAME_rails_up13 128 +#define FRAME_rails_up14 129 +#define FRAME_rails_up15 130 +#define FRAME_rails_up16 131 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/army/army.c b/src/game/monster/army/army.c new file mode 100644 index 000000000..cc36b52d3 --- /dev/null +++ b/src/game/monster/army/army.c @@ -0,0 +1,396 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "army.h" + +static int sound_death; +static int sound_search; +static int sound_pain1; +static int sound_pain2; +static int sound_attack; +static int sound_sight; + +// Stand +static mframe_t army_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}, +}; +mmove_t army_move_stand = +{ + FRAME_stand1, + FRAME_stand8, + army_frames_stand, + NULL +}; + +void +army_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &army_move_stand; +} + +// Run +static mframe_t army_frames_run [] = +{ + {ai_run, 11, NULL}, + {ai_run, 15, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + + {ai_run, 8, NULL}, + {ai_run, 15, NULL}, + {ai_run, 10, NULL}, + {ai_run, 8, NULL} +}; +mmove_t army_move_run = +{ + FRAME_run1, + FRAME_run8, + army_frames_run, + NULL +}; + +void +army_run(edict_t *self) +{ + self->monsterinfo.currentmove = &army_move_run; +} + +static void +army_fire(edict_t *self) +{ + vec3_t start; + vec3_t end; + vec3_t dir; + + VectorCopy(self->enemy->s.origin, end); + VectorCopy(self->s.origin, start); + start[2] += 30; + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, dir); + + monster_fire_shotgun(self, start, dir, 4, 1, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, 4, MZ2_SOLDIER_SHOTGUN_1); + gi.sound(self, CHAN_WEAPON, sound_attack, 1, ATTN_NORM, 0); +} + +// Attack +static mframe_t army_frames_attack [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, army_fire}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL} +}; +mmove_t army_move_attack = +{ + FRAME_shoot1, + FRAME_shoot9, + army_frames_attack, + army_run +}; + +void +army_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &army_move_attack; +} + +// Sight +void +army_sight(edict_t *self, edict_t *other /* unused */) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +// Search +void +army_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +// Pain (1) +static mframe_t army_frames_pain1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t army_move_pain1 = +{ + FRAME_pain1, + FRAME_pain6, + army_frames_pain1, + army_run +}; + +// Pain (2) +static mframe_t army_frames_pain2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 13, NULL}, + {ai_move, 9, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 2, NULL}, + {ai_move, 0, NULL} +}; +mmove_t army_move_pain2 = +{ + FRAME_painb1, + FRAME_painb14, + army_frames_pain2, + army_run +}; + +// Pain (3) +static mframe_t army_frames_pain3 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + + {ai_move, 4, NULL}, + {ai_move, 3, NULL}, + {ai_move, 6, NULL}, + {ai_move, 8, NULL}, + + {ai_move, 2, NULL} +}; +mmove_t army_move_pain3 = +{ + FRAME_painc1, + FRAME_painc13, + army_frames_pain3, + army_run +}; + +void +army_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + float r; + + // decino: No pain animations in Nightmare mode + if (skill->value == SKILL_HARDPLUS) + return; + if (level.time < self->pain_debounce_time) + return; + r = random(); + + if (r < 0.2) + { + self->pain_debounce_time = level.time + 0.6; + self->monsterinfo.currentmove = &army_move_pain1; + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else if (r < 0.6) + { + self->pain_debounce_time = level.time + 1.1; + self->monsterinfo.currentmove = &army_move_pain2; + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + else + { + self->pain_debounce_time = level.time + 1.1; + self->monsterinfo.currentmove = &army_move_pain3; + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } +} + +void +army_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); +} + +// Death (1) +static mframe_t army_frames_death1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t army_move_death1 = +{ + FRAME_death1, + FRAME_death10, + army_frames_death1, + army_dead +}; + +// Death (2) +static mframe_t army_frames_death2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, -5, NULL}, + {ai_move, -4, NULL}, + {ai_move, -13, NULL}, + + {ai_move, -3, NULL}, + {ai_move, -4, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t army_move_death2 = +{ + FRAME_deathc1, + FRAME_deathc11, + army_frames_death2, + army_dead +}; + +void +army_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 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; + } + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &army_move_death1; + } + else + { + self->monsterinfo.currentmove = &army_move_death2; + } +} + +/* + * QUAKED monster_army (1 .5 0) (-16, -16, -24) (16, 16, 40) Ambush Trigger_Spawn Sight + */ +void +SP_monster_army(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/army/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 40); + self->health = 30; + + sound_death = gi.soundindex("army/death1.wav"); + sound_search = gi.soundindex("army/idle.wav"); + sound_pain1 = gi.soundindex("army/pain1.wav"); + sound_pain2 = gi.soundindex("army/pain2.wav"); + sound_attack = gi.soundindex("army/sattck1.wav"); + sound_sight = gi.soundindex("army/sight1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -35; + self->mass = 30; + + self->monsterinfo.stand = army_stand; + self->monsterinfo.walk = army_run; + self->monsterinfo.run = army_run; + self->monsterinfo.attack = army_attack; + self->monsterinfo.sight = army_sight; + self->monsterinfo.search = army_search; + + self->pain = army_pain; + self->die = army_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/army/army.h b/src/game/monster/army/army.h new file mode 100644 index 000000000..8c9b44f39 --- /dev/null +++ b/src/game/monster/army/army.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Army/soldier animations. + * + * ======================================================================= + */ + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_stand8 7 +#define FRAME_death1 8 +#define FRAME_death2 9 +#define FRAME_death3 10 +#define FRAME_death4 11 +#define FRAME_death5 12 +#define FRAME_death6 13 +#define FRAME_death7 14 +#define FRAME_death8 15 +#define FRAME_death9 16 +#define FRAME_death10 17 +#define FRAME_deathc1 18 +#define FRAME_deathc2 19 +#define FRAME_deathc3 20 +#define FRAME_deathc4 21 +#define FRAME_deathc5 22 +#define FRAME_deathc6 23 +#define FRAME_deathc7 24 +#define FRAME_deathc8 25 +#define FRAME_deathc9 26 +#define FRAME_deathc10 27 +#define FRAME_deathc11 28 +#define FRAME_load1 29 +#define FRAME_load2 30 +#define FRAME_load3 31 +#define FRAME_load4 32 +#define FRAME_load5 33 +#define FRAME_load6 34 +#define FRAME_load7 35 +#define FRAME_load8 36 +#define FRAME_load9 37 +#define FRAME_load10 38 +#define FRAME_load11 39 +#define FRAME_pain1 40 +#define FRAME_pain2 41 +#define FRAME_pain3 42 +#define FRAME_pain4 43 +#define FRAME_pain5 44 +#define FRAME_pain6 45 +#define FRAME_painb1 46 +#define FRAME_painb2 47 +#define FRAME_painb3 48 +#define FRAME_painb4 49 +#define FRAME_painb5 50 +#define FRAME_painb6 51 +#define FRAME_painb7 52 +#define FRAME_painb8 53 +#define FRAME_painb9 54 +#define FRAME_painb10 55 +#define FRAME_painb11 56 +#define FRAME_painb12 57 +#define FRAME_painb13 58 +#define FRAME_painb14 59 +#define FRAME_painc1 60 +#define FRAME_painc2 61 +#define FRAME_painc3 62 +#define FRAME_painc4 63 +#define FRAME_painc5 64 +#define FRAME_painc6 65 +#define FRAME_painc7 66 +#define FRAME_painc8 67 +#define FRAME_painc9 68 +#define FRAME_painc10 69 +#define FRAME_painc11 70 +#define FRAME_painc12 71 +#define FRAME_painc13 72 +#define FRAME_run1 73 +#define FRAME_run2 74 +#define FRAME_run3 75 +#define FRAME_run4 76 +#define FRAME_run5 77 +#define FRAME_run6 78 +#define FRAME_run7 79 +#define FRAME_run8 80 +#define FRAME_shoot1 81 +#define FRAME_shoot2 82 +#define FRAME_shoot3 83 +#define FRAME_shoot4 84 +#define FRAME_shoot5 85 +#define FRAME_shoot6 86 +#define FRAME_shoot7 87 +#define FRAME_shoot8 88 +#define FRAME_shoot9 89 +#define FRAME_prowl_1 90 +#define FRAME_prowl_2 91 +#define FRAME_prowl_3 92 +#define FRAME_prowl_4 93 +#define FRAME_prowl_5 94 +#define FRAME_prowl_6 95 +#define FRAME_prowl_7 96 +#define FRAME_prowl_8 97 +#define FRAME_prowl_9 98 +#define FRAME_prowl_10 99 +#define FRAME_prowl_11 100 +#define FRAME_prowl_12 101 +#define FRAME_prowl_13 102 +#define FRAME_prowl_14 103 +#define FRAME_prowl_15 104 +#define FRAME_prowl_16 105 +#define FRAME_prowl_17 106 +#define FRAME_prowl_18 107 +#define FRAME_prowl_19 108 +#define FRAME_prowl_20 109 +#define FRAME_prowl_21 110 +#define FRAME_prowl_22 111 +#define FRAME_prowl_23 112 +#define FRAME_prowl_24 113 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/berserker/berserker.c b/src/game/monster/berserker/berserker.c new file mode 100644 index 000000000..05c928424 --- /dev/null +++ b/src/game/monster/berserker/berserker.c @@ -0,0 +1,846 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * The berserker. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "berserker.h" + +static int sound_pain; +static int sound_die; +static int sound_idle; +static int sound_punch; +static int sound_sight; +static int sound_search; + +static int sound_step; +static int sound_step2; + +void berserk_fidget(edict_t *self); + +void +berserk_footstep(edict_t *self) +{ + if (!g_monsterfootsteps->value) + return; + + // Lazy loading for savegame compatibility. + if (sound_step == 0 || sound_step2 == 0) + { + sound_step = gi.soundindex("berserk/step1.wav"); + sound_step2 = gi.soundindex("berserk/step2.wav"); + } + + if (randk() % 2 == 0) + { + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + } +} + + +void +berserk_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self || !other) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +berserk_search(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +static mframe_t berserk_frames_stand[] = { + {ai_stand, 0, berserk_fidget}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL} +}; + +mmove_t berserk_move_stand = +{ + FRAME_stand1, + FRAME_stand5, + berserk_frames_stand, + NULL +}; + +void +berserk_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &berserk_move_stand; +} + +static mframe_t berserk_frames_stand_fidget[] = { + {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 berserk_move_stand_fidget = +{ + FRAME_standb1, + FRAME_standb20, + berserk_frames_stand_fidget, + berserk_stand +}; + +void +berserk_fidget(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->enemy) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + return; + } + + if (random() > 0.15) + { + return; + } + + self->monsterinfo.currentmove = &berserk_move_stand_fidget; + gi.sound(self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0); +} + +static mframe_t berserk_frames_walk[] = { + {ai_walk, 9.1, NULL}, + {ai_walk, 6.3, NULL}, + {ai_walk, 4.9, NULL}, + {ai_walk, 6.7, berserk_footstep}, + {ai_walk, 6.0, NULL}, + {ai_walk, 8.2, NULL}, + {ai_walk, 7.2, NULL}, + {ai_walk, 6.1, NULL}, + {ai_walk, 4.9, berserk_footstep}, + {ai_walk, 4.7, NULL}, + {ai_walk, 4.7, NULL}, + {ai_walk, 4.8, NULL} +}; + +mmove_t berserk_move_walk = +{ + FRAME_walkc1, + FRAME_walkc11, + berserk_frames_walk, + NULL +}; + +void +berserk_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &berserk_move_walk; +} + +static mframe_t berserk_frames_run1[] = { + {ai_run, 21, NULL}, + {ai_run, 11, berserk_footstep}, + {ai_run, 21, NULL}, + {ai_run, 25, monster_done_dodge}, + {ai_run, 18, berserk_footstep}, + {ai_run, 19, NULL} +}; + +mmove_t berserk_move_run1 = +{ + FRAME_run1, + FRAME_run6, + berserk_frames_run1, + NULL +}; + +void +berserk_run(edict_t *self) +{ + if (!self) + { + return; + } + + monster_done_dodge(self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &berserk_move_stand; + } + else + { + self->monsterinfo.currentmove = &berserk_move_run1; + } +} + +void +berserk_attack_spike(edict_t *self) +{ + static vec3_t aim = {MELEE_DISTANCE, 0, -24}; + + if (!self) + { + return; + } + + fire_hit(self, aim, (15 + (randk() % 6)), 400); /* Faster attack -- upwards and backwards */ +} + +void +berserk_swing(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0); +} + +static mframe_t berserk_frames_attack_spike[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, berserk_swing}, + {ai_charge, 0, berserk_attack_spike}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t berserk_move_attack_spike = +{ + FRAME_att_c1, + FRAME_att_c8, + berserk_frames_attack_spike, + berserk_run +}; + +void +berserk_attack_club(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->mins[0], -4); + fire_hit(self, aim, (5 + (randk() % 6)), 400); /* Slower attack */ +} + +static mframe_t berserk_frames_attack_club[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, berserk_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, berserk_swing}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, berserk_attack_club}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t berserk_move_attack_club = +{ + FRAME_att_c9, + FRAME_att_c20, + berserk_frames_attack_club, + berserk_run +}; + +void +berserk_strike(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, 0, -6); + fire_hit(self, aim, (10 + (randk() % 6)), 400); /* Slower attack */ +} + +static mframe_t berserk_frames_attack_strike[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, berserk_footstep}, + {ai_move, 0, berserk_swing}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, berserk_strike}, + {ai_move, 0, berserk_footstep}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 9.7, NULL}, + {ai_move, 13.6, berserk_footstep} +}; + +mmove_t berserk_move_attack_strike = +{ + FRAME_att_c21, + FRAME_att_c34, + berserk_frames_attack_strike, + berserk_run +}; + +static void +berserk_attack_running_club(edict_t *self) +{ + /* Same as regular club attack */ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->mins[0], -4); + fire_hit(self, aim, (5 + (randk() % 6)), 400); /* Slower attack */ +} + +static mframe_t berserk_frames_attack_running_club[] = { + {ai_charge, 21, NULL}, + {ai_charge, 11, NULL}, + {ai_charge, 21, NULL}, + {ai_charge, 25, NULL}, + {ai_charge, 18, NULL}, + {ai_charge, 19, NULL}, + {ai_charge, 21, NULL}, + {ai_charge, 11, NULL}, + {ai_charge, 21, NULL}, + {ai_charge, 25, NULL}, + {ai_charge, 18, NULL}, + {ai_charge, 19, NULL}, + {ai_charge, 21, NULL}, + {ai_charge, 11, NULL}, + {ai_charge, 21, NULL}, + {ai_charge, 25, berserk_swing}, + {ai_charge, 18, berserk_attack_running_club}, + {ai_charge, 19, NULL} +}; + +mmove_t berserk_move_attack_running_club = +{ + FRAME_r_att1, + FRAME_r_att18, + berserk_frames_attack_running_club, + berserk_run +}; + +void +berserk_melee(edict_t *self) +{ + if (!self) + { + return; + } + + monster_done_dodge(self); + + const int r = randk() % 4; + + if (r == 0) + { + self->monsterinfo.currentmove = &berserk_move_attack_spike; + } + else if (r == 1) + { + self->monsterinfo.currentmove = &berserk_move_attack_strike; + } + else if (r == 2) + { + self->monsterinfo.currentmove = &berserk_move_attack_club; + } + else + { + self->monsterinfo.currentmove = &berserk_move_attack_running_club; + } +} + +static mframe_t berserk_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t berserk_move_pain1 = +{ + FRAME_painc1, + FRAME_painc4, + berserk_frames_pain1, + berserk_run +}; + +static mframe_t berserk_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t berserk_move_pain2 = +{ + FRAME_painb1, + FRAME_painb20, + berserk_frames_pain2, + berserk_run +}; + +void +berserk_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + 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, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + monster_done_dodge(self); + + if ((damage < 20) || (random() < 0.5)) + { + self->monsterinfo.currentmove = &berserk_move_pain1; + } + else + { + self->monsterinfo.currentmove = &berserk_move_pain2; + } +} + +void +berserk_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +static mframe_t berserk_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t berserk_move_death1 = +{ + FRAME_death1, + FRAME_death13, + berserk_frames_death1, + berserk_dead +}; + +static mframe_t berserk_frames_death2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t berserk_move_death2 = +{ + FRAME_deathc1, + FRAME_deathc8, + berserk_frames_death2, + berserk_dead +}; + +void +berserk_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage, vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 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; + } + + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (damage >= 50) + { + self->monsterinfo.currentmove = &berserk_move_death1; + } + else + { + self->monsterinfo.currentmove = &berserk_move_death2; + } +} + +static void +berserk_jump_now(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + monster_jump_start(self); + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +static void +berserk_jump2_now(edict_t *self) +{ + vec3_t forward,up; + + if (!self) + { + return; + } + + monster_jump_start(self); + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 150, forward, self->velocity); + VectorMA(self->velocity, 400, up, self->velocity); +} + +static void +berserk_jump_wait_land(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->groundentity == NULL) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + { + self->monsterinfo.nextframe = self->s.frame + 1; + } + } + else + { + self->monsterinfo.nextframe = self->s.frame + 1; + } +} + +static mframe_t berserk_frames_jump[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, berserk_jump_now}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, berserk_jump_wait_land}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t berserk_move_jump = { + FRAME_jump1, + FRAME_jump9, + berserk_frames_jump, + berserk_run +}; + +static mframe_t berserk_frames_jump2[] = { + {ai_move, -8, NULL}, + {ai_move, -4, NULL}, + {ai_move, -4, NULL}, + {ai_move, 0, berserk_jump2_now}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, berserk_jump_wait_land}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t berserk_move_jump2 = { + FRAME_jump1, + FRAME_jump9, + berserk_frames_jump2, + berserk_run +}; + +static void +berserk_jump(edict_t *self) +{ + if (!self || !self->enemy) + { + return; + } + + monster_done_dodge(self); + + if (self->enemy->absmin[2] > self->absmin[2]) + { + self->monsterinfo.currentmove = &berserk_move_jump2; + } + else + { + self->monsterinfo.currentmove = &berserk_move_jump; + } +} + +qboolean +berserk_blocked(edict_t *self, float dist) +{ + if (blocked_checkjump(self, dist, 256, 40)) + { + berserk_jump(self); + return true; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + return false; +} + +void +berserk_sidestep(edict_t *self) +{ + if (!self) + { + return; + } + + if ((self->monsterinfo.currentmove == &berserk_move_jump) || + (self->monsterinfo.currentmove == &berserk_move_jump2)) + { + return; + } + + if (self->monsterinfo.currentmove != &berserk_move_run1) + { + self->monsterinfo.currentmove = &berserk_move_run1; + } +} + +/* + * QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_berserk(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + + /* pre-caches */ + sound_pain = gi.soundindex("berserk/berpain2.wav"); + sound_die = gi.soundindex("berserk/berdeth2.wav"); + sound_idle = gi.soundindex("berserk/beridle1.wav"); + sound_punch = gi.soundindex("berserk/attack.wav"); + sound_search = gi.soundindex("berserk/bersrch1.wav"); + sound_sight = gi.soundindex("berserk/sight.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 240; + self->gib_health = -60; + self->mass = 250; + + self->pain = berserk_pain; + self->die = berserk_die; + + self->monsterinfo.stand = berserk_stand; + self->monsterinfo.walk = berserk_walk; + self->monsterinfo.run = berserk_run; + self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.sidestep = berserk_sidestep; + self->monsterinfo.attack = NULL; + self->monsterinfo.melee = berserk_melee; + self->monsterinfo.sight = berserk_sight; + self->monsterinfo.search = berserk_search; + self->monsterinfo.blocked = berserk_blocked; + + self->monsterinfo.currentmove = &berserk_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/berserker/berserker.h b/src/game/monster/berserker/berserker.h new file mode 100644 index 000000000..fc129576b --- /dev/null +++ b/src/game/monster/berserker/berserker.h @@ -0,0 +1,282 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Berserker animations. + * + * ======================================================================= + */ + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_standb1 5 +#define FRAME_standb2 6 +#define FRAME_standb3 7 +#define FRAME_standb4 8 +#define FRAME_standb5 9 +#define FRAME_standb6 10 +#define FRAME_standb7 11 +#define FRAME_standb8 12 +#define FRAME_standb9 13 +#define FRAME_standb10 14 +#define FRAME_standb11 15 +#define FRAME_standb12 16 +#define FRAME_standb13 17 +#define FRAME_standb14 18 +#define FRAME_standb15 19 +#define FRAME_standb16 20 +#define FRAME_standb17 21 +#define FRAME_standb18 22 +#define FRAME_standb19 23 +#define FRAME_standb20 24 +#define FRAME_walkc1 25 +#define FRAME_walkc2 26 +#define FRAME_walkc3 27 +#define FRAME_walkc4 28 +#define FRAME_walkc5 29 +#define FRAME_walkc6 30 +#define FRAME_walkc7 31 +#define FRAME_walkc8 32 +#define FRAME_walkc9 33 +#define FRAME_walkc10 34 +#define FRAME_walkc11 35 +#define FRAME_run1 36 +#define FRAME_run2 37 +#define FRAME_run3 38 +#define FRAME_run4 39 +#define FRAME_run5 40 +#define FRAME_run6 41 +#define FRAME_att_a1 42 +#define FRAME_att_a2 43 +#define FRAME_att_a3 44 +#define FRAME_att_a4 45 +#define FRAME_att_a5 46 +#define FRAME_att_a6 47 +#define FRAME_att_a7 48 +#define FRAME_att_a8 49 +#define FRAME_att_a9 50 +#define FRAME_att_a10 51 +#define FRAME_att_a11 52 +#define FRAME_att_a12 53 +#define FRAME_att_a13 54 +#define FRAME_att_b1 55 +#define FRAME_att_b2 56 +#define FRAME_att_b3 57 +#define FRAME_att_b4 58 +#define FRAME_att_b5 59 +#define FRAME_att_b6 60 +#define FRAME_att_b7 61 +#define FRAME_att_b8 62 +#define FRAME_att_b9 63 +#define FRAME_att_b10 64 +#define FRAME_att_b11 65 +#define FRAME_att_b12 66 +#define FRAME_att_b13 67 +#define FRAME_att_b14 68 +#define FRAME_att_b15 69 +#define FRAME_att_b16 70 +#define FRAME_att_b17 71 +#define FRAME_att_b18 72 +#define FRAME_att_b19 73 +#define FRAME_att_b20 74 +#define FRAME_att_b21 75 +#define FRAME_att_c1 76 +#define FRAME_att_c2 77 +#define FRAME_att_c3 78 +#define FRAME_att_c4 79 +#define FRAME_att_c5 80 +#define FRAME_att_c6 81 +#define FRAME_att_c7 82 +#define FRAME_att_c8 83 +#define FRAME_att_c9 84 +#define FRAME_att_c10 85 +#define FRAME_att_c11 86 +#define FRAME_att_c12 87 +#define FRAME_att_c13 88 +#define FRAME_att_c14 89 +#define FRAME_att_c15 90 +#define FRAME_att_c16 91 +#define FRAME_att_c17 92 +#define FRAME_att_c18 93 +#define FRAME_att_c19 94 +#define FRAME_att_c20 95 +#define FRAME_att_c21 96 +#define FRAME_att_c22 97 +#define FRAME_att_c23 98 +#define FRAME_att_c24 99 +#define FRAME_att_c25 100 +#define FRAME_att_c26 101 +#define FRAME_att_c27 102 +#define FRAME_att_c28 103 +#define FRAME_att_c29 104 +#define FRAME_att_c30 105 +#define FRAME_att_c31 106 +#define FRAME_att_c32 107 +#define FRAME_att_c33 108 +#define FRAME_att_c34 109 +#define FRAME_r_att1 110 +#define FRAME_r_att2 111 +#define FRAME_r_att3 112 +#define FRAME_r_att4 113 +#define FRAME_r_att5 114 +#define FRAME_r_att6 115 +#define FRAME_r_att7 116 +#define FRAME_r_att8 117 +#define FRAME_r_att9 118 +#define FRAME_r_att10 119 +#define FRAME_r_att11 120 +#define FRAME_r_att12 121 +#define FRAME_r_att13 122 +#define FRAME_r_att14 123 +#define FRAME_r_att15 124 +#define FRAME_r_att16 125 +#define FRAME_r_att17 126 +#define FRAME_r_att18 127 +#define FRAME_r_attb1 128 +#define FRAME_r_attb2 129 +#define FRAME_r_attb3 130 +#define FRAME_r_attb4 131 +#define FRAME_r_attb5 132 +#define FRAME_r_attb6 133 +#define FRAME_r_attb7 134 +#define FRAME_r_attb8 135 +#define FRAME_r_attb9 136 +#define FRAME_r_attb10 137 +#define FRAME_r_attb11 138 +#define FRAME_r_attb12 139 +#define FRAME_r_attb13 140 +#define FRAME_r_attb14 141 +#define FRAME_r_attb15 142 +#define FRAME_r_attb16 143 +#define FRAME_r_attb17 144 +#define FRAME_r_attb18 145 +#define FRAME_slam1 146 +#define FRAME_slam2 147 +#define FRAME_slam3 148 +#define FRAME_slam4 149 +#define FRAME_slam5 150 +#define FRAME_slam6 151 +#define FRAME_slam7 152 +#define FRAME_slam8 153 +#define FRAME_slam9 154 +#define FRAME_slam10 155 +#define FRAME_slam11 156 +#define FRAME_slam12 157 +#define FRAME_slam13 158 +#define FRAME_slam14 159 +#define FRAME_slam15 160 +#define FRAME_slam16 161 +#define FRAME_slam17 162 +#define FRAME_slam18 163 +#define FRAME_slam19 164 +#define FRAME_slam20 165 +#define FRAME_slam21 166 +#define FRAME_slam22 167 +#define FRAME_slam23 168 +#define FRAME_duck1 169 +#define FRAME_duck2 170 +#define FRAME_duck3 171 +#define FRAME_duck4 172 +#define FRAME_duck5 173 +#define FRAME_duck6 174 +#define FRAME_duck7 175 +#define FRAME_duck8 176 +#define FRAME_duck9 177 +#define FRAME_duck10 178 +#define FRAME_fall1 179 +#define FRAME_fall2 180 +#define FRAME_fall3 181 +#define FRAME_fall4 182 +#define FRAME_fall5 183 +#define FRAME_fall6 184 +#define FRAME_fall7 185 +#define FRAME_fall8 186 +#define FRAME_fall9 187 +#define FRAME_fall10 188 +#define FRAME_fall11 189 +#define FRAME_fall12 190 +#define FRAME_fall13 191 +#define FRAME_fall14 192 +#define FRAME_fall15 193 +#define FRAME_fall16 194 +#define FRAME_fall17 195 +#define FRAME_fall18 196 +#define FRAME_fall19 197 +#define FRAME_fall20 198 +#define FRAME_painc1 199 +#define FRAME_painc2 200 +#define FRAME_painc3 201 +#define FRAME_painc4 202 +#define FRAME_painb1 203 +#define FRAME_painb2 204 +#define FRAME_painb3 205 +#define FRAME_painb4 206 +#define FRAME_painb5 207 +#define FRAME_painb6 208 +#define FRAME_painb7 209 +#define FRAME_painb8 210 +#define FRAME_painb9 211 +#define FRAME_painb10 212 +#define FRAME_painb11 213 +#define FRAME_painb12 214 +#define FRAME_painb13 215 +#define FRAME_painb14 216 +#define FRAME_painb15 217 +#define FRAME_painb16 218 +#define FRAME_painb17 219 +#define FRAME_painb18 220 +#define FRAME_painb19 221 +#define FRAME_painb20 222 +#define FRAME_death1 223 +#define FRAME_death2 224 +#define FRAME_death3 225 +#define FRAME_death4 226 +#define FRAME_death5 227 +#define FRAME_death6 228 +#define FRAME_death7 229 +#define FRAME_death8 230 +#define FRAME_death9 231 +#define FRAME_death10 232 +#define FRAME_death11 233 +#define FRAME_death12 234 +#define FRAME_death13 235 +#define FRAME_deathc1 236 +#define FRAME_deathc2 237 +#define FRAME_deathc3 238 +#define FRAME_deathc4 239 +#define FRAME_deathc5 240 +#define FRAME_deathc6 241 +#define FRAME_deathc7 242 +#define FRAME_deathc8 243 + +#define FRAME_jump1 244 +#define FRAME_jump2 245 +#define FRAME_jump3 246 +#define FRAME_jump4 247 +#define FRAME_jump5 248 +#define FRAME_jump6 249 +#define FRAME_jump7 250 +#define FRAME_jump8 251 +#define FRAME_jump9 252 +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/boss2/boss2.c b/src/game/monster/boss2/boss2.c new file mode 100644 index 000000000..228d754c6 --- /dev/null +++ b/src/game/monster/boss2/boss2.c @@ -0,0 +1,914 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Boss 2 aka Hornet. Found in biggun and inner hangar. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "boss2.h" + +#define BOSS2_ROCKET_SPEED 750 + +qboolean infront(edict_t *self, edict_t *other); +void BossExplode(edict_t *self); +void boss2_run(edict_t *self); +void boss2_stand(edict_t *self); +void boss2_dead(edict_t *self); +void boss2_attack(edict_t *self); +void boss2_attack_mg(edict_t *self); +void boss2_reattack_mg(edict_t *self); +void boss2_die(edict_t *self, edict_t *inflictor, edict_t *attacker, + int damage, vec3_t point); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; + +void +boss2_search(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); + } +} + +static void +Boss2PredictiveRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + float time, dist; + + if (!self || !self->enemy || !self->enemy->inuse) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + +//1 + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start); + VectorSubtract(self->enemy->s.origin, start, dir); + dist = VectorLength(dir); + time = dist / BOSS2_ROCKET_SPEED; + VectorMA(self->enemy->s.origin, time-0.3, self->enemy->velocity, vec); + + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_1); + +//2 + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start); + VectorSubtract(self->enemy->s.origin, start, dir); + dist = VectorLength(dir); + time = dist / BOSS2_ROCKET_SPEED; + VectorMA(self->enemy->s.origin, time-0.15, self->enemy->velocity, vec); + + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_2); + +//3 + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start); + VectorSubtract(self->enemy->s.origin, start, dir); + dist = VectorLength(dir); + time = dist / BOSS2_ROCKET_SPEED; + VectorMA(self->enemy->s.origin, time, self->enemy->velocity, vec); + + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_3); + +//4 + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start); + VectorSubtract(self->enemy->s.origin, start, dir); + dist = VectorLength(dir); + time = dist / BOSS2_ROCKET_SPEED; + VectorMA(self->enemy->s.origin, time+0.15, self->enemy->velocity, vec); + + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_4); +} + +void +Boss2Rocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + if (!self || !self->enemy || !self->enemy->inuse) + { + return; + } + + if (self->enemy->client && random() < 0.9) + { + Boss2PredictiveRocket(self); + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + +//1 + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + vec[2] -= 15; + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + VectorMA(dir, 0.4, right, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1); + +//2 + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + VectorMA(dir, 0.025, right, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2); + +//3 + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + VectorMA(dir, -0.025, right, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3); + +//4 + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + vec[2] -= 15; + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + VectorMA(dir, -0.4, right, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4); +} + +void +boss2_firebullet_right(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1], + forward, right, start); + + VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract(target, start, forward); + VectorNormalize(forward); + + monster_fire_bullet(self, start, forward, + 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + MZ2_BOSS2_MACHINEGUN_R1); +} + +void +boss2_firebullet_left(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1], + forward, right, start); + + VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target); + + target[2] += self->enemy->viewheight; + VectorSubtract(target, start, forward); + VectorNormalize(forward); + + monster_fire_bullet(self, start, forward, 6, 4, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + MZ2_BOSS2_MACHINEGUN_L1); +} + +void +Boss2MachineGun(edict_t *self) +{ + if (!self) + { + return; + } + + boss2_firebullet_left(self); + boss2_firebullet_right(self); +} + +static mframe_t boss2_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} +}; + +mmove_t boss2_move_stand = +{ + FRAME_stand30, + FRAME_stand50, + boss2_frames_stand, + NULL +}; + +static mframe_t boss2_frames_fidget[] = { + {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 boss2_move_fidget = +{ + FRAME_stand1, + FRAME_stand30, + boss2_frames_fidget, + NULL +}; + +static mframe_t boss2_frames_walk[] = { + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 10, NULL} +}; + +mmove_t boss2_move_walk = { + FRAME_walk1, + FRAME_walk20, + boss2_frames_walk, + NULL +}; + +static mframe_t boss2_frames_run[] = { + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL} +}; + +mmove_t boss2_move_run = { + FRAME_walk1, + FRAME_walk20, + boss2_frames_run, + NULL +}; + +static mframe_t boss2_frames_attack_pre_mg[] = { + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, boss2_attack_mg} +}; + +mmove_t boss2_move_attack_pre_mg = +{ + FRAME_attack1, + FRAME_attack9, + boss2_frames_attack_pre_mg, + NULL +}; + +/* Loop this */ +static mframe_t boss2_frames_attack_mg[] = { + {ai_charge, 2, Boss2MachineGun}, + {ai_charge, 2, Boss2MachineGun}, + {ai_charge, 2, Boss2MachineGun}, + {ai_charge, 2, Boss2MachineGun}, + {ai_charge, 2, Boss2MachineGun}, + {ai_charge, 2, boss2_reattack_mg} +}; + +mmove_t boss2_move_attack_mg = +{ + FRAME_attack10, + FRAME_attack15, + boss2_frames_attack_mg, + NULL +}; + +static mframe_t boss2_frames_attack_post_mg[] = { + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL} +}; + +mmove_t boss2_move_attack_post_mg = +{ + FRAME_attack16, + FRAME_attack19, + boss2_frames_attack_post_mg, + boss2_run +}; + +static mframe_t boss2_frames_attack_rocket[] = { + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_move, -5, Boss2Rocket}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL} +}; + +mmove_t boss2_move_attack_rocket = +{ + FRAME_attack20, + FRAME_attack40, + boss2_frames_attack_rocket, + boss2_run +}; + +static mframe_t boss2_frames_pain_heavy[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss2_move_pain_heavy = +{ + FRAME_pain2, + FRAME_pain19, + boss2_frames_pain_heavy, + boss2_run +}; + +static mframe_t boss2_frames_pain_light[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss2_move_pain_light = +{ + FRAME_pain20, + FRAME_pain23, + boss2_frames_pain_light, + boss2_run +}; + +static mframe_t boss2_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, BossExplode} +}; + +mmove_t boss2_move_death = +{ + FRAME_death2, + FRAME_death50, + boss2_frames_death, + boss2_dead +}; + +void +boss2_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &boss2_move_stand; +} + +void +boss2_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &boss2_move_stand; + } + else + { + self->monsterinfo.currentmove = &boss2_move_run; + } +} + +void +boss2_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &boss2_move_walk; +} + +void +boss2_attack(edict_t *self) +{ + vec3_t vec; + float range; + + if (!self) + { + return; + } + + VectorSubtract(self->enemy->s.origin, self->s.origin, vec); + range = VectorLength(vec); + + if (range <= 125) + { + self->monsterinfo.currentmove = &boss2_move_attack_pre_mg; + } + else + { + if (random() <= 0.6) + { + self->monsterinfo.currentmove = &boss2_move_attack_pre_mg; + } + else + { + self->monsterinfo.currentmove = &boss2_move_attack_rocket; + } + } +} + +void +boss2_attack_mg(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &boss2_move_attack_mg; +} + +void +boss2_reattack_mg(edict_t *self) +{ + if (!self) + { + return; + } + + if (infront(self, self->enemy)) + { + if (random() <= 0.7) + { + self->monsterinfo.currentmove = &boss2_move_attack_mg; + } + else + { + self->monsterinfo.currentmove = &boss2_move_attack_post_mg; + } + } + else + { + self->monsterinfo.currentmove = &boss2_move_attack_post_mg; + } +} + +void +boss2_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + 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; + + /* American wanted these at no attenuation */ + if (damage < 10) + { + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_light; + } + else if (damage < 30) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_light; + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &boss2_move_pain_heavy; + } +} + +void +boss2_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -56, -56, 0); + VectorSet(self->maxs, 56, 56, 80); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +void +boss2_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage /* unused */, vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &boss2_move_death; +} + +qboolean +Boss2_CheckAttack(edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + int enemy_range; + float enemy_yaw; + + if (!self) + { + return false; + } + + if (self->enemy->health > 0) + { + /* see if any entities are in the way of the shot */ + VectorCopy(self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy(self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace(spot1, NULL, NULL, spot2, self, + CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | + CONTENTS_LAVA); + + /* do we have a clear shot? */ + if (tr.ent != self->enemy) + { + /* we want them to go ahead and shoot at info_notnulls if they can */ + if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) + { + return false; + } + } + } + + enemy_range = range(self, self->enemy); + VectorSubtract(self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + /* melee attack */ + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + { + self->monsterinfo.attack_state = AS_MELEE; + } + else + { + self->monsterinfo.attack_state = AS_MISSILE; + } + + return true; + } + + /* missile attack */ + if (!self->monsterinfo.attack) + { + return false; + } + + if (level.time < self->monsterinfo.attack_finished) + { + return false; + } + + if (enemy_range == RANGE_FAR) + { + return false; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.8; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.8; + } + else + { + return false; + } + + if ((random() < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2 * random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + { + self->monsterinfo.attack_state = AS_SLIDING; + } + else + { + self->monsterinfo.attack_state = AS_STRAIGHT; + } + } + + return false; +} + +/* + * QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight + */ +void +SP_monster_boss2(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("bosshovr/bhvpain1.wav"); + sound_pain2 = gi.soundindex("bosshovr/bhvpain2.wav"); + sound_pain3 = gi.soundindex("bosshovr/bhvpain3.wav"); + sound_death = gi.soundindex("bosshovr/bhvdeth1.wav"); + sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav"); + + self->s.sound = gi.soundindex("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/boss2/tris.md2"); + VectorSet(self->mins, -56, -56, 0); + VectorSet(self->maxs, 56, 56, 80); + + self->health = 2000; + self->gib_health = -200; + self->mass = 1000; + + self->flags |= FL_IMMUNE_LASER; + + self->pain = boss2_pain; + self->die = boss2_die; + + self->monsterinfo.stand = boss2_stand; + self->monsterinfo.walk = boss2_walk; + self->monsterinfo.run = boss2_run; + self->monsterinfo.attack = boss2_attack; + self->monsterinfo.search = boss2_search; + self->monsterinfo.checkattack = Boss2_CheckAttack; + gi.linkentity(self); + + self->monsterinfo.currentmove = &boss2_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start(self); +} diff --git a/src/game/monster/boss2/boss2.h b/src/game/monster/boss2/boss2.h new file mode 100644 index 000000000..c970d124a --- /dev/null +++ b/src/game/monster/boss2/boss2.h @@ -0,0 +1,210 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Animations for boss2. + * + * ======================================================================= + */ + +#define FRAME_stand30 0 +#define FRAME_stand31 1 +#define FRAME_stand32 2 +#define FRAME_stand33 3 +#define FRAME_stand34 4 +#define FRAME_stand35 5 +#define FRAME_stand36 6 +#define FRAME_stand37 7 +#define FRAME_stand38 8 +#define FRAME_stand39 9 +#define FRAME_stand40 10 +#define FRAME_stand41 11 +#define FRAME_stand42 12 +#define FRAME_stand43 13 +#define FRAME_stand44 14 +#define FRAME_stand45 15 +#define FRAME_stand46 16 +#define FRAME_stand47 17 +#define FRAME_stand48 18 +#define FRAME_stand49 19 +#define FRAME_stand50 20 +#define FRAME_stand1 21 +#define FRAME_stand2 22 +#define FRAME_stand3 23 +#define FRAME_stand4 24 +#define FRAME_stand5 25 +#define FRAME_stand6 26 +#define FRAME_stand7 27 +#define FRAME_stand8 28 +#define FRAME_stand9 29 +#define FRAME_stand10 30 +#define FRAME_stand11 31 +#define FRAME_stand12 32 +#define FRAME_stand13 33 +#define FRAME_stand14 34 +#define FRAME_stand15 35 +#define FRAME_stand16 36 +#define FRAME_stand17 37 +#define FRAME_stand18 38 +#define FRAME_stand19 39 +#define FRAME_stand20 40 +#define FRAME_stand21 41 +#define FRAME_stand22 42 +#define FRAME_stand23 43 +#define FRAME_stand24 44 +#define FRAME_stand25 45 +#define FRAME_stand26 46 +#define FRAME_stand27 47 +#define FRAME_stand28 48 +#define FRAME_stand29 49 +#define FRAME_walk1 50 +#define FRAME_walk2 51 +#define FRAME_walk3 52 +#define FRAME_walk4 53 +#define FRAME_walk5 54 +#define FRAME_walk6 55 +#define FRAME_walk7 56 +#define FRAME_walk8 57 +#define FRAME_walk9 58 +#define FRAME_walk10 59 +#define FRAME_walk11 60 +#define FRAME_walk12 61 +#define FRAME_walk13 62 +#define FRAME_walk14 63 +#define FRAME_walk15 64 +#define FRAME_walk16 65 +#define FRAME_walk17 66 +#define FRAME_walk18 67 +#define FRAME_walk19 68 +#define FRAME_walk20 69 +#define FRAME_attack1 70 +#define FRAME_attack2 71 +#define FRAME_attack3 72 +#define FRAME_attack4 73 +#define FRAME_attack5 74 +#define FRAME_attack6 75 +#define FRAME_attack7 76 +#define FRAME_attack8 77 +#define FRAME_attack9 78 +#define FRAME_attack10 79 +#define FRAME_attack11 80 +#define FRAME_attack12 81 +#define FRAME_attack13 82 +#define FRAME_attack14 83 +#define FRAME_attack15 84 +#define FRAME_attack16 85 +#define FRAME_attack17 86 +#define FRAME_attack18 87 +#define FRAME_attack19 88 +#define FRAME_attack20 89 +#define FRAME_attack21 90 +#define FRAME_attack22 91 +#define FRAME_attack23 92 +#define FRAME_attack24 93 +#define FRAME_attack25 94 +#define FRAME_attack26 95 +#define FRAME_attack27 96 +#define FRAME_attack28 97 +#define FRAME_attack29 98 +#define FRAME_attack30 99 +#define FRAME_attack31 100 +#define FRAME_attack32 101 +#define FRAME_attack33 102 +#define FRAME_attack34 103 +#define FRAME_attack35 104 +#define FRAME_attack36 105 +#define FRAME_attack37 106 +#define FRAME_attack38 107 +#define FRAME_attack39 108 +#define FRAME_attack40 109 +#define FRAME_pain2 110 +#define FRAME_pain3 111 +#define FRAME_pain4 112 +#define FRAME_pain5 113 +#define FRAME_pain6 114 +#define FRAME_pain7 115 +#define FRAME_pain8 116 +#define FRAME_pain9 117 +#define FRAME_pain10 118 +#define FRAME_pain11 119 +#define FRAME_pain12 120 +#define FRAME_pain13 121 +#define FRAME_pain14 122 +#define FRAME_pain15 123 +#define FRAME_pain16 124 +#define FRAME_pain17 125 +#define FRAME_pain18 126 +#define FRAME_pain19 127 +#define FRAME_pain20 128 +#define FRAME_pain21 129 +#define FRAME_pain22 130 +#define FRAME_pain23 131 +#define FRAME_death2 132 +#define FRAME_death3 133 +#define FRAME_death4 134 +#define FRAME_death5 135 +#define FRAME_death6 136 +#define FRAME_death7 137 +#define FRAME_death8 138 +#define FRAME_death9 139 +#define FRAME_death10 140 +#define FRAME_death11 141 +#define FRAME_death12 142 +#define FRAME_death13 143 +#define FRAME_death14 144 +#define FRAME_death15 145 +#define FRAME_death16 146 +#define FRAME_death17 147 +#define FRAME_death18 148 +#define FRAME_death19 149 +#define FRAME_death20 150 +#define FRAME_death21 151 +#define FRAME_death22 152 +#define FRAME_death23 153 +#define FRAME_death24 154 +#define FRAME_death25 155 +#define FRAME_death26 156 +#define FRAME_death27 157 +#define FRAME_death28 158 +#define FRAME_death29 159 +#define FRAME_death30 160 +#define FRAME_death31 161 +#define FRAME_death32 162 +#define FRAME_death33 163 +#define FRAME_death34 164 +#define FRAME_death35 165 +#define FRAME_death36 166 +#define FRAME_death37 167 +#define FRAME_death38 168 +#define FRAME_death39 169 +#define FRAME_death40 170 +#define FRAME_death41 171 +#define FRAME_death42 172 +#define FRAME_death43 173 +#define FRAME_death44 174 +#define FRAME_death45 175 +#define FRAME_death46 176 +#define FRAME_death47 177 +#define FRAME_death48 178 +#define FRAME_death49 179 +#define FRAME_death50 180 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/boss3/boss3.c b/src/game/monster/boss3/boss3.c new file mode 100644 index 000000000..e0c3f7fcd --- /dev/null +++ b/src/game/monster/boss3/boss3.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Final boss, just standing and waiting at "inner chamber". + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "boss32.h" + +void +Use_Boss3(edict_t *ent, edict_t *other /* unused */, + edict_t *activator /* unused */) +{ + if (!ent) + { + return; + } + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BOSSTPORT); + gi.WritePosition(ent->s.origin); + gi.multicast(ent->s.origin, MULTICAST_PVS); + G_FreeEdict(ent); +} + +void +Think_Boss3Stand(edict_t *ent) +{ + if (!ent) + { + return; + } + + if (ent->s.frame == FRAME_stand260) + { + ent->s.frame = FRAME_stand201; + } + else + { + ent->s.frame++; + } + + ent->nextthink = level.time + FRAMETIME; +} + +/* + * QUAKED monster_boss3_stand (1 .5 0) (-32 -32 0) (32 32 90) + * + * Just stands and cycles in one place until targeted, then teleports away. + */ +void +SP_monster_boss3_stand(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->model = "models/monsters/boss3/rider/tris.md2"; + self->s.modelindex = gi.modelindex(self->model); + self->s.frame = FRAME_stand201; + + gi.soundindex("misc/bigtele.wav"); + + VectorSet(self->mins, -32, -32, 0); + VectorSet(self->maxs, 32, 32, 90); + + self->use = Use_Boss3; + self->think = Think_Boss3Stand; + self->nextthink = level.time + FRAMETIME; + gi.linkentity(self); +} diff --git a/src/game/monster/boss3/boss31.c b/src/game/monster/boss3/boss31.c new file mode 100644 index 000000000..191994486 --- /dev/null +++ b/src/game/monster/boss3/boss31.c @@ -0,0 +1,944 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Final boss, stage 1 (jorg). + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "boss31.h" + +extern void SP_monster_makron(edict_t *self); +qboolean visible(edict_t *self, edict_t *other); +void BossExplode(edict_t *self); +void MakronToss(edict_t *self); +void MakronPrecache(void); +void jorg_dead(edict_t *self); +void jorgBFG(edict_t *self); +void jorgMachineGun(edict_t *self); +void jorg_firebullet(edict_t *self); +void jorg_reattack1(edict_t *self); +void jorg_attack1(edict_t *self); +void jorg_idle(edict_t *self); +void jorg_step_left(edict_t *self); +void jorg_step_right(edict_t *self); +void jorg_death_hit(edict_t *self); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_idle; +static int sound_death; +static int sound_search1; +static int sound_search2; +static int sound_search3; +static int sound_attack1; +static int sound_attack2; +static int sound_firegun; +static int sound_step_left; +static int sound_step_right; +static int sound_death_hit; + +void +jorg_search(edict_t *self) +{ + float r; + + if (!self) + { + return; + } + + r = random(); + + if (r <= 0.3) + { + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + } + else if (r <= 0.6) + { + gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0); + } +} + +/* stand */ +static mframe_t jorg_frames_stand[] = { + {ai_stand, 0, jorg_idle}, + {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}, /* 10 */ + {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}, /* 20 */ + {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}, /* 30 */ + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 19, NULL}, + {ai_stand, 11, jorg_step_left}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 6, NULL}, + {ai_stand, 9, jorg_step_right}, + {ai_stand, 0, NULL}, /* 40 */ + {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, -2, NULL}, + {ai_stand, -17, jorg_step_left}, + {ai_stand, 0, NULL}, + {ai_stand, -12, NULL}, /* 50 */ + {ai_stand, -14, jorg_step_right} /* 51 */ +}; + +mmove_t jorg_move_stand = +{ + FRAME_stand01, + FRAME_stand51, + jorg_frames_stand, + NULL +}; + +void +jorg_idle(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_NORM, 0); +} + +void +jorg_death_hit(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_death_hit, 1, ATTN_NORM, 0); +} + +void +jorg_step_left(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_step_left, 1, ATTN_NORM, 0); +} + +void +jorg_step_right(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_step_right, 1, ATTN_NORM, 0); +} + +void +jorg_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &jorg_move_stand; +} + +static mframe_t jorg_frames_run[] = { + {ai_run, 17, jorg_step_left}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 12, NULL}, + {ai_run, 8, NULL}, + {ai_run, 10, NULL}, + {ai_run, 33, jorg_step_right}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 9, NULL}, + {ai_run, 9, NULL}, + {ai_run, 9, NULL} +}; + +mmove_t jorg_move_run = +{ + FRAME_walk06, + FRAME_walk19, + jorg_frames_run, + NULL +}; + +/* walk */ +static mframe_t jorg_frames_start_walk[] = { + {ai_walk, 5, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 15, NULL} +}; + +mmove_t jorg_move_start_walk = +{ + FRAME_walk01, + FRAME_walk05, + jorg_frames_start_walk, + NULL +}; + +static mframe_t jorg_frames_walk[] = { + {ai_walk, 17, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 12, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 33, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 9, NULL} +}; + +mmove_t jorg_move_walk = +{ + FRAME_walk06, + FRAME_walk19, + jorg_frames_walk, + NULL +}; + +static mframe_t jorg_frames_end_walk[] = { + {ai_walk, 11, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, -8, NULL} +}; + +mmove_t jorg_move_end_walk = +{ + FRAME_walk20, + FRAME_walk25, + jorg_frames_end_walk, + NULL +}; + +void +jorg_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &jorg_move_walk; +} + +void +jorg_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &jorg_move_stand; + } + else + { + self->monsterinfo.currentmove = &jorg_move_run; + } +} + +static mframe_t jorg_frames_pain3[] = { + {ai_move, -28, NULL}, + {ai_move, -6, NULL}, + {ai_move, -3, jorg_step_left}, + {ai_move, -9, NULL}, + {ai_move, 0, jorg_step_right}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -7, NULL}, + {ai_move, 1, NULL}, + {ai_move, -11, NULL}, + {ai_move, -4, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 10, NULL}, + {ai_move, 11, NULL}, + {ai_move, 0, NULL}, + {ai_move, 10, NULL}, + {ai_move, 3, NULL}, + {ai_move, 10, NULL}, + {ai_move, 7, jorg_step_left}, + {ai_move, 17, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, jorg_step_right} +}; + +mmove_t jorg_move_pain3 = +{ + FRAME_pain301, + FRAME_pain325, + jorg_frames_pain3, + jorg_run +}; + +static mframe_t jorg_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t jorg_move_pain2 = +{ + FRAME_pain201, + FRAME_pain203, + jorg_frames_pain2, + jorg_run +}; + +static mframe_t jorg_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t jorg_move_pain1 = +{ + FRAME_pain101, + FRAME_pain103, + jorg_frames_pain1, + jorg_run +}; + +static mframe_t jorg_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 10 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 20 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 30 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 40 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, MakronToss}, + {ai_move, 0, BossExplode} /* 50 */ +}; + +mmove_t jorg_move_death = +{ + FRAME_death01, + FRAME_death50, + jorg_frames_death1, + jorg_dead +}; + +static mframe_t jorg_frames_attack2[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, jorgBFG}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t jorg_move_attack2 = +{ + FRAME_attak201, + FRAME_attak213, + jorg_frames_attack2, + jorg_run +}; + +static mframe_t jorg_frames_start_attack1[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t jorg_move_start_attack1 = +{ + FRAME_attak101, + FRAME_attak108, + jorg_frames_start_attack1, + jorg_attack1 +}; + +static mframe_t jorg_frames_attack1[] = { + {ai_charge, 0, jorg_firebullet}, + {ai_charge, 0, jorg_firebullet}, + {ai_charge, 0, jorg_firebullet}, + {ai_charge, 0, jorg_firebullet}, + {ai_charge, 0, jorg_firebullet}, + {ai_charge, 0, jorg_firebullet} +}; + +mmove_t jorg_move_attack1 = +{ + FRAME_attak109, + FRAME_attak114, + jorg_frames_attack1, + jorg_reattack1 +}; + +static mframe_t jorg_frames_end_attack1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t jorg_move_end_attack1 = +{ + FRAME_attak115, + FRAME_attak118, + jorg_frames_end_attack1, + jorg_run +}; + +void +jorg_reattack1(edict_t *self) +{ + if (!self) + { + return; + } + + if (visible(self, self->enemy)) + { + if (random() < 0.9) + { + self->monsterinfo.currentmove = &jorg_move_attack1; + } + else + { + self->s.sound = 0; + self->monsterinfo.currentmove = &jorg_move_end_attack1; + } + } + else + { + self->s.sound = 0; + self->monsterinfo.currentmove = &jorg_move_end_attack1; + } +} + +void +jorg_attack1(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &jorg_move_attack1; +} + +void +jorg_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + self->s.sound = 0; + + if (level.time < self->pain_debounce_time) + { + return; + } + + /* Lessen the chance of him going into his + pain frames if he takes little damage */ + if (damage <= 40) + { + if (random() <= 0.6) + { + return; + } + } + + /* If he's entering his attack1 or using attack1, + lessen the chance of him going into pain */ + if ((self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak108)) + { + if (random() <= 0.005) + { + return; + } + } + + if ((self->s.frame >= FRAME_attak109) && (self->s.frame <= FRAME_attak114)) + { + if (random() <= 0.00005) + { + return; + } + } + + if ((self->s.frame >= FRAME_attak201) && (self->s.frame <= FRAME_attak208)) + { + if (random() <= 0.005) + { + return; + } + } + + self->pain_debounce_time = level.time + 3; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (damage <= 50) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &jorg_move_pain1; + } + else if (damage <= 100) + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &jorg_move_pain2; + } + else + { + if (random() <= 0.3) + { + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &jorg_move_pain3; + } + } +} + +void +jorgBFG(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_JORG_BFG_1], + forward, right, start); + + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + gi.sound(self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0); + monster_fire_bfg(self, start, dir, 50, 300, 100, 200, MZ2_JORG_BFG_1); +} + +void +jorg_firebullet_right(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_R1], + forward, right, start); + + VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract(target, start, forward); + VectorNormalize(forward); + + monster_fire_bullet(self, start, forward, 6, 4, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + MZ2_JORG_MACHINEGUN_R1); +} + +void +jorg_firebullet_left(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_L1], + forward, right, start); + + VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract(target, start, forward); + VectorNormalize(forward); + + monster_fire_bullet(self, start, forward, 6, 4, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + MZ2_JORG_MACHINEGUN_L1); +} + +void +jorg_firebullet(edict_t *self) +{ + if (!self) + { + return; + } + + jorg_firebullet_left(self); + jorg_firebullet_right(self); +} + +void +jorg_attack(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() <= 0.75) + { + gi.sound(self, CHAN_VOICE, sound_attack1, 1, ATTN_NORM, 0); + self->s.sound = gi.soundindex("boss3/w_loop.wav"); + self->monsterinfo.currentmove = &jorg_move_start_attack1; + } + else + { + gi.sound(self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &jorg_move_attack2; + } +} + +void +jorg_dead(edict_t *self /* unused */) +{ + /* unused, but removal is PITA */ +} + +void +jorg_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage /* unused */, vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->s.sound = 0; + self->count = 0; + self->monsterinfo.currentmove = &jorg_move_death; +} + +qboolean +Jorg_CheckAttack(edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + int enemy_range; + float enemy_yaw; + + if (!self) + { + return false; + } + + if (self->enemy->health > 0) + { + /* see if any entities are in the way of the shot */ + VectorCopy(self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy(self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace(spot1, NULL, NULL, spot2, self, + CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | + CONTENTS_LAVA); + + /* do we have a clear shot? */ + if (tr.ent != self->enemy) + { + return false; + } + } + + enemy_range = range(self, self->enemy); + VectorSubtract(self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + /* melee attack */ + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + { + self->monsterinfo.attack_state = AS_MELEE; + } + else + { + self->monsterinfo.attack_state = AS_MISSILE; + } + + return true; + } + + /* missile attack */ + if (!self->monsterinfo.attack) + { + return false; + } + + if (level.time < self->monsterinfo.attack_finished) + { + return false; + } + + if (enemy_range == RANGE_FAR) + { + return false; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.2; + } + else + { + return false; + } + + if (random() < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2 * random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + { + self->monsterinfo.attack_state = AS_SLIDING; + } + else + { + self->monsterinfo.attack_state = AS_STRAIGHT; + } + } + + return false; +} + +/* + * QUAKED monster_jorg (1 .5 0) (-80 -80 0) (90 90 140) Ambush Trigger_Spawn Sight + */ +void +SP_monster_jorg(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("boss3/bs3pain1.wav"); + sound_pain2 = gi.soundindex("boss3/bs3pain2.wav"); + sound_pain3 = gi.soundindex("boss3/bs3pain3.wav"); + sound_death = gi.soundindex("boss3/bs3deth1.wav"); + sound_attack1 = gi.soundindex("boss3/bs3atck1.wav"); + sound_attack2 = gi.soundindex("boss3/bs3atck2.wav"); + sound_search1 = gi.soundindex("boss3/bs3srch1.wav"); + sound_search2 = gi.soundindex("boss3/bs3srch2.wav"); + sound_search3 = gi.soundindex("boss3/bs3srch3.wav"); + sound_idle = gi.soundindex("boss3/bs3idle1.wav"); + sound_step_left = gi.soundindex("boss3/step1.wav"); + sound_step_right = gi.soundindex("boss3/step2.wav"); + sound_firegun = gi.soundindex("boss3/xfire.wav"); + sound_death_hit = gi.soundindex("boss3/d_hit.wav"); + + MakronPrecache(); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/boss3/jorg/tris.md2"); + self->s.modelindex2 = gi.modelindex("models/monsters/boss3/rider/tris.md2"); + VectorSet(self->mins, -80, -80, 0); + VectorSet(self->maxs, 80, 80, 140); + + self->health = 3000; + self->gib_health = -2000; + self->mass = 1000; + + self->pain = jorg_pain; + self->die = jorg_die; + self->monsterinfo.stand = jorg_stand; + self->monsterinfo.walk = jorg_walk; + self->monsterinfo.run = jorg_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = jorg_attack; + self->monsterinfo.search = jorg_search; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + self->monsterinfo.checkattack = Jorg_CheckAttack; + gi.linkentity(self); + + self->monsterinfo.currentmove = &jorg_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/src/game/monster/boss3/boss31.h b/src/game/monster/boss3/boss31.h new file mode 100644 index 000000000..c19a0f288 --- /dev/null +++ b/src/game/monster/boss3/boss31.h @@ -0,0 +1,217 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Animations for final boss stage 1. + * + * ======================================================================= + */ + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak201 18 +#define FRAME_attak202 19 +#define FRAME_attak203 20 +#define FRAME_attak204 21 +#define FRAME_attak205 22 +#define FRAME_attak206 23 +#define FRAME_attak207 24 +#define FRAME_attak208 25 +#define FRAME_attak209 26 +#define FRAME_attak210 27 +#define FRAME_attak211 28 +#define FRAME_attak212 29 +#define FRAME_attak213 30 +#define FRAME_death01 31 +#define FRAME_death02 32 +#define FRAME_death03 33 +#define FRAME_death04 34 +#define FRAME_death05 35 +#define FRAME_death06 36 +#define FRAME_death07 37 +#define FRAME_death08 38 +#define FRAME_death09 39 +#define FRAME_death10 40 +#define FRAME_death11 41 +#define FRAME_death12 42 +#define FRAME_death13 43 +#define FRAME_death14 44 +#define FRAME_death15 45 +#define FRAME_death16 46 +#define FRAME_death17 47 +#define FRAME_death18 48 +#define FRAME_death19 49 +#define FRAME_death20 50 +#define FRAME_death21 51 +#define FRAME_death22 52 +#define FRAME_death23 53 +#define FRAME_death24 54 +#define FRAME_death25 55 +#define FRAME_death26 56 +#define FRAME_death27 57 +#define FRAME_death28 58 +#define FRAME_death29 59 +#define FRAME_death30 60 +#define FRAME_death31 61 +#define FRAME_death32 62 +#define FRAME_death33 63 +#define FRAME_death34 64 +#define FRAME_death35 65 +#define FRAME_death36 66 +#define FRAME_death37 67 +#define FRAME_death38 68 +#define FRAME_death39 69 +#define FRAME_death40 70 +#define FRAME_death41 71 +#define FRAME_death42 72 +#define FRAME_death43 73 +#define FRAME_death44 74 +#define FRAME_death45 75 +#define FRAME_death46 76 +#define FRAME_death47 77 +#define FRAME_death48 78 +#define FRAME_death49 79 +#define FRAME_death50 80 +#define FRAME_pain101 81 +#define FRAME_pain102 82 +#define FRAME_pain103 83 +#define FRAME_pain201 84 +#define FRAME_pain202 85 +#define FRAME_pain203 86 +#define FRAME_pain301 87 +#define FRAME_pain302 88 +#define FRAME_pain303 89 +#define FRAME_pain304 90 +#define FRAME_pain305 91 +#define FRAME_pain306 92 +#define FRAME_pain307 93 +#define FRAME_pain308 94 +#define FRAME_pain309 95 +#define FRAME_pain310 96 +#define FRAME_pain311 97 +#define FRAME_pain312 98 +#define FRAME_pain313 99 +#define FRAME_pain314 100 +#define FRAME_pain315 101 +#define FRAME_pain316 102 +#define FRAME_pain317 103 +#define FRAME_pain318 104 +#define FRAME_pain319 105 +#define FRAME_pain320 106 +#define FRAME_pain321 107 +#define FRAME_pain322 108 +#define FRAME_pain323 109 +#define FRAME_pain324 110 +#define FRAME_pain325 111 +#define FRAME_stand01 112 +#define FRAME_stand02 113 +#define FRAME_stand03 114 +#define FRAME_stand04 115 +#define FRAME_stand05 116 +#define FRAME_stand06 117 +#define FRAME_stand07 118 +#define FRAME_stand08 119 +#define FRAME_stand09 120 +#define FRAME_stand10 121 +#define FRAME_stand11 122 +#define FRAME_stand12 123 +#define FRAME_stand13 124 +#define FRAME_stand14 125 +#define FRAME_stand15 126 +#define FRAME_stand16 127 +#define FRAME_stand17 128 +#define FRAME_stand18 129 +#define FRAME_stand19 130 +#define FRAME_stand20 131 +#define FRAME_stand21 132 +#define FRAME_stand22 133 +#define FRAME_stand23 134 +#define FRAME_stand24 135 +#define FRAME_stand25 136 +#define FRAME_stand26 137 +#define FRAME_stand27 138 +#define FRAME_stand28 139 +#define FRAME_stand29 140 +#define FRAME_stand30 141 +#define FRAME_stand31 142 +#define FRAME_stand32 143 +#define FRAME_stand33 144 +#define FRAME_stand34 145 +#define FRAME_stand35 146 +#define FRAME_stand36 147 +#define FRAME_stand37 148 +#define FRAME_stand38 149 +#define FRAME_stand39 150 +#define FRAME_stand40 151 +#define FRAME_stand41 152 +#define FRAME_stand42 153 +#define FRAME_stand43 154 +#define FRAME_stand44 155 +#define FRAME_stand45 156 +#define FRAME_stand46 157 +#define FRAME_stand47 158 +#define FRAME_stand48 159 +#define FRAME_stand49 160 +#define FRAME_stand50 161 +#define FRAME_stand51 162 +#define FRAME_walk01 163 +#define FRAME_walk02 164 +#define FRAME_walk03 165 +#define FRAME_walk04 166 +#define FRAME_walk05 167 +#define FRAME_walk06 168 +#define FRAME_walk07 169 +#define FRAME_walk08 170 +#define FRAME_walk09 171 +#define FRAME_walk10 172 +#define FRAME_walk11 173 +#define FRAME_walk12 174 +#define FRAME_walk13 175 +#define FRAME_walk14 176 +#define FRAME_walk15 177 +#define FRAME_walk16 178 +#define FRAME_walk17 179 +#define FRAME_walk18 180 +#define FRAME_walk19 181 +#define FRAME_walk20 182 +#define FRAME_walk21 183 +#define FRAME_walk22 184 +#define FRAME_walk23 185 +#define FRAME_walk24 186 +#define FRAME_walk25 187 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/boss3/boss32.c b/src/game/monster/boss3/boss32.c new file mode 100644 index 000000000..4a14642be --- /dev/null +++ b/src/game/monster/boss3/boss32.c @@ -0,0 +1,1296 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Final boss, stage 2 (makron). + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "boss32.h" + +qboolean visible(edict_t *self, edict_t *other); + +void MakronRailgun(edict_t *self); +void MakronSaveloc(edict_t *self); +void MakronHyperblaster(edict_t *self); +void makron_step_left(edict_t *self); +void makron_step_right(edict_t *self); +void makronBFG(edict_t *self); +void makron_dead(edict_t *self); + +static int sound_pain4; +static int sound_pain5; +static int sound_pain6; +static int sound_death; +static int sound_step_left; +static int sound_step_right; +static int sound_attack_bfg; +static int sound_brainsplorch; +static int sound_prerailgun; +static int sound_popup; +static int sound_taunt1; +static int sound_taunt2; +static int sound_taunt3; +static int sound_hit; + +void +makron_taunt(edict_t *self) +{ + float r; + + if (!self) + { + return; + } + + r = random(); + + if (r <= 0.3) + { + gi.sound(self, CHAN_AUTO, sound_taunt1, 1, ATTN_NONE, 0); + } + else if (r <= 0.6) + { + gi.sound(self, CHAN_AUTO, sound_taunt2, 1, ATTN_NONE, 0); + } + else + { + gi.sound(self, CHAN_AUTO, sound_taunt3, 1, ATTN_NONE, 0); + } +} + +/* stand */ +static mframe_t makron_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}, /* 10 */ + {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}, /* 20 */ + {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}, /* 30 */ + {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}, /* 40 */ + {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}, /* 50 */ + {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} /* 60 */ +}; + +mmove_t makron_move_stand = +{ + FRAME_stand201, + FRAME_stand260, + makron_frames_stand, + NULL +}; + +void +makron_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &makron_move_stand; +} + +static mframe_t makron_frames_run[] = { + {ai_run, 3, makron_step_left}, + {ai_run, 12, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, makron_step_right}, + {ai_run, 6, NULL}, + {ai_run, 12, NULL}, + {ai_run, 9, NULL}, + {ai_run, 6, NULL}, + {ai_run, 12, NULL} +}; + +mmove_t makron_move_run = +{ + FRAME_walk204, + FRAME_walk213, + makron_frames_run, + NULL +}; + +void +makron_hit(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_AUTO, sound_hit, 1, ATTN_NONE, 0); +} + +void +makron_popup(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_popup, 1, ATTN_NONE, 0); +} + +void +makron_step_left(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_step_left, 1, ATTN_NORM, 0); +} + +void +makron_step_right(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_step_right, 1, ATTN_NORM, 0); +} + +void +makron_brainsplorch(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_brainsplorch, 1, ATTN_NORM, 0); +} + +void +makron_prerailgun(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_prerailgun, 1, ATTN_NORM, 0); +} + +static mframe_t makron_frames_walk[] = { + {ai_walk, 3, makron_step_left}, + {ai_walk, 12, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, makron_step_right}, + {ai_walk, 6, NULL}, + {ai_walk, 12, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 12, NULL} +}; + +mmove_t makron_move_walk = +{ + FRAME_walk204, + FRAME_walk213, + makron_frames_walk, + NULL +}; + +void +makron_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &makron_move_walk; +} + +void +makron_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &makron_move_stand; + } + else + { + self->monsterinfo.currentmove = &makron_move_run; + } +} + +static mframe_t makron_frames_pain6[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 10 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, makron_popup}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 20 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, makron_taunt}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t makron_move_pain6 = +{ + FRAME_pain601, + FRAME_pain627, + makron_frames_pain6, + makron_run +}; + +static mframe_t makron_frames_pain5[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t makron_move_pain5 = +{ + FRAME_pain501, + FRAME_pain504, + makron_frames_pain5, + makron_run +}; + +static mframe_t makron_frames_pain4[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t makron_move_pain4 = +{ + FRAME_pain401, + FRAME_pain404, + makron_frames_pain4, + makron_run +}; + +static mframe_t makron_frames_death2[] = { + {ai_move, -15, NULL}, + {ai_move, 3, NULL}, + {ai_move, -12, NULL}, + {ai_move, 0, makron_step_left}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 10 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 11, NULL}, + {ai_move, 12, NULL}, + {ai_move, 11, makron_step_right}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 20 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 30 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 5, NULL}, + {ai_move, 7, NULL}, + {ai_move, 6, makron_step_left}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -1, NULL}, + {ai_move, 2, NULL}, /* 40 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 50 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -6, NULL}, + {ai_move, -4, NULL}, + {ai_move, -6, makron_step_right}, + {ai_move, -4, NULL}, + {ai_move, -4, makron_step_left}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 60 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -2, NULL}, + {ai_move, -5, NULL}, + {ai_move, -3, makron_step_right}, + {ai_move, -8, NULL}, + {ai_move, -3, makron_step_left}, + {ai_move, -7, NULL}, + {ai_move, -4, NULL}, + {ai_move, -4, makron_step_right}, /* 70 */ + {ai_move, -6, NULL}, + {ai_move, -7, NULL}, + {ai_move, 0, makron_step_left}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 80 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, /* 90 */ + {ai_move, 27, makron_hit}, + {ai_move, 26, NULL}, + {ai_move, 0, makron_brainsplorch}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} /* 95 */ +}; + +mmove_t makron_move_death2 = +{ + FRAME_death201, + FRAME_death295, + makron_frames_death2, + makron_dead +}; + +static mframe_t makron_frames_death3[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t makron_move_death3 = +{ + FRAME_death301, + FRAME_death320, + makron_frames_death3, + NULL +}; + +static mframe_t makron_frames_sight[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t makron_move_sight = +{ + FRAME_active01, + FRAME_active13, + makron_frames_sight, + makron_run +}; + +void +makronBFG(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_MAKRON_BFG], + forward, right, start); + + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + gi.sound(self, CHAN_VOICE, sound_attack_bfg, 1, ATTN_NORM, 0); + monster_fire_bfg(self, start, dir, 50, 300, 100, 300, MZ2_MAKRON_BFG); +} + +static mframe_t makron_frames_attack3[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, makronBFG}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t makron_move_attack3 = +{ + FRAME_attak301, + FRAME_attak308, + makron_frames_attack3, + makron_run +}; + +static mframe_t makron_frames_attack4[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, MakronHyperblaster}, /* fire */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t makron_move_attack4 = +{ + FRAME_attak401, + FRAME_attak426, + makron_frames_attack4, + makron_run +}; + +static mframe_t makron_frames_attack5[] = { + {ai_charge, 0, makron_prerailgun}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, MakronSaveloc}, + {ai_move, 0, MakronRailgun}, /* Fire railgun */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t makron_move_attack5 = +{ + FRAME_attak501, + FRAME_attak516, + makron_frames_attack5, + makron_run +}; + +void +MakronSaveloc(edict_t *self) +{ + if (!self) + { + return; + } + + VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */ + self->pos1[2] += self->enemy->viewheight; +} + +void +MakronRailgun(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_MAKRON_RAILGUN_1], + forward, right, start); + + /* calc direction to where we targted */ + VectorSubtract(self->pos1, start, dir); + VectorNormalize(dir); + + monster_fire_railgun(self, start, dir, 50, 100, MZ2_MAKRON_RAILGUN_1); +} + +void +MakronHyperblaster(edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + if (!self) + { + return; + } + + flash_number = MZ2_MAKRON_BLASTER_1 + (self->s.frame - FRAME_attak405); + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + if (self->enemy) + { + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, vec); + vectoangles(vec, vec); + dir[0] = vec[0]; + } + else + { + dir[0] = 0; + } + + if (self->s.frame <= FRAME_attak413) + { + dir[1] = self->s.angles[1] - 10 * (self->s.frame - FRAME_attak413); + } + else + { + dir[1] = self->s.angles[1] + 10 * (self->s.frame - FRAME_attak421); + } + + dir[2] = 0; + + AngleVectors(dir, forward, NULL, NULL); + + monster_fire_blaster(self, start, forward, 15, 1000, + MZ2_MAKRON_BLASTER_1, EF_BLASTER); +} + +void +makron_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + /* Lessen the chance of him + going into his pain frames */ + if (damage <= 25) + { + if (random() < 0.2) + { + return; + } + } + + self->pain_debounce_time = level.time + 3; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (damage <= 40) + { + gi.sound(self, CHAN_VOICE, sound_pain4, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &makron_move_pain4; + } + else if (damage <= 110) + { + gi.sound(self, CHAN_VOICE, sound_pain5, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &makron_move_pain5; + } + else + { + if (damage <= 150) + { + if (random() <= 0.45) + { + gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &makron_move_pain6; + } + } + else + { + if (random() <= 0.35) + { + gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &makron_move_pain6; + } + } + } +} + +void +makron_sight(edict_t *self, edict_t *other /* unused */) +{ +} + +void +makron_attack(edict_t *self) +{ + float r; + + if (!self) + { + return; + } + + r = random(); + + if (r <= 0.3) + { + self->monsterinfo.currentmove = &makron_move_attack3; + } + else if (r <= 0.6) + { + self->monsterinfo.currentmove = &makron_move_attack4; + } + else + { + self->monsterinfo.currentmove = &makron_move_attack5; + } +} + +/* + * Makron Torso. This needs to be spawned in + */ +void +makron_torso_think(edict_t *self) +{ + if (!self) + { + return; + } + + /* detach from the makron if the legs are gone completely */ + if (self->owner && (!self->owner->inuse || (self->owner->health <= self->owner->gib_health))) + { + self->owner = NULL; + } + + /* if the makron is revived the torso was put back on him */ + if (self->owner && self->owner->deadflag != DEAD_DEAD) + { + G_FreeEdict(self); + return; + } + + if (self->owner && (self->owner->monsterinfo.aiflags & AI_RESURRECTING)) + { + self->s.effects |= EF_COLOR_SHELL; + self->s.renderfx |= RF_SHELL_RED; + } + else + { + self->s.effects &= ~EF_COLOR_SHELL; + self->s.renderfx &= ~RF_SHELL_RED; + } + + if (++self->s.frame >= FRAME_death320) + { + self->s.frame = FRAME_death301; + } + + self->nextthink = level.time + FRAMETIME; +} + +static void +makron_torso_origin(edict_t *self, edict_t *torso) +{ + vec3_t v; + trace_t tr; + + AngleVectors(self->s.angles, v, NULL, NULL); + VectorMA(self->s.origin, -84.0f, v, v); + + tr = gi.trace(self->s.origin, torso->mins, torso->maxs, v, self, MASK_SOLID); + + VectorCopy (tr.endpos, torso->s.origin); +} + +void +makron_torso_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage /* unused */, vec3_t point /* unused */) +{ + int n; + + if (self->health > self->gib_health) + { + return; + } + + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", + damage, GIB_ORGANIC); + + for (n = 0; n < 4; n++) + { + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", + damage, GIB_METALLIC); + } + + G_FreeEdict(self); +} + +void +makron_torso(edict_t *self) +{ + edict_t *torso; + + if (!self) + { + return; + } + + torso = G_SpawnOptional(); + + if (!torso) + { + return; + } + + VectorCopy(self->s.angles, torso->s.angles); + VectorSet(torso->mins, -24, -24, 0); + VectorSet(torso->maxs, 24, 24, 16); + makron_torso_origin(self, torso); + + torso->gib_health = -800; + torso->takedamage = DAMAGE_YES; + torso->die = makron_torso_die; + torso->deadflag = DEAD_DEAD; + + torso->owner = self; + torso->movetype = MOVETYPE_TOSS; + torso->solid = SOLID_BBOX; + torso->svflags = SVF_MONSTER|SVF_DEADMONSTER; + torso->clipmask = MASK_MONSTERSOLID; + torso->s.frame = FRAME_death301; + torso->s.modelindex = gi.modelindex("models/monsters/boss3/rider/tris.md2"); + torso->think = makron_torso_think; + torso->nextthink = level.time + 2 * FRAMETIME; + torso->s.sound = gi.soundindex("makron/spine.wav"); + + gi.linkentity(torso); +} + +/* death */ +void +makron_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -48, -48, 0); + VectorSet(self->maxs, 48, 48, 24); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +void +makron_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage /* unused */, vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + self->s.sound = 0; + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + for (n = 0; n < 1 /*4*/; n++) + { + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", + damage, GIB_ORGANIC); + } + + for (n = 0; n < 4; n++) + { + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", + damage, GIB_METALLIC); + } + + ThrowHead(self, "models/objects/gibs/gear/tris.md2", + damage, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + { + return; + } + + /* regular death */ + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + makron_torso(self); + + /* lower bbox since the torso is gone */ + self->maxs[2] = 64; + gi.linkentity (self); + + self->monsterinfo.currentmove = &makron_move_death2; +} + +qboolean +Makron_CheckAttack(edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance; + trace_t tr; + int enemy_range; + float enemy_yaw; + + if (!self) + { + return false; + } + + if (self->enemy->health > 0) + { + /* see if any entities are in the way of the shot */ + VectorCopy(self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy(self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace(spot1, NULL, NULL, spot2, self, + CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | + CONTENTS_LAVA); + + /* do we have a clear shot? */ + if (tr.ent != self->enemy) + { + return false; + } + } + + enemy_range = range(self, self->enemy); + VectorSubtract(self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + self->ideal_yaw = enemy_yaw; + + /* melee attack */ + if (enemy_range == RANGE_MELEE) + { + if (self->monsterinfo.melee) + { + self->monsterinfo.attack_state = AS_MELEE; + } + else + { + self->monsterinfo.attack_state = AS_MISSILE; + } + + return true; + } + + /* missile attack */ + if (!self->monsterinfo.attack) + { + return false; + } + + if (level.time < self->monsterinfo.attack_finished) + { + return false; + } + + if (enemy_range == RANGE_FAR) + { + return false; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.2; + } + else + { + return false; + } + + if (random() < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2 * random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + { + self->monsterinfo.attack_state = AS_SLIDING; + } + else + { + self->monsterinfo.attack_state = AS_STRAIGHT; + } + } + + return false; +} + +/* monster_makron */ + +void +MakronPrecache(void) +{ + sound_pain4 = gi.soundindex("makron/pain3.wav"); + sound_pain5 = gi.soundindex("makron/pain2.wav"); + sound_pain6 = gi.soundindex("makron/pain1.wav"); + sound_death = gi.soundindex("makron/death.wav"); + sound_step_left = gi.soundindex("makron/step1.wav"); + sound_step_right = gi.soundindex("makron/step2.wav"); + sound_attack_bfg = gi.soundindex("makron/bfg_fire.wav"); + sound_brainsplorch = gi.soundindex("makron/brain1.wav"); + sound_prerailgun = gi.soundindex("makron/rail_up.wav"); + sound_popup = gi.soundindex("makron/popup.wav"); + sound_taunt1 = gi.soundindex("makron/voice4.wav"); + sound_taunt2 = gi.soundindex("makron/voice3.wav"); + sound_taunt3 = gi.soundindex("makron/voice.wav"); + sound_hit = gi.soundindex("makron/bhit.wav"); + + gi.modelindex("models/monsters/boss3/rider/tris.md2"); +} + +/* + * QUAKED monster_makron (1 .5 0) (-30 -30 0) (30 30 90) Ambush Trigger_Spawn Sight + */ +void +SP_monster_makron(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + MakronPrecache(); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/boss3/rider/tris.md2"); + VectorSet(self->mins, -30, -30, 0); + VectorSet(self->maxs, 30, 30, 90); + + self->health = 3000; + self->gib_health = -2000; + self->mass = 500; + + self->pain = makron_pain; + self->die = makron_die; + self->monsterinfo.stand = makron_stand; + self->monsterinfo.walk = makron_walk; + self->monsterinfo.run = makron_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = makron_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = makron_sight; + self->monsterinfo.checkattack = Makron_CheckAttack; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &makron_move_sight; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} + +void +MakronSpawn(edict_t *self) +{ + vec3_t vec; + edict_t *enemy; + edict_t *oldenemy; + + if (!self) + { + return; + } + + /* spawning can mess with enemy state so clear it temporarily */ + enemy = self->enemy; + self->enemy = NULL; + + oldenemy = self->oldenemy; + self->oldenemy = NULL; + + SP_monster_makron(self); + if (self->think) + { + self->think(self); + } + + /* and re-link enemy state now that he's spawned */ + if (enemy && enemy->inuse && enemy->deadflag != DEAD_DEAD) + { + self->enemy = enemy; + } + + if (oldenemy && oldenemy->inuse && oldenemy->deadflag != DEAD_DEAD) + { + self->oldenemy = oldenemy; + } + + if (!self->enemy) + { + self->enemy = self->oldenemy; + self->oldenemy = NULL; + } + + enemy = self->enemy; + + if (enemy) + { + FoundTarget(self); + VectorCopy(self->pos1, self->monsterinfo.last_sighting); + } + + if (enemy && visible(self, enemy)) + { + VectorSubtract(enemy->s.origin, self->s.origin, vec); + self->s.angles[YAW] = vectoyaw(vec); + VectorNormalize(vec); + } + else + { + AngleVectors(self->s.angles, vec, NULL, NULL); + } + + VectorScale(vec, 400, self->velocity); + /* the jump frames are fixed length so best to normalize the up speed */ + self->velocity[2] = 200.0f * (sv_gravity->value / 800.0f); + + self->groundentity = NULL; + self->s.origin[2] += 1; + gi.linkentity(self); + + self->pain_debounce_time = level.time + 1; + + self->monsterinfo.currentmove = &makron_move_sight; +} + +/* + * Jorg is just about dead, so set up to launch Makron out + */ +void +MakronToss(edict_t *self) +{ + edict_t *ent; + + if (!self) + { + return; + } + + ent = G_Spawn(); + ent->classname = "monster_makron"; + ent->nextthink = level.time + 0.8; + ent->think = MakronSpawn; + ent->target = self->target; + VectorCopy(self->s.origin, ent->s.origin); + VectorCopy(self->s.angles, ent->s.angles); + VectorCopy(self->monsterinfo.last_sighting, ent->pos1); + + ent->enemy = self->enemy; + ent->oldenemy = self->oldenemy; +} diff --git a/src/game/monster/boss3/boss32.h b/src/game/monster/boss3/boss32.h new file mode 100644 index 000000000..7e6f26b9d --- /dev/null +++ b/src/game/monster/boss3/boss32.h @@ -0,0 +1,520 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Final boss, stage 2 (makron). + * + * ======================================================================= + */ + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak201 18 +#define FRAME_attak202 19 +#define FRAME_attak203 20 +#define FRAME_attak204 21 +#define FRAME_attak205 22 +#define FRAME_attak206 23 +#define FRAME_attak207 24 +#define FRAME_attak208 25 +#define FRAME_attak209 26 +#define FRAME_attak210 27 +#define FRAME_attak211 28 +#define FRAME_attak212 29 +#define FRAME_attak213 30 +#define FRAME_death01 31 +#define FRAME_death02 32 +#define FRAME_death03 33 +#define FRAME_death04 34 +#define FRAME_death05 35 +#define FRAME_death06 36 +#define FRAME_death07 37 +#define FRAME_death08 38 +#define FRAME_death09 39 +#define FRAME_death10 40 +#define FRAME_death11 41 +#define FRAME_death12 42 +#define FRAME_death13 43 +#define FRAME_death14 44 +#define FRAME_death15 45 +#define FRAME_death16 46 +#define FRAME_death17 47 +#define FRAME_death18 48 +#define FRAME_death19 49 +#define FRAME_death20 50 +#define FRAME_death21 51 +#define FRAME_death22 52 +#define FRAME_death23 53 +#define FRAME_death24 54 +#define FRAME_death25 55 +#define FRAME_death26 56 +#define FRAME_death27 57 +#define FRAME_death28 58 +#define FRAME_death29 59 +#define FRAME_death30 60 +#define FRAME_death31 61 +#define FRAME_death32 62 +#define FRAME_death33 63 +#define FRAME_death34 64 +#define FRAME_death35 65 +#define FRAME_death36 66 +#define FRAME_death37 67 +#define FRAME_death38 68 +#define FRAME_death39 69 +#define FRAME_death40 70 +#define FRAME_death41 71 +#define FRAME_death42 72 +#define FRAME_death43 73 +#define FRAME_death44 74 +#define FRAME_death45 75 +#define FRAME_death46 76 +#define FRAME_death47 77 +#define FRAME_death48 78 +#define FRAME_death49 79 +#define FRAME_death50 80 +#define FRAME_pain101 81 +#define FRAME_pain102 82 +#define FRAME_pain103 83 +#define FRAME_pain201 84 +#define FRAME_pain202 85 +#define FRAME_pain203 86 +#define FRAME_pain301 87 +#define FRAME_pain302 88 +#define FRAME_pain303 89 +#define FRAME_pain304 90 +#define FRAME_pain305 91 +#define FRAME_pain306 92 +#define FRAME_pain307 93 +#define FRAME_pain308 94 +#define FRAME_pain309 95 +#define FRAME_pain310 96 +#define FRAME_pain311 97 +#define FRAME_pain312 98 +#define FRAME_pain313 99 +#define FRAME_pain314 100 +#define FRAME_pain315 101 +#define FRAME_pain316 102 +#define FRAME_pain317 103 +#define FRAME_pain318 104 +#define FRAME_pain319 105 +#define FRAME_pain320 106 +#define FRAME_pain321 107 +#define FRAME_pain322 108 +#define FRAME_pain323 109 +#define FRAME_pain324 110 +#define FRAME_pain325 111 +#define FRAME_stand01 112 +#define FRAME_stand02 113 +#define FRAME_stand03 114 +#define FRAME_stand04 115 +#define FRAME_stand05 116 +#define FRAME_stand06 117 +#define FRAME_stand07 118 +#define FRAME_stand08 119 +#define FRAME_stand09 120 +#define FRAME_stand10 121 +#define FRAME_stand11 122 +#define FRAME_stand12 123 +#define FRAME_stand13 124 +#define FRAME_stand14 125 +#define FRAME_stand15 126 +#define FRAME_stand16 127 +#define FRAME_stand17 128 +#define FRAME_stand18 129 +#define FRAME_stand19 130 +#define FRAME_stand20 131 +#define FRAME_stand21 132 +#define FRAME_stand22 133 +#define FRAME_stand23 134 +#define FRAME_stand24 135 +#define FRAME_stand25 136 +#define FRAME_stand26 137 +#define FRAME_stand27 138 +#define FRAME_stand28 139 +#define FRAME_stand29 140 +#define FRAME_stand30 141 +#define FRAME_stand31 142 +#define FRAME_stand32 143 +#define FRAME_stand33 144 +#define FRAME_stand34 145 +#define FRAME_stand35 146 +#define FRAME_stand36 147 +#define FRAME_stand37 148 +#define FRAME_stand38 149 +#define FRAME_stand39 150 +#define FRAME_stand40 151 +#define FRAME_stand41 152 +#define FRAME_stand42 153 +#define FRAME_stand43 154 +#define FRAME_stand44 155 +#define FRAME_stand45 156 +#define FRAME_stand46 157 +#define FRAME_stand47 158 +#define FRAME_stand48 159 +#define FRAME_stand49 160 +#define FRAME_stand50 161 +#define FRAME_stand51 162 +#define FRAME_walk01 163 +#define FRAME_walk02 164 +#define FRAME_walk03 165 +#define FRAME_walk04 166 +#define FRAME_walk05 167 +#define FRAME_walk06 168 +#define FRAME_walk07 169 +#define FRAME_walk08 170 +#define FRAME_walk09 171 +#define FRAME_walk10 172 +#define FRAME_walk11 173 +#define FRAME_walk12 174 +#define FRAME_walk13 175 +#define FRAME_walk14 176 +#define FRAME_walk15 177 +#define FRAME_walk16 178 +#define FRAME_walk17 179 +#define FRAME_walk18 180 +#define FRAME_walk19 181 +#define FRAME_walk20 182 +#define FRAME_walk21 183 +#define FRAME_walk22 184 +#define FRAME_walk23 185 +#define FRAME_walk24 186 +#define FRAME_walk25 187 +#define FRAME_active01 188 +#define FRAME_active02 189 +#define FRAME_active03 190 +#define FRAME_active04 191 +#define FRAME_active05 192 +#define FRAME_active06 193 +#define FRAME_active07 194 +#define FRAME_active08 195 +#define FRAME_active09 196 +#define FRAME_active10 197 +#define FRAME_active11 198 +#define FRAME_active12 199 +#define FRAME_active13 200 +#define FRAME_attak301 201 +#define FRAME_attak302 202 +#define FRAME_attak303 203 +#define FRAME_attak304 204 +#define FRAME_attak305 205 +#define FRAME_attak306 206 +#define FRAME_attak307 207 +#define FRAME_attak308 208 +#define FRAME_attak401 209 +#define FRAME_attak402 210 +#define FRAME_attak403 211 +#define FRAME_attak404 212 +#define FRAME_attak405 213 +#define FRAME_attak406 214 +#define FRAME_attak407 215 +#define FRAME_attak408 216 +#define FRAME_attak409 217 +#define FRAME_attak410 218 +#define FRAME_attak411 219 +#define FRAME_attak412 220 +#define FRAME_attak413 221 +#define FRAME_attak414 222 +#define FRAME_attak415 223 +#define FRAME_attak416 224 +#define FRAME_attak417 225 +#define FRAME_attak418 226 +#define FRAME_attak419 227 +#define FRAME_attak420 228 +#define FRAME_attak421 229 +#define FRAME_attak422 230 +#define FRAME_attak423 231 +#define FRAME_attak424 232 +#define FRAME_attak425 233 +#define FRAME_attak426 234 +#define FRAME_attak501 235 +#define FRAME_attak502 236 +#define FRAME_attak503 237 +#define FRAME_attak504 238 +#define FRAME_attak505 239 +#define FRAME_attak506 240 +#define FRAME_attak507 241 +#define FRAME_attak508 242 +#define FRAME_attak509 243 +#define FRAME_attak510 244 +#define FRAME_attak511 245 +#define FRAME_attak512 246 +#define FRAME_attak513 247 +#define FRAME_attak514 248 +#define FRAME_attak515 249 +#define FRAME_attak516 250 +#define FRAME_death201 251 +#define FRAME_death202 252 +#define FRAME_death203 253 +#define FRAME_death204 254 +#define FRAME_death205 255 +#define FRAME_death206 256 +#define FRAME_death207 257 +#define FRAME_death208 258 +#define FRAME_death209 259 +#define FRAME_death210 260 +#define FRAME_death211 261 +#define FRAME_death212 262 +#define FRAME_death213 263 +#define FRAME_death214 264 +#define FRAME_death215 265 +#define FRAME_death216 266 +#define FRAME_death217 267 +#define FRAME_death218 268 +#define FRAME_death219 269 +#define FRAME_death220 270 +#define FRAME_death221 271 +#define FRAME_death222 272 +#define FRAME_death223 273 +#define FRAME_death224 274 +#define FRAME_death225 275 +#define FRAME_death226 276 +#define FRAME_death227 277 +#define FRAME_death228 278 +#define FRAME_death229 279 +#define FRAME_death230 280 +#define FRAME_death231 281 +#define FRAME_death232 282 +#define FRAME_death233 283 +#define FRAME_death234 284 +#define FRAME_death235 285 +#define FRAME_death236 286 +#define FRAME_death237 287 +#define FRAME_death238 288 +#define FRAME_death239 289 +#define FRAME_death240 290 +#define FRAME_death241 291 +#define FRAME_death242 292 +#define FRAME_death243 293 +#define FRAME_death244 294 +#define FRAME_death245 295 +#define FRAME_death246 296 +#define FRAME_death247 297 +#define FRAME_death248 298 +#define FRAME_death249 299 +#define FRAME_death250 300 +#define FRAME_death251 301 +#define FRAME_death252 302 +#define FRAME_death253 303 +#define FRAME_death254 304 +#define FRAME_death255 305 +#define FRAME_death256 306 +#define FRAME_death257 307 +#define FRAME_death258 308 +#define FRAME_death259 309 +#define FRAME_death260 310 +#define FRAME_death261 311 +#define FRAME_death262 312 +#define FRAME_death263 313 +#define FRAME_death264 314 +#define FRAME_death265 315 +#define FRAME_death266 316 +#define FRAME_death267 317 +#define FRAME_death268 318 +#define FRAME_death269 319 +#define FRAME_death270 320 +#define FRAME_death271 321 +#define FRAME_death272 322 +#define FRAME_death273 323 +#define FRAME_death274 324 +#define FRAME_death275 325 +#define FRAME_death276 326 +#define FRAME_death277 327 +#define FRAME_death278 328 +#define FRAME_death279 329 +#define FRAME_death280 330 +#define FRAME_death281 331 +#define FRAME_death282 332 +#define FRAME_death283 333 +#define FRAME_death284 334 +#define FRAME_death285 335 +#define FRAME_death286 336 +#define FRAME_death287 337 +#define FRAME_death288 338 +#define FRAME_death289 339 +#define FRAME_death290 340 +#define FRAME_death291 341 +#define FRAME_death292 342 +#define FRAME_death293 343 +#define FRAME_death294 344 +#define FRAME_death295 345 +#define FRAME_death301 346 +#define FRAME_death302 347 +#define FRAME_death303 348 +#define FRAME_death304 349 +#define FRAME_death305 350 +#define FRAME_death306 351 +#define FRAME_death307 352 +#define FRAME_death308 353 +#define FRAME_death309 354 +#define FRAME_death310 355 +#define FRAME_death311 356 +#define FRAME_death312 357 +#define FRAME_death313 358 +#define FRAME_death314 359 +#define FRAME_death315 360 +#define FRAME_death316 361 +#define FRAME_death317 362 +#define FRAME_death318 363 +#define FRAME_death319 364 +#define FRAME_death320 365 +#define FRAME_jump01 366 +#define FRAME_jump02 367 +#define FRAME_jump03 368 +#define FRAME_jump04 369 +#define FRAME_jump05 370 +#define FRAME_jump06 371 +#define FRAME_jump07 372 +#define FRAME_jump08 373 +#define FRAME_jump09 374 +#define FRAME_jump10 375 +#define FRAME_jump11 376 +#define FRAME_jump12 377 +#define FRAME_jump13 378 +#define FRAME_pain401 379 +#define FRAME_pain402 380 +#define FRAME_pain403 381 +#define FRAME_pain404 382 +#define FRAME_pain501 383 +#define FRAME_pain502 384 +#define FRAME_pain503 385 +#define FRAME_pain504 386 +#define FRAME_pain601 387 +#define FRAME_pain602 388 +#define FRAME_pain603 389 +#define FRAME_pain604 390 +#define FRAME_pain605 391 +#define FRAME_pain606 392 +#define FRAME_pain607 393 +#define FRAME_pain608 394 +#define FRAME_pain609 395 +#define FRAME_pain610 396 +#define FRAME_pain611 397 +#define FRAME_pain612 398 +#define FRAME_pain613 399 +#define FRAME_pain614 400 +#define FRAME_pain615 401 +#define FRAME_pain616 402 +#define FRAME_pain617 403 +#define FRAME_pain618 404 +#define FRAME_pain619 405 +#define FRAME_pain620 406 +#define FRAME_pain621 407 +#define FRAME_pain622 408 +#define FRAME_pain623 409 +#define FRAME_pain624 410 +#define FRAME_pain625 411 +#define FRAME_pain626 412 +#define FRAME_pain627 413 +#define FRAME_stand201 414 +#define FRAME_stand202 415 +#define FRAME_stand203 416 +#define FRAME_stand204 417 +#define FRAME_stand205 418 +#define FRAME_stand206 419 +#define FRAME_stand207 420 +#define FRAME_stand208 421 +#define FRAME_stand209 422 +#define FRAME_stand210 423 +#define FRAME_stand211 424 +#define FRAME_stand212 425 +#define FRAME_stand213 426 +#define FRAME_stand214 427 +#define FRAME_stand215 428 +#define FRAME_stand216 429 +#define FRAME_stand217 430 +#define FRAME_stand218 431 +#define FRAME_stand219 432 +#define FRAME_stand220 433 +#define FRAME_stand221 434 +#define FRAME_stand222 435 +#define FRAME_stand223 436 +#define FRAME_stand224 437 +#define FRAME_stand225 438 +#define FRAME_stand226 439 +#define FRAME_stand227 440 +#define FRAME_stand228 441 +#define FRAME_stand229 442 +#define FRAME_stand230 443 +#define FRAME_stand231 444 +#define FRAME_stand232 445 +#define FRAME_stand233 446 +#define FRAME_stand234 447 +#define FRAME_stand235 448 +#define FRAME_stand236 449 +#define FRAME_stand237 450 +#define FRAME_stand238 451 +#define FRAME_stand239 452 +#define FRAME_stand240 453 +#define FRAME_stand241 454 +#define FRAME_stand242 455 +#define FRAME_stand243 456 +#define FRAME_stand244 457 +#define FRAME_stand245 458 +#define FRAME_stand246 459 +#define FRAME_stand247 460 +#define FRAME_stand248 461 +#define FRAME_stand249 462 +#define FRAME_stand250 463 +#define FRAME_stand251 464 +#define FRAME_stand252 465 +#define FRAME_stand253 466 +#define FRAME_stand254 467 +#define FRAME_stand255 468 +#define FRAME_stand256 469 +#define FRAME_stand257 470 +#define FRAME_stand258 471 +#define FRAME_stand259 472 +#define FRAME_stand260 473 +#define FRAME_walk201 474 +#define FRAME_walk202 475 +#define FRAME_walk203 476 +#define FRAME_walk204 477 +#define FRAME_walk205 478 +#define FRAME_walk206 479 +#define FRAME_walk207 480 +#define FRAME_walk208 481 +#define FRAME_walk209 482 +#define FRAME_walk210 483 +#define FRAME_walk211 484 +#define FRAME_walk212 485 +#define FRAME_walk213 486 +#define FRAME_walk214 487 +#define FRAME_walk215 488 +#define FRAME_walk216 489 +#define FRAME_walk217 490 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/boss5/boss5.c b/src/game/monster/boss5/boss5.c new file mode 100644 index 000000000..9f84a1667 --- /dev/null +++ b/src/game/monster/boss5/boss5.c @@ -0,0 +1,906 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * boss 5, only found in xatrix + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "../supertank/supertank.h" + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; +static int sound_search2; +static int tread_sound; + +qboolean visible(edict_t *self, edict_t *other); +void BossExplode2(edict_t *self); +void boss5_dead(edict_t *self); +void boss5Rocket(edict_t *self); +void boss5MachineGun(edict_t *self); +void boss5_reattack1(edict_t *self); + +void +TreadSound2(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0); +} + +void +boss5_search(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + } +} + +/* stand */ +static mframe_t boss5_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}, + {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 boss5_move_stand = { + FRAME_stand_1, + FRAME_stand_60, + boss5_frames_stand, + NULL +}; + +void +boss5_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &boss5_move_stand; +} + +static mframe_t boss5_frames_run[] = { + {ai_run, 12, TreadSound2}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL} +}; + +mmove_t boss5_move_run = { + FRAME_forwrd_1, + FRAME_forwrd_18, + boss5_frames_run, + NULL +}; + +/* walk */ +static mframe_t boss5_frames_forward[] = { + {ai_walk, 4, TreadSound2}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL} +}; + +mmove_t boss5_move_forward = { + FRAME_forwrd_1, + FRAME_forwrd_18, + boss5_frames_forward, + NULL +}; + +void +boss5_forward(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &boss5_move_forward; +} + +void +boss5_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &boss5_move_forward; +} + +void +boss5_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &boss5_move_stand; + } + else + { + self->monsterinfo.currentmove = &boss5_move_run; + } +} + +static mframe_t boss5_frames_turn_right[] = { + {ai_move, 0, TreadSound2}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss5_move_turn_right = { + FRAME_right_1, + FRAME_right_18, + boss5_frames_turn_right, + boss5_run +}; + +static mframe_t boss5_frames_turn_left[] = { + {ai_move, 0, TreadSound2}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss5_move_turn_left = { + FRAME_left_1, + FRAME_left_18, + boss5_frames_turn_left, + boss5_run +}; + +static mframe_t boss5_frames_pain3[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss5_move_pain3 = { + FRAME_pain3_9, + FRAME_pain3_12, + boss5_frames_pain3, + boss5_run +}; + +static mframe_t boss5_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss5_move_pain2 = { + FRAME_pain2_5, + FRAME_pain2_8, + boss5_frames_pain2, + boss5_run +}; + +static mframe_t boss5_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss5_move_pain1 = { + FRAME_pain1_1, + FRAME_pain1_4, + boss5_frames_pain1, + boss5_run +}; + +static mframe_t boss5_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, BossExplode2} +}; + +mmove_t boss5_move_death = { + FRAME_death_1, + FRAME_death_24, + boss5_frames_death1, + boss5_dead +}; + +static mframe_t boss5_frames_backward[] = { + {ai_walk, 0, TreadSound2}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL} +}; + +mmove_t boss5_move_backward = { + FRAME_backwd_1, + FRAME_backwd_18, + boss5_frames_backward, + NULL +}; + +static mframe_t boss5_frames_attack4[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss5_move_attack4 = { + FRAME_attak4_1, + FRAME_attak4_6, + boss5_frames_attack4, + boss5_run +}; + +static mframe_t boss5_frames_attack3[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss5_move_attack3 = { + FRAME_attak3_1, + FRAME_attak3_27, + boss5_frames_attack3, + boss5_run +}; + +static mframe_t boss5_frames_attack2[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, boss5Rocket}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, boss5Rocket}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, boss5Rocket}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss5_move_attack2 = { + FRAME_attak2_1, + FRAME_attak2_27, + boss5_frames_attack2, + boss5_run +}; + +static mframe_t boss5_frames_attack1[] = { + {ai_charge, 0, boss5MachineGun}, + {ai_charge, 0, boss5MachineGun}, + {ai_charge, 0, boss5MachineGun}, + {ai_charge, 0, boss5MachineGun}, + {ai_charge, 0, boss5MachineGun}, + {ai_charge, 0, boss5MachineGun}, +}; + +mmove_t boss5_move_attack1 = { + FRAME_attak1_1, + FRAME_attak1_6, + boss5_frames_attack1, + boss5_reattack1 +}; + +static mframe_t boss5_frames_end_attack1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t boss5_move_end_attack1 = { + FRAME_attak1_7, + FRAME_attak1_20, + boss5_frames_end_attack1, + boss5_run +}; + +void +boss5_reattack1(edict_t *self) +{ + if (!self) + { + return; + } + + if (visible(self, self->enemy)) + { + if (random() < 0.9) + { + self->monsterinfo.currentmove = &boss5_move_attack1; + } + else + { + self->monsterinfo.currentmove = &boss5_move_end_attack1; + } + } + else + { + self->monsterinfo.currentmove = &boss5_move_end_attack1; + } +} + +void +boss5_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + /* Lessen the chance of him going into his pain frames */ + if (damage <= 25) + { + if (random() < 0.2) + { + return; + } + } + + /* Don't go into pain if he's firing his rockets */ + if (skill->value >= SKILL_HARD) + { + if ((self->s.frame >= FRAME_attak2_1) && + (self->s.frame <= FRAME_attak2_14)) + { + return; + } + } + + self->pain_debounce_time = level.time + 3; + + if (damage <= 10) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &boss5_move_pain1; + } + else if (damage <= 25) + { + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &boss5_move_pain2; + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &boss5_move_pain3; + } +} + +void +boss5Rocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + if (!self) + { + return; + } + + if (self->s.frame == FRAME_attak2_8) + { + flash_number = MZ2_SUPERTANK_ROCKET_1; + } + else if (self->s.frame == FRAME_attak2_11) + { + flash_number = MZ2_SUPERTANK_ROCKET_2; + } + else + { + flash_number = MZ2_SUPERTANK_ROCKET_3; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + + monster_fire_rocket(self, start, dir, 50, 500, flash_number); +} + +void +boss5MachineGun(edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + if (!self) + { + return; + } + + flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1); + + dir[0] = 0; + dir[1] = self->s.angles[1]; + dir[2] = 0; + + AngleVectors(dir, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + if (self->enemy) + { + VectorCopy(self->enemy->s.origin, vec); + VectorMA(vec, 0, self->enemy->velocity, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, forward); + VectorNormalize(forward); + } + + monster_fire_bullet(self, start, forward, 6, 4, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + flash_number); +} + +void +boss5_attack(edict_t *self) +{ + vec3_t vec; + float range; + + if (!self) + { + return; + } + + VectorSubtract(self->enemy->s.origin, self->s.origin, vec); + range = VectorLength(vec); + + if (range <= 160) + { + self->monsterinfo.currentmove = &boss5_move_attack1; + } + else + { + /* fire rockets more often at distance */ + if (random() < 0.3) + { + self->monsterinfo.currentmove = &boss5_move_attack1; + } + else + { + self->monsterinfo.currentmove = &boss5_move_attack2; + } + } +} + +/* death */ + +void +boss5_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -60, -60, 0); + VectorSet(self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +void +BossExplode2(edict_t *self) +{ + vec3_t org; + int n; + + if (!self) + { + return; + } + + self->think = BossExplode2; + VectorCopy(self->s.origin, org); + org[2] += 24 + (rand() & 15); + + switch (self->count++) + { + case 0: + org[0] -= 24; + org[1] -= 24; + break; + case 1: + org[0] += 24; + org[1] += 24; + break; + case 2: + org[0] += 24; + org[1] -= 24; + break; + case 3: + org[0] -= 24; + org[1] += 24; + break; + case 4: + org[0] -= 48; + org[1] -= 48; + break; + case 5: + org[0] += 48; + org[1] += 48; + break; + case 6: + org[0] -= 48; + org[1] += 48; + break; + case 7: + org[0] += 48; + org[1] -= 48; + break; + case 8: + self->s.sound = 0; + + for (n = 0; n < 4; n++) + { + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", + 500, GIB_ORGANIC); + } + + for (n = 0; n < 8; n++) + { + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", + 500, GIB_METALLIC); + } + + ThrowGib(self, "models/objects/gibs/chest/tris.md2", + 500, GIB_ORGANIC); + ThrowHead(self, "models/objects/gibs/gear/tris.md2", + 500, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_PVS); + + self->nextthink = level.time + 0.1; +} + +void +boss5_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage /* unused */, + vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &boss5_move_death; +} + +/* + * QUAKED monster_boss5 (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight + */ +void +SP_monster_boss5(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("bosstank/btkpain1.wav"); + sound_pain2 = gi.soundindex("bosstank/btkpain2.wav"); + sound_pain3 = gi.soundindex("bosstank/btkpain3.wav"); + sound_death = gi.soundindex("bosstank/btkdeth1.wav"); + sound_search1 = gi.soundindex("bosstank/btkunqv1.wav"); + sound_search2 = gi.soundindex("bosstank/btkunqv2.wav"); + + tread_sound = gi.soundindex("bosstank/btkengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/boss5/tris.md2"); + VectorSet(self->mins, -64, -64, 0); + VectorSet(self->maxs, 64, 64, 112); + + self->health = 1500; + self->gib_health = -500; + self->mass = 800; + + self->pain = boss5_pain; + self->die = boss5_die; + self->monsterinfo.stand = boss5_stand; + self->monsterinfo.walk = boss5_walk; + self->monsterinfo.run = boss5_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = boss5_attack; + self->monsterinfo.search = boss5_search; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &boss5_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 400; + + walkmonster_start(self); +} diff --git a/src/game/monster/brain/brain.c b/src/game/monster/brain/brain.c new file mode 100644 index 000000000..5c33870fd --- /dev/null +++ b/src/game/monster/brain/brain.c @@ -0,0 +1,1218 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Brain. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "brain.h" + +static int sound_chest_open; +static int sound_tentacles_extend; +static int sound_tentacles_retract; +static int sound_death; +static int sound_idle1; +static int sound_idle2; +static int sound_idle3; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_melee1; +static int sound_melee2; +static int sound_melee3; + +void brain_run(edict_t *self); +void brain_dead(edict_t *self); + +static int sound_step; +static int sound_step2; + +void +brain_footstep(edict_t *self) +{ + if (!g_monsterfootsteps->value) + return; + + // Lazy loading for savegame compatibility. + if (sound_step == 0 || sound_step2 == 0) + { + sound_step = gi.soundindex("brain/step1.wav"); + sound_step2 = gi.soundindex("brain/step2.wav"); + } + + if (randk() % 2 == 0) + { + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + } +} + + +void +brain_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +brain_search(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +/* STAND */ + +static mframe_t brain_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} +}; + +mmove_t brain_move_stand = +{ + FRAME_stand01, + FRAME_stand30, + brain_frames_stand, + NULL +}; + +void +brain_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &brain_move_stand; +} + +/* IDLE */ + +static mframe_t brain_frames_idle[] = { + {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 brain_move_idle = +{ + FRAME_stand31, + FRAME_stand60, + brain_frames_idle, + brain_stand +}; + +void +brain_idle(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_AUTO, sound_idle3, 1, ATTN_IDLE, 0); + self->monsterinfo.currentmove = &brain_move_idle; +} + +/* WALK */ + +static mframe_t brain_frames_walk1[] = { + {ai_walk, 7, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 3, NULL}, + {ai_walk, 3, brain_footstep}, + {ai_walk, 1, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, -4, NULL}, + {ai_walk, -1, brain_footstep}, + {ai_walk, 2, NULL} +}; + +mmove_t brain_move_walk1 = +{ + FRAME_walk101, + FRAME_walk111, + brain_frames_walk1, + NULL +}; + +void +brain_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &brain_move_walk1; +} + +static mframe_t brain_frames_defense[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t brain_move_defense = +{ + FRAME_defens01, + FRAME_defens08, + brain_frames_defense, + NULL +}; + +static mframe_t brain_frames_pain3[] = { + {ai_move, -2, NULL}, + {ai_move, 2, NULL}, + {ai_move, 1, NULL}, + {ai_move, 3, NULL}, + {ai_move, 0, NULL}, + {ai_move, -4, NULL} +}; + +mmove_t brain_move_pain3 = +{ + FRAME_pain301, + FRAME_pain306, + brain_frames_pain3, + brain_run +}; + +static mframe_t brain_frames_pain2[] = { + {ai_move, -2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 3, NULL}, + {ai_move, 1, NULL}, + {ai_move, -2, NULL} +}; + +mmove_t brain_move_pain2 = +{ + FRAME_pain201, + FRAME_pain208, + brain_frames_pain2, + brain_run +}; + +static mframe_t brain_frames_pain1[] = { + {ai_move, -6, NULL}, + {ai_move, -2, NULL}, + {ai_move, -6, brain_footstep}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 1, NULL}, + {ai_move, 7, NULL}, + {ai_move, 0, NULL}, + {ai_move, 3, brain_footstep}, + {ai_move, -1, NULL} +}; + +mmove_t brain_move_pain1 = +{ + FRAME_pain101, + FRAME_pain121, + brain_frames_pain1, + brain_run +}; + +/* DUCK */ + +void +brain_duck_down(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + return; + } + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + gi.linkentity(self); +} + +void +brain_duck_hold(edict_t *self) +{ + if (!self) + { + return; + } + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +void +brain_duck_up(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + +static mframe_t brain_frames_duck[] = { + {ai_move, 0, NULL}, + {ai_move, -2, brain_duck_down}, + {ai_move, 17, brain_duck_hold}, + {ai_move, -3, brain_footstep}, + {ai_move, -1, brain_duck_up}, + {ai_move, -5, NULL}, + {ai_move, -6, NULL}, + {ai_move, -6, brain_footstep} +}; + +mmove_t brain_move_duck = +{ + FRAME_duck01, + FRAME_duck08, + brain_frames_duck, + brain_run +}; + +void +brain_dodge(edict_t *self, edict_t *attacker, float eta, + trace_t *tr /* unused */) +{ + if (!self || !attacker) + { + return; + } + + if (random() > 0.25) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + } + + self->monsterinfo.pausetime = level.time + eta + 0.5; + self->monsterinfo.currentmove = &brain_move_duck; +} + +static mframe_t brain_frames_death2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 9, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t brain_move_death2 = +{ + FRAME_death201, + FRAME_death205, + brain_frames_death2, + brain_dead +}; + +static mframe_t brain_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -2, NULL}, + {ai_move, 9, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t brain_move_death1 = +{ + FRAME_death101, + FRAME_death118, + brain_frames_death1, + brain_dead +}; + +/* MELEE */ + +void +brain_swing_right(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_melee1, 1, ATTN_NORM, 0); +} + +void +brain_hit_right(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 8); + + if (fire_hit(self, aim, (15 + (randk() % 5)), 40)) + { + gi.sound(self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0); + } +} + +void +brain_swing_left(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_melee2, 1, ATTN_NORM, 0); +} + +void +brain_hit_left(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->mins[0], 8); + + if (fire_hit(self, aim, (15 + (randk() % 5)), 40)) + { + gi.sound(self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0); + } +} + +static mframe_t brain_frames_attack1[] = { + {ai_charge, 8, NULL}, + {ai_charge, 3, NULL}, + {ai_charge, 5, NULL}, + {ai_charge, 0, brain_footstep}, + {ai_charge, -3, brain_swing_right}, + {ai_charge, 0, NULL}, + {ai_charge, -5, NULL}, + {ai_charge, -7, brain_hit_right}, + {ai_charge, 0, NULL}, + {ai_charge, 6, brain_swing_left}, + {ai_charge, 1, NULL}, + {ai_charge, 2, brain_hit_left}, + {ai_charge, -3, NULL}, + {ai_charge, 6, NULL}, + {ai_charge, -1, NULL}, + {ai_charge, -3, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, -11, brain_footstep} +}; + +mmove_t brain_move_attack1 = +{ + FRAME_attak101, + FRAME_attak118, + brain_frames_attack1, + brain_run +}; + +void +brain_chest_open(edict_t *self) +{ + if (!self) + { + return; + } + + self->spawnflags &= ~65536; + self->monsterinfo.power_armor_type = POWER_ARMOR_NONE; + gi.sound(self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0); +} + +void +brain_tentacle_attack(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, 0, 8); + + if (fire_hit(self, aim, (10 + (randk() % 5)), -600) && (skill->value > SKILL_EASY)) + { + self->spawnflags |= 65536; + } + + gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); +} + +void +brain_chest_closed(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + + if (self->spawnflags & 65536) + { + self->spawnflags &= ~65536; + self->monsterinfo.currentmove = &brain_move_attack1; + } +} + +static mframe_t brain_frames_attack2[] = { + {ai_charge, 5, NULL}, + {ai_charge, -4, NULL}, + {ai_charge, -4, NULL}, + {ai_charge, -3, NULL}, + {ai_charge, 0, brain_chest_open}, + {ai_charge, 0, NULL}, + {ai_charge, 13, brain_tentacle_attack}, + {ai_charge, 0, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, -9, brain_chest_closed}, + {ai_charge, 0, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 3, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, -3, NULL}, + {ai_charge, -6, NULL} +}; + +mmove_t brain_move_attack2 = +{ + FRAME_attak201, + FRAME_attak217, + brain_frames_attack2, + brain_run +}; + +void +brain_melee(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() <= 0.5) + { + self->monsterinfo.currentmove = &brain_move_attack1; + } + else + { + self->monsterinfo.currentmove = &brain_move_attack2; + } +} + +static qboolean +brain_tounge_attack_ok(vec3_t start, vec3_t end) +{ + vec3_t dir, angles; + + /* check for max distance */ + VectorSubtract(start, end, dir); + + if (VectorLength(dir) > 512) + { + return false; + } + + /* check for min/max pitch */ + vectoangles(dir, angles); + + if (angles[0] < -180) + { + angles[0] += 360; + } + + if (fabs(angles[0]) > 30) + { + return false; + } + + return true; +} + +void +brain_tounge_attack(edict_t *self) +{ + vec3_t offset, start, f, r, end, dir; + trace_t tr; + int damage; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, NULL); + VectorSet(offset, 24, 0, 16); + G_ProjectSource(self->s.origin, offset, f, r, start); + + VectorCopy(self->enemy->s.origin, end); + + if (!brain_tounge_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + + if (!brain_tounge_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + + if (!brain_tounge_attack_ok(start, end)) + { + return; + } + } + } + + VectorCopy(self->enemy->s.origin, end); + + tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT); + + if (tr.ent != self->enemy) + { + return; + } + + damage = 5; + gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PARASITE_ATTACK); + gi.WriteShort(self - g_edicts); + gi.WritePosition(start); + gi.WritePosition(end); + gi.multicast(self->s.origin, MULTICAST_PVS); + + VectorSubtract(start, end, dir); + T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, + vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_BRAINTENTACLE); + + /* pull the enemy in */ + vec3_t forward; + self->s.origin[2] += 1; + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorScale(forward, -1200, self->enemy->velocity); +} + +/* Brian right eye center */ +struct r_eyeball +{ + float x; + float y; + float z; +} brain_reye[11] = { + {0.746700, 0.238370, 34.167690}, + {-1.076390, 0.238370, 33.386372}, + {-1.335500, 5.334300, 32.177170}, + {-0.175360, 8.846370, 30.635479}, + {-2.757590, 7.804610, 30.150860}, + {-5.575090, 5.152840, 30.056160}, + {-7.017550, 3.262470, 30.552521}, + {-7.915740, 0.638800, 33.176189}, + {-3.915390, 8.285730, 33.976349}, + {-0.913540, 10.933030, 34.141811}, + {-0.369900, 8.923900, 34.189079} +}; + +/* Brain left eye center */ +struct l_eyeball +{ + float x; + float y; + float z; +} brain_leye[11] = { + {-3.364710, 0.327750, 33.938381}, + {-5.140450, 0.493480, 32.659851}, + {-5.341980, 5.646980, 31.277901}, + {-4.134480, 9.277440, 29.925621}, + {-6.598340, 6.815090, 29.322620}, + {-8.610840, 2.529650, 29.251591}, + {-9.231360, 0.093280, 29.747959}, + {-11.004110, 1.936930, 32.395260}, + {-7.878310, 7.648190, 33.148151}, + {-4.947370, 11.430050, 33.313610}, + {-4.332820, 9.444570, 33.526340} +}; + +void +brain_laserbeam(edict_t *self) +{ + vec3_t forward, right, up; + vec3_t tempang, start; + vec3_t dir, angles, end; + edict_t *ent; + + if (!self) + { + return; + } + + if (random() > 0.8) + { + gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), + 1, ATTN_STATIC, 0); + } + + /* check for max distance */ + VectorCopy(self->s.origin, start); + VectorCopy(self->enemy->s.origin, end); + VectorSubtract(end, start, dir); + vectoangles(dir, angles); + + /* dis is my right eye */ + ent = G_Spawn(); + VectorCopy(self->s.origin, ent->s.origin); + VectorCopy(angles, tempang); + AngleVectors(tempang, forward, right, up); + VectorCopy(tempang, ent->s.angles); + VectorCopy(ent->s.origin, start); + VectorMA(start, brain_reye[self->s.frame - FRAME_walk101].x, right, start); + VectorMA(start, brain_reye[self->s.frame - FRAME_walk101].y, forward, start); + VectorMA(start, brain_reye[self->s.frame - FRAME_walk101].z, up, start); + VectorCopy(start, ent->s.origin); + ent->enemy = self->enemy; + ent->owner = self; + ent->dmg = 1; + monster_dabeam(ent); + + /* dis is me left eye */ + ent = G_Spawn(); + VectorCopy(self->s.origin, ent->s.origin); + VectorCopy(angles, tempang); + AngleVectors(tempang, forward, right, up); + VectorCopy(tempang, ent->s.angles); + VectorCopy(ent->s.origin, start); + VectorMA(start, brain_leye[self->s.frame - FRAME_walk101].x, right, start); + VectorMA(start, brain_leye[self->s.frame - FRAME_walk101].y, forward, start); + VectorMA(start, brain_leye[self->s.frame - FRAME_walk101].z, up, start); + VectorCopy(start, ent->s.origin); + ent->enemy = self->enemy; + ent->owner = self; + ent->dmg = 1; + monster_dabeam(ent); +} + +void +brain_laserbeam_reattack(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + if (visible(self, self->enemy)) + { + if (self->enemy->health > 0) + { + self->s.frame = FRAME_walk101; + } + } + } +} + +static mframe_t brain_frames_attack3[] = { + {ai_charge, 5, NULL}, + {ai_charge, -4, NULL}, + {ai_charge, -4, NULL}, + {ai_charge, -3, NULL}, + {ai_charge, 0, brain_chest_open}, + {ai_charge, 0, brain_tounge_attack}, + {ai_charge, 13, NULL}, + {ai_charge, 0, brain_tentacle_attack}, + {ai_charge, 2, NULL}, + {ai_charge, 0, brain_tounge_attack}, + {ai_charge, -9, brain_chest_closed}, + {ai_charge, 0, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 3, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, -3, NULL}, + {ai_charge, -6, NULL} +}; + +mmove_t brain_move_attack3 = { + FRAME_attak201, + FRAME_attak217, + brain_frames_attack3, + brain_run +}; + +static mframe_t brain_frames_attack4[] = { + {ai_charge, 9, brain_laserbeam}, + {ai_charge, 2, brain_laserbeam}, + {ai_charge, 3, brain_laserbeam}, + {ai_charge, 3, brain_laserbeam}, + {ai_charge, 1, brain_laserbeam}, + {ai_charge, 0, brain_laserbeam}, + {ai_charge, 0, brain_laserbeam}, + {ai_charge, 10, brain_laserbeam}, + {ai_charge, -4, brain_laserbeam}, + {ai_charge, -1, brain_laserbeam}, + {ai_charge, 2, brain_laserbeam_reattack} +}; + +mmove_t brain_move_attack4 = { + FRAME_walk101, + FRAME_walk111, + brain_frames_attack4, + brain_run +}; + +void +brain_attack(edict_t *self) +{ + int r; + + if (!self) + { + return; + } + + if (random() < 0.8) + { + r = range(self, self->enemy); + + if (r == RANGE_NEAR) + { + if (random() < 0.5) + { + self->monsterinfo.currentmove = &brain_move_attack3; + } + else + { + self->monsterinfo.currentmove = &brain_move_attack4; + } + } + else if (r > RANGE_NEAR) + { + self->monsterinfo.currentmove = &brain_move_attack4; + } + } +} + +/* RUN */ + +static mframe_t brain_frames_run[] = { + {ai_run, 9, NULL}, + {ai_run, 2, NULL}, + {ai_run, 3, NULL}, + {ai_run, 3, brain_footstep}, + {ai_run, 1, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 10, NULL}, + {ai_run, -4, NULL}, + {ai_run, -1, brain_footstep}, + {ai_run, 2, NULL} +}; + +mmove_t brain_move_run = +{ + FRAME_walk101, + FRAME_walk111, + brain_frames_run, + NULL +}; + +void +brain_run(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &brain_move_stand; + } + else + { + self->monsterinfo.currentmove = &brain_move_run; + } +} + +void +brain_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) +{ + float r; + + if (!self) + { + return; + } + + 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; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + r = random(); + + if (r < 0.33) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain1; + } + else if (r < 0.66) + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain2; + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &brain_move_pain3; + } + + /* clear duck flag */ + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } +} + +void +brain_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +void +brain_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage, vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + self->s.effects = 0; + self->monsterinfo.power_armor_type = POWER_ARMOR_NONE; + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), + 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, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (random() <= 0.5) + { + self->monsterinfo.currentmove = &brain_move_death1; + } + else + { + self->monsterinfo.currentmove = &brain_move_death2; + } +} + +void +brain_duck(edict_t *self, float eta) +{ + if (!self) + { + return; + } + + /* has to be done immediately otherwise he can get stuck */ + monster_duck_down(self); + + if (skill->value == SKILL_EASY) + { + /* PMM - stupid dodge */ + self->monsterinfo.duck_wait_time = level.time + eta + 1; + } + else + { + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + } + + self->monsterinfo.currentmove = &brain_move_duck; + self->monsterinfo.nextframe = FRAME_duck01; + return; +} + +/* + * QUAKED monster_brain (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_brain(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + + sound_chest_open = gi.soundindex("brain/brnatck1.wav"); + sound_tentacles_extend = gi.soundindex("brain/brnatck2.wav"); + sound_tentacles_retract = gi.soundindex("brain/brnatck3.wav"); + sound_death = gi.soundindex("brain/brndeth1.wav"); + sound_idle1 = gi.soundindex("brain/brnidle1.wav"); + sound_idle2 = gi.soundindex("brain/brnidle2.wav"); + sound_idle3 = gi.soundindex("brain/brnlens1.wav"); + sound_pain1 = gi.soundindex("brain/brnpain1.wav"); + sound_pain2 = gi.soundindex("brain/brnpain2.wav"); + sound_sight = gi.soundindex("brain/brnsght1.wav"); + sound_search = gi.soundindex("brain/brnsrch1.wav"); + sound_melee1 = gi.soundindex("brain/melee1.wav"); + sound_melee2 = gi.soundindex("brain/melee2.wav"); + sound_melee3 = gi.soundindex("brain/melee3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/brain/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 32); + + self->health = 300; + self->gib_health = -150; + self->mass = 400; + + self->pain = brain_pain; + self->die = brain_die; + + self->monsterinfo.stand = brain_stand; + self->monsterinfo.walk = brain_walk; + self->monsterinfo.run = brain_run; + self->monsterinfo.attack = brain_attack; + self->monsterinfo.dodge = brain_dodge; + self->monsterinfo.duck = brain_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.melee = brain_melee; + self->monsterinfo.sight = brain_sight; + self->monsterinfo.search = brain_search; + self->monsterinfo.idle = brain_idle; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + self->monsterinfo.power_armor_power = 100; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &brain_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/src/game/monster/brain/brain.h b/src/game/monster/brain/brain.h new file mode 100644 index 000000000..ea41221a8 --- /dev/null +++ b/src/game/monster/brain/brain.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Brain animations. + * + * ======================================================================= + * + */ + +#define FRAME_walk101 0 +#define FRAME_walk102 1 +#define FRAME_walk103 2 +#define FRAME_walk104 3 +#define FRAME_walk105 4 +#define FRAME_walk106 5 +#define FRAME_walk107 6 +#define FRAME_walk108 7 +#define FRAME_walk109 8 +#define FRAME_walk110 9 +#define FRAME_walk111 10 +#define FRAME_walk112 11 +#define FRAME_walk113 12 +#define FRAME_walk201 13 +#define FRAME_walk202 14 +#define FRAME_walk203 15 +#define FRAME_walk204 16 +#define FRAME_walk205 17 +#define FRAME_walk206 18 +#define FRAME_walk207 19 +#define FRAME_walk208 20 +#define FRAME_walk209 21 +#define FRAME_walk210 22 +#define FRAME_walk211 23 +#define FRAME_walk212 24 +#define FRAME_walk213 25 +#define FRAME_walk214 26 +#define FRAME_walk215 27 +#define FRAME_walk216 28 +#define FRAME_walk217 29 +#define FRAME_walk218 30 +#define FRAME_walk219 31 +#define FRAME_walk220 32 +#define FRAME_walk221 33 +#define FRAME_walk222 34 +#define FRAME_walk223 35 +#define FRAME_walk224 36 +#define FRAME_walk225 37 +#define FRAME_walk226 38 +#define FRAME_walk227 39 +#define FRAME_walk228 40 +#define FRAME_walk229 41 +#define FRAME_walk230 42 +#define FRAME_walk231 43 +#define FRAME_walk232 44 +#define FRAME_walk233 45 +#define FRAME_walk234 46 +#define FRAME_walk235 47 +#define FRAME_walk236 48 +#define FRAME_walk237 49 +#define FRAME_walk238 50 +#define FRAME_walk239 51 +#define FRAME_walk240 52 +#define FRAME_attak101 53 +#define FRAME_attak102 54 +#define FRAME_attak103 55 +#define FRAME_attak104 56 +#define FRAME_attak105 57 +#define FRAME_attak106 58 +#define FRAME_attak107 59 +#define FRAME_attak108 60 +#define FRAME_attak109 61 +#define FRAME_attak110 62 +#define FRAME_attak111 63 +#define FRAME_attak112 64 +#define FRAME_attak113 65 +#define FRAME_attak114 66 +#define FRAME_attak115 67 +#define FRAME_attak116 68 +#define FRAME_attak117 69 +#define FRAME_attak118 70 +#define FRAME_attak201 71 +#define FRAME_attak202 72 +#define FRAME_attak203 73 +#define FRAME_attak204 74 +#define FRAME_attak205 75 +#define FRAME_attak206 76 +#define FRAME_attak207 77 +#define FRAME_attak208 78 +#define FRAME_attak209 79 +#define FRAME_attak210 80 +#define FRAME_attak211 81 +#define FRAME_attak212 82 +#define FRAME_attak213 83 +#define FRAME_attak214 84 +#define FRAME_attak215 85 +#define FRAME_attak216 86 +#define FRAME_attak217 87 +#define FRAME_pain101 88 +#define FRAME_pain102 89 +#define FRAME_pain103 90 +#define FRAME_pain104 91 +#define FRAME_pain105 92 +#define FRAME_pain106 93 +#define FRAME_pain107 94 +#define FRAME_pain108 95 +#define FRAME_pain109 96 +#define FRAME_pain110 97 +#define FRAME_pain111 98 +#define FRAME_pain112 99 +#define FRAME_pain113 100 +#define FRAME_pain114 101 +#define FRAME_pain115 102 +#define FRAME_pain116 103 +#define FRAME_pain117 104 +#define FRAME_pain118 105 +#define FRAME_pain119 106 +#define FRAME_pain120 107 +#define FRAME_pain121 108 +#define FRAME_pain201 109 +#define FRAME_pain202 110 +#define FRAME_pain203 111 +#define FRAME_pain204 112 +#define FRAME_pain205 113 +#define FRAME_pain206 114 +#define FRAME_pain207 115 +#define FRAME_pain208 116 +#define FRAME_pain301 117 +#define FRAME_pain302 118 +#define FRAME_pain303 119 +#define FRAME_pain304 120 +#define FRAME_pain305 121 +#define FRAME_pain306 122 +#define FRAME_death101 123 +#define FRAME_death102 124 +#define FRAME_death103 125 +#define FRAME_death104 126 +#define FRAME_death105 127 +#define FRAME_death106 128 +#define FRAME_death107 129 +#define FRAME_death108 130 +#define FRAME_death109 131 +#define FRAME_death110 132 +#define FRAME_death111 133 +#define FRAME_death112 134 +#define FRAME_death113 135 +#define FRAME_death114 136 +#define FRAME_death115 137 +#define FRAME_death116 138 +#define FRAME_death117 139 +#define FRAME_death118 140 +#define FRAME_death201 141 +#define FRAME_death202 142 +#define FRAME_death203 143 +#define FRAME_death204 144 +#define FRAME_death205 145 +#define FRAME_duck01 146 +#define FRAME_duck02 147 +#define FRAME_duck03 148 +#define FRAME_duck04 149 +#define FRAME_duck05 150 +#define FRAME_duck06 151 +#define FRAME_duck07 152 +#define FRAME_duck08 153 +#define FRAME_defens01 154 +#define FRAME_defens02 155 +#define FRAME_defens03 156 +#define FRAME_defens04 157 +#define FRAME_defens05 158 +#define FRAME_defens06 159 +#define FRAME_defens07 160 +#define FRAME_defens08 161 +#define FRAME_stand01 162 +#define FRAME_stand02 163 +#define FRAME_stand03 164 +#define FRAME_stand04 165 +#define FRAME_stand05 166 +#define FRAME_stand06 167 +#define FRAME_stand07 168 +#define FRAME_stand08 169 +#define FRAME_stand09 170 +#define FRAME_stand10 171 +#define FRAME_stand11 172 +#define FRAME_stand12 173 +#define FRAME_stand13 174 +#define FRAME_stand14 175 +#define FRAME_stand15 176 +#define FRAME_stand16 177 +#define FRAME_stand17 178 +#define FRAME_stand18 179 +#define FRAME_stand19 180 +#define FRAME_stand20 181 +#define FRAME_stand21 182 +#define FRAME_stand22 183 +#define FRAME_stand23 184 +#define FRAME_stand24 185 +#define FRAME_stand25 186 +#define FRAME_stand26 187 +#define FRAME_stand27 188 +#define FRAME_stand28 189 +#define FRAME_stand29 190 +#define FRAME_stand30 191 +#define FRAME_stand31 192 +#define FRAME_stand32 193 +#define FRAME_stand33 194 +#define FRAME_stand34 195 +#define FRAME_stand35 196 +#define FRAME_stand36 197 +#define FRAME_stand37 198 +#define FRAME_stand38 199 +#define FRAME_stand39 200 +#define FRAME_stand40 201 +#define FRAME_stand41 202 +#define FRAME_stand42 203 +#define FRAME_stand43 204 +#define FRAME_stand44 205 +#define FRAME_stand45 206 +#define FRAME_stand46 207 +#define FRAME_stand47 208 +#define FRAME_stand48 209 +#define FRAME_stand49 210 +#define FRAME_stand50 211 +#define FRAME_stand51 212 +#define FRAME_stand52 213 +#define FRAME_stand53 214 +#define FRAME_stand54 215 +#define FRAME_stand55 216 +#define FRAME_stand56 217 +#define FRAME_stand57 218 +#define FRAME_stand58 219 +#define FRAME_stand59 220 +#define FRAME_stand60 221 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/carrier/carrier.c b/src/game/monster/carrier/carrier.c new file mode 100644 index 000000000..e3a44a1f2 --- /dev/null +++ b/src/game/monster/carrier/carrier.c @@ -0,0 +1,1597 @@ +/* + * Copyright (c) ZeniMax Media Inc. + * Licensed under the GNU General Public License 2.0. + */ + +/* + * ============================================================================== + * + * Carrier. + * + * ============================================================================== + */ + +#include "../../header/local.h" +#include "carrier.h" + +#define CARRIER_ROCKET_TIME 2 /* number of seconds between rocket shots */ +#define CARRIER_ROCKET_SPEED 750 +#define NUM_FLYERS_SPAWNED 6 /* max # of flyers he can spawn */ +#define RAIL_FIRE_TIME 3 + +void BossExplode(edict_t *self); +void Grenade_Explode(edict_t *ent); + +void carrier_run(edict_t *self); +void carrier_stand(edict_t *self); +void carrier_dead(edict_t *self); +void carrier_attack(edict_t *self); +void carrier_attack_mg(edict_t *self); +void carrier_reattack_mg(edict_t *self); +void carrier_die(edict_t *self, + edict_t *inflictor, + edict_t *attacker, + int damage, + vec3_t point); +void carrier_attack_gren(edict_t *self); +void carrier_reattack_gren(edict_t *self); +void carrier_start_spawn(edict_t *self); +void carrier_spawn_check(edict_t *self); +void carrier_prep_spawn(edict_t *self); +void CarrierMachineGunHold(edict_t *self); +void CarrierRocket(edict_t *self); + +qboolean infront(edict_t *self, edict_t *other); +qboolean inback(edict_t *self, edict_t *other); +qboolean below(edict_t *self, edict_t *other); +void drawbbox(edict_t *self); +void ED_CallSpawn(edict_t *ent); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_sight; +static int sound_rail; +static int sound_spawn; + +float orig_yaw_speed; + +vec3_t flyer_mins = {-16, -16, -24}; +vec3_t flyer_maxs = {16, 16, 16}; + +extern mmove_t flyer_move_attack2, flyer_move_attack3, flyer_move_kamikaze; + +void +carrier_sight(edict_t *self, edict_t *other /* other */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +CarrierCoopCheck(edict_t *self) +{ + if (!self) + { + return; + } + + /* no more than 4 players in coop, so.. */ + edict_t *targets[4]; + int num_targets = 0, target, player; + edict_t *ent; + trace_t tr; + + /* if we're not in coop, this is a noop */ + if (!coop || !coop->value) + { + return; + } + + /* if we are, and we have recently fired, bail */ + if (self->wait > level.time) + { + return; + } + + memset(targets, 0, 4 * sizeof(edict_t *)); + + /* cycle through players */ + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + + if (!ent->inuse) + { + continue; + } + + if (!ent->client) + { + continue; + } + + if (inback(self, ent) || below(self, ent)) + { + tr = gi.trace(self->s.origin, NULL, NULL, ent->s.origin, + self, MASK_SOLID); + + if (tr.fraction == 1.0) + { + targets[num_targets++] = ent; + } + } + } + + if (!num_targets) + { + return; + } + + /* get a number from 0 to (num_targets-1) */ + target = random() * num_targets; + + /* just in case we got a 1.0 from random */ + if (target == num_targets) + { + target--; + } + + /* make sure to prevent rapid fire rockets */ + self->wait = level.time + CARRIER_ROCKET_TIME; + + /* save off the real enemy */ + ent = self->enemy; + /* set the new guy as temporary enemy */ + self->enemy = targets[target]; + CarrierRocket(self); + /* put the real enemy back */ + self->enemy = ent; + + /* we're done */ + return; +} + +void +CarrierGrenade(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + int flash_number; + float direction; /* from lower left to upper right, or lower right to upper left */ + float spreadR, spreadU; + int mytime; + + if (!self) + { + return; + } + + CarrierCoopCheck(self); + + if (!self->enemy) + { + return; + } + + if (random() < 0.5) + { + direction = -1.0; + } + else + { + direction = 1.0; + } + + mytime = (int)((level.time - self->timestamp) / 0.4); + + if (mytime == 0) + { + spreadR = 0.15 * direction; + spreadU = 0.1 - 0.1 * direction; + } + else if (mytime == 1) + { + spreadR = 0; + spreadU = 0.1; + } + else if (mytime == 2) + { + spreadR = -0.15 * direction; + spreadU = 0.1 - -0.1 * direction; + } + else if (mytime == 3) + { + spreadR = 0; + spreadU = 0.1; + } + else + { + /* error, shoot straight */ + spreadR = 0; + spreadU = 0; + } + + AngleVectors(self->s.angles, forward, right, up); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_GRENADE], + forward, right, start); + + VectorSubtract(self->enemy->s.origin, start, aim); + VectorNormalize(aim); + + VectorMA(aim, spreadR, right, aim); + VectorMA(aim, spreadU, up, aim); + + if (aim[2] > 0.15) + { + aim[2] = 0.15; + } + else if (aim[2] < -0.5) + { + aim[2] = -0.5; + } + + flash_number = MZ2_GUNNER_GRENADE_1; + monster_fire_grenade(self, start, aim, 50, 600, flash_number); +} + +void +CarrierPredictiveRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + + /* 1 */ + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_1], + forward, right, start); + PredictAim(self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.3, dir, NULL); + monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_1); + + /* 2 */ + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_2], + forward, right, start); + PredictAim(self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.15, dir, NULL); + monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_2); + + /* 3 */ + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_3], forward, + right, start); + PredictAim(self->enemy, start, CARRIER_ROCKET_SPEED, false, 0, dir, NULL); + monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_3); + + /* 4 */ + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_4], forward, + right, start); + PredictAim(self->enemy, start, CARRIER_ROCKET_SPEED, false, 0.15, dir, NULL); + monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_4); +} + +void +CarrierRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + + if (!self) + { + return; + } + + if (self->enemy) + { + if (self->enemy->client && (random() < 0.5)) + { + CarrierPredictiveRocket(self); + return; + } + } + else + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + + /* 1 */ + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_1], + forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + vec[2] -= 15; + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + VectorMA(dir, 0.4, right, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_1); + + /* 2 */ + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_2], + forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + VectorMA(dir, 0.025, right, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_2); + + /* 3 */ + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_3], + forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + VectorMA(dir, -0.025, right, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_3); + + /* 4 */ + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_4], + forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + vec[2] -= 15; + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + VectorMA(dir, -0.4, right, dir); + VectorNormalize(dir); + monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_4); +} + +void +carrier_firebullet_right(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + int flashnum; + + if (!self) + { + return; + } + + /* if we're in manual steering mode, it means we're leaning down .. use the lower shot */ + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + flashnum = MZ2_CARRIER_MACHINEGUN_R2; + } + else + { + flashnum = MZ2_CARRIER_MACHINEGUN_R1; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, right, start); + + VectorMA(self->enemy->s.origin, 0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract(target, start, forward); + VectorNormalize(forward); + + monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD * 3, + DEFAULT_BULLET_VSPREAD, flashnum); +} + +void +carrier_firebullet_left(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + int flashnum; + + if (!self) + { + return; + } + + /* if we're in manual steering mode, it means we're leaning down .. use the lower shot */ + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + flashnum = MZ2_CARRIER_MACHINEGUN_L2; + } + else + { + flashnum = MZ2_CARRIER_MACHINEGUN_L1; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], + forward, right, start); + + VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target); + + target[2] += self->enemy->viewheight; + VectorSubtract(target, start, forward); + VectorNormalize(forward); + + monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD * 3, + DEFAULT_BULLET_VSPREAD, flashnum); +} + +void +CarrierMachineGun(edict_t *self) +{ + if (!self) + { + return; + } + + CarrierCoopCheck(self); + + if (self->enemy) + { + carrier_firebullet_left(self); + } + + if (self->enemy) + { + carrier_firebullet_right(self); + } +} + +void +CarrierSpawn(edict_t *self) +{ + vec3_t f, r, offset, startpoint, spawnpoint; + edict_t *ent; + int mytime; + + if (!self) + { + return; + } + + VectorSet(offset, 105, 0, -58); + AngleVectors(self->s.angles, f, r, NULL); + + G_ProjectSource(self->s.origin, offset, f, r, startpoint); + + /* the +0.1 is because level.time is sometimes a little low */ + mytime = (int)((level.time + 0.1 - self->timestamp) / 0.5); + + if (FindSpawnPoint(startpoint, flyer_mins, flyer_maxs, spawnpoint, 32)) + { + /* the second flier should be a kamikaze flyer */ + if (mytime != 2) + { + ent = CreateFlyMonster(spawnpoint, self->s.angles, + flyer_mins, flyer_maxs, "monster_flyer"); + } + else + { + ent = CreateFlyMonster(spawnpoint, self->s.angles, + flyer_mins, flyer_maxs, "monster_kamikaze"); + } + + if (!ent) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_spawn, 1, ATTN_NONE, 0); + + self->monsterinfo.monster_slots--; + + ent->nextthink = level.time; + ent->think(ent); + + ent->monsterinfo.aiflags |= AI_SPAWNED_CARRIER | AI_DO_NOT_COUNT | + AI_IGNORE_SHOTS; + ent->monsterinfo.commander = self; + + if ((self->enemy->inuse) && (self->enemy->health > 0)) + { + ent->enemy = self->enemy; + FoundTarget(ent); + + if (mytime == 1) + { + ent->monsterinfo.lefty = 0; + ent->monsterinfo.attack_state = AS_SLIDING; + ent->monsterinfo.currentmove = &flyer_move_attack3; + } + else if (mytime == 2) + { + ent->monsterinfo.lefty = 0; + ent->monsterinfo.attack_state = AS_STRAIGHT; + ent->monsterinfo.currentmove = &flyer_move_kamikaze; + ent->mass = 100; + ent->monsterinfo.aiflags |= AI_CHARGING; + } + else if (mytime == 3) + { + ent->monsterinfo.lefty = 1; + ent->monsterinfo.attack_state = AS_SLIDING; + ent->monsterinfo.currentmove = &flyer_move_attack3; + } + } + } +} + +void +carrier_prep_spawn(edict_t *self) +{ + if (!self) + { + return; + } + + CarrierCoopCheck(self); + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->timestamp = level.time; + self->yaw_speed = 10; + CarrierMachineGun(self); +} + +void +carrier_spawn_check(edict_t *self) +{ + if (!self) + { + return; + } + + CarrierCoopCheck(self); + CarrierMachineGun(self); + CarrierSpawn(self); + + if (level.time > (self->timestamp + 1.1)) /* 0.5 seconds per flyer. this gets three */ + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->yaw_speed = orig_yaw_speed; + return; + } + else + { + self->monsterinfo.nextframe = FRAME_spawn08; + } +} + +void +carrier_ready_spawn(edict_t *self) +{ + float current_yaw; + vec3_t offset, f, r, startpoint, spawnpoint; + + if (!self) + { + return; + } + + CarrierCoopCheck(self); + CarrierMachineGun(self); + + current_yaw = anglemod(self->s.angles[YAW]); + + if (fabs(current_yaw - self->ideal_yaw) > 0.1) + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + self->timestamp += FRAMETIME; + return; + } + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + VectorSet(offset, 105, 0, -58); + AngleVectors(self->s.angles, f, r, NULL); + G_ProjectSource(self->s.origin, offset, f, r, startpoint); + + if (FindSpawnPoint(startpoint, flyer_mins, flyer_maxs, spawnpoint, 32)) + { + SpawnGrow_Spawn(spawnpoint, 0); + } +} + +void +carrier_start_spawn(edict_t *self) +{ + int mytime; + float enemy_yaw; + vec3_t temp; + + if (!self) + { + return; + } + + CarrierCoopCheck(self); + + if (!orig_yaw_speed) + { + orig_yaw_speed = self->yaw_speed; + } + + if (!self->enemy) + { + return; + } + + mytime = (int)((level.time - self->timestamp) / 0.5); + + VectorSubtract(self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw2(temp); + + /* note that the offsets are based on a forward of 105 from the end angle */ + if (mytime == 0) + { + self->ideal_yaw = anglemod(enemy_yaw - 30); + } + else if (mytime == 1) + { + self->ideal_yaw = anglemod(enemy_yaw); + } + else if (mytime == 2) + { + self->ideal_yaw = anglemod(enemy_yaw + 30); + } + + CarrierMachineGun(self); +} + +static mframe_t carrier_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} +}; + +mmove_t carrier_move_stand = { + FRAME_search01, + FRAME_search13, + carrier_frames_stand, + NULL +}; + +static mframe_t carrier_frames_walk[] = { + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL} +}; + +mmove_t carrier_move_walk = { + FRAME_search01, + FRAME_search13, + carrier_frames_walk, + NULL +}; + +static mframe_t carrier_frames_run[] = { + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck}, + {ai_run, 6, CarrierCoopCheck} +}; + +mmove_t carrier_move_run = { + FRAME_search01, + FRAME_search13, + carrier_frames_run, + NULL +}; + +static mframe_t carrier_frames_attack_pre_mg[] = { + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, carrier_attack_mg} +}; + +mmove_t carrier_move_attack_pre_mg = { + FRAME_firea01, + FRAME_firea08, + carrier_frames_attack_pre_mg, + NULL +}; + +/* Loop this */ +static mframe_t carrier_frames_attack_mg[] = { + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, carrier_reattack_mg} +}; + +mmove_t carrier_move_attack_mg = { + FRAME_firea09, + FRAME_firea11, + carrier_frames_attack_mg, + NULL +}; + +static mframe_t carrier_frames_attack_post_mg[] = { + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck} +}; + +mmove_t carrier_move_attack_post_mg = { + FRAME_firea12, + FRAME_firea15, + carrier_frames_attack_post_mg, + carrier_run +}; + +static mframe_t carrier_frames_attack_pre_gren[] = { + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, carrier_attack_gren} +}; + +mmove_t carrier_move_attack_pre_gren = { + FRAME_fireb01, + FRAME_fireb06, + carrier_frames_attack_pre_gren, + NULL +}; + +static mframe_t carrier_frames_attack_gren[] = { + {ai_charge, -15, CarrierGrenade}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, carrier_reattack_gren} +}; + +mmove_t carrier_move_attack_gren = { + FRAME_fireb07, + FRAME_fireb10, + carrier_frames_attack_gren, + NULL +}; + +static mframe_t carrier_frames_attack_post_gren[] = { + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck}, + {ai_charge, 4, CarrierCoopCheck} +}; + +mmove_t carrier_move_attack_post_gren = { + FRAME_fireb11, + FRAME_fireb16, + carrier_frames_attack_post_gren, + carrier_run +}; + +static mframe_t carrier_frames_attack_rocket[] = { + {ai_charge, 15, CarrierRocket} +}; + +mmove_t carrier_move_attack_rocket = { + FRAME_fireb01, + FRAME_fireb01, + carrier_frames_attack_rocket, + carrier_run +}; + +void +CarrierRail(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + if (!self) + { + return; + } + + CarrierCoopCheck(self); + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_RAILGUN], + forward, right, start); + + /* calc direction to where we targeted */ + VectorSubtract(self->pos1, start, dir); + VectorNormalize(dir); + + monster_fire_railgun(self, start, dir, 50, 100, MZ2_CARRIER_RAILGUN); + self->monsterinfo.attack_finished = level.time + RAIL_FIRE_TIME; +} + +void +CarrierSaveLoc(edict_t *self) +{ + if (!self) + { + return; + } + + CarrierCoopCheck(self); + VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */ + self->pos1[2] += self->enemy->viewheight; +} + +static mframe_t carrier_frames_attack_rail[] = { + {ai_charge, 2, CarrierCoopCheck}, + {ai_charge, 2, CarrierSaveLoc}, + {ai_charge, 2, CarrierCoopCheck}, + {ai_charge, -20, CarrierRail}, + {ai_charge, 2, CarrierCoopCheck}, + {ai_charge, 2, CarrierCoopCheck}, + {ai_charge, 2, CarrierCoopCheck}, + {ai_charge, 2, CarrierCoopCheck}, + {ai_charge, 2, CarrierCoopCheck} +}; + +mmove_t carrier_move_attack_rail = { + FRAME_search01, + FRAME_search09, + carrier_frames_attack_rail, + carrier_run +}; + +static mframe_t carrier_frames_spawn[] = { + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, carrier_prep_spawn}, /* 7 - end of wind down */ + {ai_charge, -2, carrier_start_spawn}, /* 8 - start of spawn */ + {ai_charge, -2, carrier_ready_spawn}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -10, carrier_spawn_check}, /* 12 - actual spawn */ + {ai_charge, -2, CarrierMachineGun}, /* 13 - begin of wind down */ + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, CarrierMachineGun}, + {ai_charge, -2, carrier_reattack_mg} /* 18 - end of wind down */ +}; + +mmove_t carrier_move_spawn = { + FRAME_spawn01, + FRAME_spawn18, + carrier_frames_spawn, + NULL +}; + +static mframe_t carrier_frames_pain_heavy[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t carrier_move_pain_heavy = { + FRAME_death01, + FRAME_death10, + carrier_frames_pain_heavy, + carrier_run +}; + +static mframe_t carrier_frames_pain_light[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t carrier_move_pain_light = { + FRAME_spawn01, + FRAME_spawn04, + carrier_frames_pain_light, + carrier_run +}; + +static mframe_t carrier_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, BossExplode} +}; + +mmove_t carrier_move_death = { + FRAME_death01, + FRAME_death16, + carrier_frames_death, + carrier_dead +}; + +void +carrier_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &carrier_move_stand; +} + +void +carrier_run(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &carrier_move_stand; + } + else + { + self->monsterinfo.currentmove = &carrier_move_run; + } +} + +void +carrier_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &carrier_move_walk; +} + +void +CarrierMachineGunHold(edict_t *self) +{ + if (!self) + { + return; + } + + CarrierMachineGun(self); +} + +void +carrier_attack(edict_t *self) +{ + vec3_t vec; + float range, luck; + qboolean enemy_inback, enemy_infront, enemy_below; + + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if ((!self->enemy) || (!self->enemy->inuse)) + { + return; + } + + enemy_inback = inback(self, self->enemy); + enemy_infront = infront(self, self->enemy); + enemy_below = below(self, self->enemy); + + if (self->bad_area) + { + if ((enemy_inback) || (enemy_below)) + { + self->monsterinfo.currentmove = &carrier_move_attack_rocket; + } + else if ((random() < 0.1) || + (level.time < self->monsterinfo.attack_finished)) + { + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + } + else + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + + return; + } + + if (self->monsterinfo.attack_state == AS_BLIND) + { + self->monsterinfo.currentmove = &carrier_move_spawn; + return; + } + + if (!enemy_inback && !enemy_infront && !enemy_below) /* to side and not under */ + { + if ((random() < 0.1) || + (level.time < self->monsterinfo.attack_finished)) + { + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + } + else + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + + return; + } + + if (enemy_infront) + { + VectorSubtract(self->enemy->s.origin, self->s.origin, vec); + range = VectorLength(vec); + + if (range <= 125) + { + if ((random() < 0.8) || + (level.time < self->monsterinfo.attack_finished)) + { + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + } + else + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + } + else if (range < 600) + { + luck = random(); + + if (self->monsterinfo.monster_slots > 2) + { + if (luck <= 0.20) + { + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + } + else if (luck <= 0.40) + { + self->monsterinfo.currentmove = + &carrier_move_attack_pre_gren; + } + else if ((luck <= 0.7) && + !(level.time < self->monsterinfo.attack_finished)) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + else + { + self->monsterinfo.currentmove = &carrier_move_spawn; + } + } + else + { + if (luck <= 0.30) + { + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + } + else if (luck <= 0.65) + { + self->monsterinfo.currentmove = + &carrier_move_attack_pre_gren; + } + else if (level.time >= self->monsterinfo.attack_finished) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + else + { + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + } + } + } + else /* won't use grenades at this range */ + { + luck = random(); + + if (self->monsterinfo.monster_slots > 2) + { + if (luck < 0.3) + { + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + } + else if ((luck < 0.65) && !(level.time < self->monsterinfo.attack_finished)) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */ + self->pos1[2] += self->enemy->viewheight; + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + else + { + self->monsterinfo.currentmove = &carrier_move_spawn; + } + } + else + { + if ((luck < 0.45) || + (level.time < self->monsterinfo.attack_finished)) + { + self->monsterinfo.currentmove = &carrier_move_attack_pre_mg; + } + else + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &carrier_move_attack_rail; + } + } + } + } + else if ((enemy_below) || (enemy_inback)) + { + self->monsterinfo.currentmove = &carrier_move_attack_rocket; + } +} + +void +carrier_attack_mg(edict_t *self) +{ + if (!self) + { + return; + } + + CarrierCoopCheck(self); + self->monsterinfo.currentmove = &carrier_move_attack_mg; +} + +void +carrier_reattack_mg(edict_t *self) +{ + if (!self) + { + return; + } + + CarrierCoopCheck(self); + + if (infront(self, self->enemy)) + { + if (random() <= 0.5) + { + if ((random() < 0.7) || (self->monsterinfo.monster_slots <= 2)) + { + self->monsterinfo.currentmove = &carrier_move_attack_mg; + } + else + { + self->monsterinfo.currentmove = &carrier_move_spawn; + } + } + else + { + self->monsterinfo.currentmove = &carrier_move_attack_post_mg; + } + } + else + { + self->monsterinfo.currentmove = &carrier_move_attack_post_mg; + } +} + +void +carrier_attack_gren(edict_t *self) +{ + if (!self) + { + return; + } + + CarrierCoopCheck(self); + self->timestamp = level.time; + self->monsterinfo.currentmove = &carrier_move_attack_gren; +} + +void +carrier_reattack_gren(edict_t *self) +{ + if (!self) + { + return; + } + + CarrierCoopCheck(self); + + if (infront(self, self->enemy)) + { + if (self->timestamp + 1.3 > level.time) /* four grenades */ + { + self->monsterinfo.currentmove = &carrier_move_attack_gren; + return; + } + } + + self->monsterinfo.currentmove = &carrier_move_attack_post_gren; +} + +void +carrier_pain(edict_t *self, edict_t *other /* unused */, float kick /* unused */, int damage) +{ + qboolean changed = false; + + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 5; + + if (damage < 10) + { + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + } + else if (damage < 30) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + + if (random() < 0.5) + { + changed = true; + self->monsterinfo.currentmove = &carrier_move_pain_light; + } + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + self->monsterinfo.currentmove = &carrier_move_pain_heavy; + changed = true; + } + + if (changed) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->yaw_speed = orig_yaw_speed; + } +} + +void +carrier_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -56, -56, 0); + VectorSet(self->maxs, 56, 56, 80); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +void +carrier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage /* unused */, vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &carrier_move_death; +} + +qboolean +Carrier_CheckAttack(edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance = 0; + trace_t tr; + qboolean enemy_infront, enemy_inback, enemy_below; + int enemy_range; + float enemy_yaw; + + if (!self) + { + return false; + } + + if (self->enemy->health > 0) + { + /* see if any entities are in the way of the shot */ + VectorCopy(self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy(self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace(spot1, NULL, NULL, spot2, self, + CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | + CONTENTS_LAVA); + + /* do we have a clear shot? */ + if (tr.ent != self->enemy) + { + /* go ahead and spawn stuff if we're mad a a client */ + if (self->enemy->client && (self->monsterinfo.monster_slots > 2)) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + /* we want them to go ahead and shoot at info_notnulls if they can. */ + if ((self->enemy->solid != SOLID_NOT) || (tr.fraction < 1.0)) + { + return false; + } + } + } + + enemy_infront = infront(self, self->enemy); + enemy_inback = inback(self, self->enemy); + enemy_below = below(self, self->enemy); + + enemy_range = range(self, self->enemy); + VectorSubtract(self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw2(temp); + + self->ideal_yaw = enemy_yaw; + + /* shoot out the back if appropriate */ + if ((enemy_inback) || (!enemy_infront && enemy_below)) + { + /* this is using wait because the attack is supposed to be independent */ + if (level.time >= self->wait) + { + self->wait = level.time + CARRIER_ROCKET_TIME; + self->monsterinfo.attack(self); + + if (random() < 0.6) + { + self->monsterinfo.attack_state = AS_SLIDING; + } + else + { + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return true; + } + } + + /* melee attack */ + if (enemy_range == RANGE_MELEE) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.8; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.8; + } + else if (enemy_range == RANGE_FAR) + { + chance = 0.5; + } + + /* go ahead and shoot every time if it's a info_notnull */ + if ((random() < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.6) + { + self->monsterinfo.attack_state = AS_SLIDING; + } + else + { + self->monsterinfo.attack_state = AS_STRAIGHT; + } + } + + return false; +} + +static void +CarrierPrecache() +{ + gi.soundindex("flyer/flysght1.wav"); + gi.soundindex("flyer/flysrch1.wav"); + gi.soundindex("flyer/flypain1.wav"); + gi.soundindex("flyer/flypain2.wav"); + gi.soundindex("flyer/flyatck2.wav"); + gi.soundindex("flyer/flyatck1.wav"); + gi.soundindex("flyer/flydeth1.wav"); + gi.soundindex("flyer/flyatck3.wav"); + gi.soundindex("flyer/flyidle1.wav"); + gi.soundindex("weapons/rockfly.wav"); + gi.soundindex("infantry/infatck1.wav"); + gi.soundindex("gunner/gunatck3.wav"); + gi.soundindex("weapons/grenlb1b.wav"); + gi.soundindex("tank/rocket.wav"); + + gi.modelindex("models/monsters/flyer/tris.md2"); + gi.modelindex("models/objects/rocket/tris.md2"); + gi.modelindex("models/objects/debris2/tris.md2"); + gi.modelindex("models/objects/grenade/tris.md2"); + gi.modelindex("models/items/spawngro/tris.md2"); + gi.modelindex("models/items/spawngro2/tris.md2"); + gi.modelindex("models/objects/gibs/sm_metal/tris.md2"); + gi.modelindex("models/objects/gibs/gear/tris.md2"); +} + +/* + * QUAKED monster_carrier (1 .5 0) (-56 -56 -44) (56 56 44) Ambush Trigger_Spawn Sight + */ +void +SP_monster_carrier(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("carrier/pain_md.wav"); + sound_pain2 = gi.soundindex("carrier/pain_lg.wav"); + sound_pain3 = gi.soundindex("carrier/pain_sm.wav"); + sound_death = gi.soundindex("carrier/death.wav"); + sound_rail = gi.soundindex("gladiator/railgun.wav"); + sound_sight = gi.soundindex("carrier/sight.wav"); + sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav"); + + self->s.sound = gi.soundindex("bosshovr/bhvengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/carrier/tris.md2"); + VectorSet(self->mins, -56, -56, -44); + VectorSet(self->maxs, 56, 56, 44); + + /* 2000 - 4000 health */ + self->health = Q_max(2000, 2000 + 1000 * ((skill->value) - 1)); + + /* add health in coop (500 * skill) */ + if (coop->value) + { + self->health += 500 * (skill->value); + } + + self->gib_health = -200; + self->mass = 1000; + + self->yaw_speed = 15; + orig_yaw_speed = self->yaw_speed; + + self->flags |= FL_IMMUNE_LASER; + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + self->pain = carrier_pain; + self->die = carrier_die; + + self->monsterinfo.melee = NULL; + self->monsterinfo.stand = carrier_stand; + self->monsterinfo.walk = carrier_walk; + self->monsterinfo.run = carrier_run; + self->monsterinfo.attack = carrier_attack; + self->monsterinfo.sight = carrier_sight; + self->monsterinfo.checkattack = Carrier_CheckAttack; + gi.linkentity(self); + + self->monsterinfo.currentmove = &carrier_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + CarrierPrecache(); + + flymonster_start(self); + + self->monsterinfo.attack_finished = 0; + + switch ((int)skill->value) + { + case 0: + self->monsterinfo.monster_slots = 3; + break; + case 1: + case 2: + self->monsterinfo.monster_slots = 6; + break; + case 3: + self->monsterinfo.monster_slots = 9; + break; + default: + self->monsterinfo.monster_slots = 6; + break; + } +} diff --git a/src/game/monster/carrier/carrier.h b/src/game/monster/carrier/carrier.h new file mode 100644 index 000000000..1f3f819e4 --- /dev/null +++ b/src/game/monster/carrier/carrier.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Carrier animations. + * + * ======================================================================= + * + */ + +#define FRAME_search01 0 +#define FRAME_search02 1 +#define FRAME_search03 2 +#define FRAME_search04 3 +#define FRAME_search05 4 +#define FRAME_search06 5 +#define FRAME_search07 6 +#define FRAME_search08 7 +#define FRAME_search09 8 +#define FRAME_search10 9 +#define FRAME_search11 10 +#define FRAME_search12 11 +#define FRAME_search13 12 +#define FRAME_firea01 13 +#define FRAME_firea02 14 +#define FRAME_firea03 15 +#define FRAME_firea04 16 +#define FRAME_firea05 17 +#define FRAME_firea06 18 +#define FRAME_firea07 19 +#define FRAME_firea08 20 +#define FRAME_firea09 21 +#define FRAME_firea10 22 +#define FRAME_firea11 23 +#define FRAME_firea12 24 +#define FRAME_firea13 25 +#define FRAME_firea14 26 +#define FRAME_firea15 27 +#define FRAME_fireb01 28 +#define FRAME_fireb02 29 +#define FRAME_fireb03 30 +#define FRAME_fireb04 31 +#define FRAME_fireb05 32 +#define FRAME_fireb06 33 +#define FRAME_fireb07 34 +#define FRAME_fireb08 35 +#define FRAME_fireb09 36 +#define FRAME_fireb10 37 +#define FRAME_fireb11 38 +#define FRAME_fireb12 39 +#define FRAME_fireb13 40 +#define FRAME_fireb14 41 +#define FRAME_fireb15 42 +#define FRAME_fireb16 43 +#define FRAME_spawn01 44 +#define FRAME_spawn02 45 +#define FRAME_spawn03 46 +#define FRAME_spawn04 47 +#define FRAME_spawn05 48 +#define FRAME_spawn06 49 +#define FRAME_spawn07 50 +#define FRAME_spawn08 51 +#define FRAME_spawn09 52 +#define FRAME_spawn10 53 +#define FRAME_spawn11 54 +#define FRAME_spawn12 55 +#define FRAME_spawn13 56 +#define FRAME_spawn14 57 +#define FRAME_spawn15 58 +#define FRAME_spawn16 59 +#define FRAME_spawn17 60 +#define FRAME_spawn18 61 +#define FRAME_death01 62 +#define FRAME_death02 63 +#define FRAME_death03 64 +#define FRAME_death04 65 +#define FRAME_death05 66 +#define FRAME_death06 67 +#define FRAME_death07 68 +#define FRAME_death08 69 +#define FRAME_death09 70 +#define FRAME_death10 71 +#define FRAME_death11 72 +#define FRAME_death12 73 +#define FRAME_death13 74 +#define FRAME_death14 75 +#define FRAME_death15 76 +#define FRAME_death16 77 +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/chick/chick.c b/src/game/monster/chick/chick.c new file mode 100644 index 000000000..a79e217ba --- /dev/null +++ b/src/game/monster/chick/chick.c @@ -0,0 +1,1259 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Iron Maiden. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "chick.h" + +#define LEAD_TARGET 1 + +qboolean visible(edict_t *self, edict_t *other); + +void chick_stand(edict_t *self); +void chick_run(edict_t *self); +void chick_reslash(edict_t *self); +void chick_rerocket(edict_t *self); +void chick_attack1(edict_t *self); + +static int sound_missile_prelaunch; +static int sound_missile_launch; +static int sound_melee_swing; +static int sound_melee_hit; +static int sound_missile_reload; +static int sound_death1; +static int sound_death2; +static int sound_fall_down; +static int sound_idle1; +static int sound_idle2; +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_sight; +static int sound_search; + +static int sound_step; +static int sound_step2; + + +void +chick_footstep(edict_t *self) +{ + if (!g_monsterfootsteps->value) + return; + + // Lazy loading for savegame compatibility. + if (sound_step == 0 || sound_step2 == 0) + { + sound_step = gi.soundindex("bitch/step1.wav"); + sound_step2 = gi.soundindex("bitch/step2.wav"); + } + + if (randk() % 2 == 0) + { + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + } +} + + +void +ChickMoan(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0); + } +} + +static mframe_t chick_frames_fidget[] = { + {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, ChickMoan}, + {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 chick_move_fidget = +{ + FRAME_stand201, + FRAME_stand230, + chick_frames_fidget, + chick_stand +}; + +void +chick_fidget(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + return; + } + + if (random() <= 0.3) + { + self->monsterinfo.currentmove = &chick_move_fidget; + } +} + +static mframe_t chick_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, chick_fidget}, +}; + +mmove_t chick_move_stand = +{ + FRAME_stand101, + FRAME_stand130, + chick_frames_stand, + NULL +}; + +void +chick_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &chick_move_stand; +} + +static mframe_t chick_frames_start_run[] = { + {ai_run, 1, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, -1, NULL}, + {ai_run, -1, NULL}, + {ai_run, 0, NULL}, + {ai_run, 1, NULL}, + {ai_run, 3, NULL}, + {ai_run, 6, NULL}, + {ai_run, 3, NULL} +}; + +mmove_t chick_move_start_run = +{ + FRAME_walk01, + FRAME_walk10, + chick_frames_start_run, + chick_run +}; + +static mframe_t chick_frames_run[] = { + {ai_run, 6, NULL}, + {ai_run, 8, chick_footstep}, + {ai_run, 13, NULL}, + {ai_run, 5, NULL}, + {ai_run, 7, NULL}, + {ai_run, 4, NULL}, + {ai_run, 11, chick_footstep}, + {ai_run, 5, NULL}, + {ai_run, 9, NULL}, + {ai_run, 7, NULL} +}; + +mmove_t chick_move_run = +{ + FRAME_walk11, + FRAME_walk20, + chick_frames_run, + NULL +}; + +static mframe_t chick_frames_walk[] = { + {ai_walk, 6, NULL}, + {ai_walk, 8, chick_footstep}, + {ai_walk, 13, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 11, chick_footstep}, + {ai_walk, 5, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 7, NULL} +}; + +mmove_t chick_move_walk = +{ + FRAME_walk11, + FRAME_walk20, + chick_frames_walk, + NULL +}; + +void +chick_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &chick_move_walk; +} + +void +chick_run(edict_t *self) +{ + if (!self) + { + return; + } + + monster_done_dodge(self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &chick_move_stand; + return; + } + + if ((self->monsterinfo.currentmove == &chick_move_walk) || + (self->monsterinfo.currentmove == &chick_move_start_run)) + { + self->monsterinfo.currentmove = &chick_move_run; + } + else + { + self->monsterinfo.currentmove = &chick_move_start_run; + } +} + +static mframe_t chick_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t chick_move_pain1 = +{ + FRAME_pain101, + FRAME_pain105, + chick_frames_pain1, + chick_run +}; + +static mframe_t chick_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t chick_move_pain2 = +{ + FRAME_pain201, + FRAME_pain205, + chick_frames_pain2, + chick_run +}; + +static mframe_t chick_frames_pain3[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -6, NULL}, + {ai_move, 3, NULL}, + {ai_move, 11, NULL}, + {ai_move, 3, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 4, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, -3, NULL}, + {ai_move, -4, NULL}, + {ai_move, 5, NULL}, + {ai_move, 7, NULL}, + {ai_move, -2, NULL}, + {ai_move, 3, NULL}, + {ai_move, -5, NULL}, + {ai_move, -2, NULL}, + {ai_move, -8, NULL}, + {ai_move, 2, NULL} +}; + +mmove_t chick_move_pain3 = +{ + FRAME_pain301, + FRAME_pain321, + chick_frames_pain3, + chick_run +}; + +void +chick_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + float r; + + if (!self) + { + return; + } + + monster_done_dodge(self); + + 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; + + r = random(); + + if (r < 0.33) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else if (r < 0.66) + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + /* clear this from blindfire */ + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if (damage <= 10) + { + self->monsterinfo.currentmove = &chick_move_pain1; + } + else if (damage <= 25) + { + self->monsterinfo.currentmove = &chick_move_pain2; + } + else + { + self->monsterinfo.currentmove = &chick_move_pain3; + } + + /* clear duck flag */ + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } +} + +void +chick_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -16, -16, 0); + VectorSet(self->maxs, 16, 16, 16); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +static mframe_t chick_frames_death2[] = { + {ai_move, -6, NULL}, + {ai_move, 0, NULL}, + {ai_move, -1, NULL}, + {ai_move, -5, chick_footstep}, + {ai_move, 0, NULL}, + {ai_move, -1, NULL}, + {ai_move, -2, NULL}, + {ai_move, 1, NULL}, + {ai_move, 10, NULL}, + {ai_move, 2, NULL}, + {ai_move, 3, chick_footstep}, + {ai_move, 1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 3, NULL}, + {ai_move, 3, NULL}, + {ai_move, 1, chick_footstep}, + {ai_move, -3, NULL}, + {ai_move, -5, NULL}, + {ai_move, 4, NULL}, + {ai_move, 15, NULL}, + {ai_move, 14, NULL}, + {ai_move, 1, NULL} +}; + +mmove_t chick_move_death2 = +{ + FRAME_death201, + FRAME_death223, + chick_frames_death2, + chick_dead +}; + +static mframe_t chick_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -7, NULL}, + {ai_move, 4, NULL}, + {ai_move, 11, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t chick_move_death1 = +{ + FRAME_death101, + FRAME_death112, + chick_frames_death1, + chick_dead +}; + +void +chick_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /*unused */) +{ + int n; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), + 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 */ + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (randk() % 2 == 0) + { + self->monsterinfo.currentmove = &chick_move_death1; + gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &chick_move_death2; + gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + } +} + +void +chick_duck_down(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + return; + } + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity(self); +} + +void +chick_duck_hold(edict_t *self) +{ + if (!self) + { + return; + } + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +void +chick_duck_up(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + +static mframe_t chick_frames_duck[] = { + {ai_move, 0, chick_duck_down}, + {ai_move, 1, NULL}, + {ai_move, 4, chick_duck_hold}, + {ai_move, -4, NULL}, + {ai_move, -5, chick_duck_up}, + {ai_move, 3, NULL}, + {ai_move, 1, NULL} +}; + +mmove_t chick_move_duck = +{ + FRAME_duck01, + FRAME_duck07, + chick_frames_duck, + chick_run +}; + +void +chick_dodge(edict_t *self, edict_t *attacker, float eta /* unused */, + trace_t *tr /* unused */) +{ + if (!self || !attacker) + { + return; + } + + if (random() > 0.25) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + } + + self->monsterinfo.currentmove = &chick_move_duck; +} + +void +ChickSlash(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->mins[0], 10); + gi.sound(self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0); + fire_hit(self, aim, (10 + (randk() % 6)), 100); +} + +void +ChickRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + trace_t trace; /* check target */ + int rocketSpeed; + float dist; + vec3_t target; + qboolean blindfire = false; + + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + blindfire = true; + } + else + { + blindfire = false; + } + + if (!self->enemy || !self->enemy->inuse) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1], + forward, right, start); + + rocketSpeed = 500 + (100 * skill->value); /* rock & roll.... :) */ + + if (blindfire) + { + VectorCopy(self->monsterinfo.blind_fire_target, target); + } + else + { + VectorCopy(self->enemy->s.origin, target); + } + + /* blindfire shooting */ + if (blindfire) + { + VectorCopy(target, vec); + VectorSubtract(vec, start, dir); + } + /* don't shoot at feet if they're above where i'm shooting from. */ + else if ((random() < 0.33) || (start[2] < self->enemy->absmin[2])) + { + VectorCopy(target, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, dir); + } + else + { + VectorCopy(target, vec); + vec[2] = self->enemy->absmin[2]; + VectorSubtract(vec, start, dir); + } + + /* lead target (not when blindfiring) */ + if ((!blindfire) && ((random() < (0.2 + ((3 - skill->value) * 0.15))))) + { + float time; + + dist = VectorLength(dir); + time = dist / rocketSpeed; + VectorMA(vec, time, self->enemy->velocity, vec); + VectorSubtract(vec, start, dir); + } + + VectorNormalize(dir); + + if (blindfire) + { + /* blindfire has different fail criteria for the trace */ + if (!blind_rocket_ok(self, start, right, target, 10.0f, dir)) + { + return; + } + } + else + { + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + + if (((trace.ent != self->enemy) && (trace.ent != world)) || + ((trace.fraction <= 0.5f) && !trace.ent->client)) + { + return; + } + } + + if (!strcmp(self->classname, "monster_chick_heat")) + { + monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + } + else + { + monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + } +} + +void +Chick_PreAttack1(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0); +} + +void +ChickReload(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0); +} + +static mframe_t chick_frames_start_attack1[] = { + {ai_charge, 0, Chick_PreAttack1}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, -3, NULL}, + {ai_charge, 3, NULL}, + {ai_charge, 5, NULL}, + {ai_charge, 7, chick_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, chick_attack1} +}; + +mmove_t chick_move_start_attack1 = +{ + FRAME_attak101, + FRAME_attak113, + chick_frames_start_attack1, + NULL +}; + +static mframe_t chick_frames_attack1[] = { + {ai_charge, 19, ChickRocket}, + {ai_charge, -6, NULL}, + {ai_charge, -5, chick_footstep}, + {ai_charge, -2, NULL}, + {ai_charge, -7, chick_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, 10, ChickReload}, + {ai_charge, 4, NULL}, + {ai_charge, 5, chick_footstep}, + {ai_charge, 6, NULL}, + {ai_charge, 6, NULL}, + {ai_charge, 4, chick_footstep}, + {ai_charge, 3, chick_rerocket} +}; + +mmove_t chick_move_attack1 = +{ + FRAME_attak114, + FRAME_attak127, + chick_frames_attack1, + NULL +}; + +static mframe_t chick_frames_end_attack1[] = { + {ai_charge, -3, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, -6, NULL}, + {ai_charge, -4, NULL}, + {ai_charge, -2, chick_footstep} +}; + +mmove_t chick_move_end_attack1 = +{ + FRAME_attak128, + FRAME_attak132, + chick_frames_end_attack1, + chick_run +}; + +void +chick_rerocket(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &chick_move_end_attack1; + return; + } + + if (self->enemy->health > 0) + { + if (range(self, self->enemy) > RANGE_MELEE) + { + if (visible(self, self->enemy)) + { + if (random() <= (0.6 + (0.05 * ((float)skill->value)))) + { + self->monsterinfo.currentmove = &chick_move_attack1; + return; + } + } + } + } + + self->monsterinfo.currentmove = &chick_move_end_attack1; +} + +void +chick_attack1(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &chick_move_attack1; +} + +static mframe_t chick_frames_slash[] = { + {ai_charge, 1, NULL}, + {ai_charge, 7, ChickSlash}, + {ai_charge, -7, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, -1, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, -2, chick_reslash} +}; + +mmove_t chick_move_slash = +{ + FRAME_attak204, + FRAME_attak212, + chick_frames_slash, + NULL +}; + +static mframe_t chick_frames_end_slash[] = { + {ai_charge, -6, NULL}, + {ai_charge, -1, NULL}, + {ai_charge, -6, NULL}, + {ai_charge, 0, chick_footstep} +}; + +mmove_t chick_move_end_slash = +{ + FRAME_attak213, + FRAME_attak216, + chick_frames_end_slash, + chick_run +}; + +void +chick_reslash(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->enemy->health > 0) + { + if (range(self, self->enemy) == RANGE_MELEE) + { + if (random() <= 0.9) + { + self->monsterinfo.currentmove = &chick_move_slash; + return; + } + else + { + self->monsterinfo.currentmove = &chick_move_end_slash; + return; + } + } + } + + self->monsterinfo.currentmove = &chick_move_end_slash; +} + +void +chick_slash(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &chick_move_slash; +} + +static mframe_t chick_frames_start_slash[] = { + {ai_charge, 1, NULL}, + {ai_charge, 8, NULL}, + {ai_charge, 3, chick_footstep} +}; + +mmove_t chick_move_start_slash = +{ + FRAME_attak201, + FRAME_attak203, + chick_frames_start_slash, + chick_slash +}; + +void +chick_melee(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &chick_move_start_slash; +} + +void +chick_attack(edict_t *self) +{ + float r, chance; + + if (!self) + { + return; + } + + monster_done_dodge(self); + + if (self->monsterinfo.attack_state == AS_BLIND) + { + /* setup shot probabilities */ + if (self->monsterinfo.blind_fire_delay < 1.0) + { + chance = 1.0; + } + else if (self->monsterinfo.blind_fire_delay < 7.5) + { + chance = 0.4; + } + else + { + chance = 0.1; + } + + r = random(); + + /* minimum of 2 seconds, plus 0-3, after the shots are done */ + self->monsterinfo.blind_fire_delay += 4.0 + 1.5 + random(); + + /* don't shoot at the origin */ + if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin)) + { + return; + } + + /* don't shoot if the dice say not to */ + if (r > chance) + { + return; + } + + /* turn on manual steering to signal both manual steering and blindfire */ + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &chick_move_start_attack1; + self->monsterinfo.attack_finished = level.time + 2 * random(); + return; + } + + self->monsterinfo.currentmove = &chick_move_start_attack1; +} + +void +chick_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +qboolean +chick_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + return false; +} + +void +chick_duck(edict_t *self, float eta) +{ + if (!self) + { + return; + } + + if ((self->monsterinfo.currentmove == &chick_move_start_attack1) || + (self->monsterinfo.currentmove == &chick_move_attack1)) + { + /* if we're shooting, and not on easy, don't dodge */ + if (skill->value) + { + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + } + + if (skill->value == SKILL_EASY) + { + /* stupid dodge */ + self->monsterinfo.duck_wait_time = level.time + eta + 1; + } + else + { + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + } + + /* has to be done immediately otherwise she can get stuck */ + monster_duck_down(self); + + self->monsterinfo.nextframe = FRAME_duck01; + self->monsterinfo.currentmove = &chick_move_duck; + return; +} + +void +chick_sidestep(edict_t *self) +{ + if (!self) + { + return; + } + + if ((self->monsterinfo.currentmove == &chick_move_start_attack1) || + (self->monsterinfo.currentmove == &chick_move_attack1)) + { + /* if we're shooting, and not on easy, don't dodge */ + if (skill->value > SKILL_EASY) + { + self->monsterinfo.aiflags &= ~AI_DODGING; + return; + } + } + + if (self->monsterinfo.currentmove != &chick_move_run) + { + self->monsterinfo.currentmove = &chick_move_run; + } +} + +/* + * QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_chick(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + + sound_missile_prelaunch = gi.soundindex("chick/chkatck1.wav"); + sound_missile_launch = gi.soundindex("chick/chkatck2.wav"); + sound_melee_swing = gi.soundindex("chick/chkatck3.wav"); + sound_melee_hit = gi.soundindex("chick/chkatck4.wav"); + sound_missile_reload = gi.soundindex("chick/chkatck5.wav"); + sound_death1 = gi.soundindex("chick/chkdeth1.wav"); + sound_death2 = gi.soundindex("chick/chkdeth2.wav"); + sound_fall_down = gi.soundindex("chick/chkfall1.wav"); + sound_idle1 = gi.soundindex("chick/chkidle1.wav"); + sound_idle2 = gi.soundindex("chick/chkidle2.wav"); + sound_pain1 = gi.soundindex("chick/chkpain1.wav"); + sound_pain2 = gi.soundindex("chick/chkpain2.wav"); + sound_pain3 = gi.soundindex("chick/chkpain3.wav"); + sound_sight = gi.soundindex("chick/chksght1.wav"); + sound_search = gi.soundindex("chick/chksrch1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/bitch2/tris.md2"); + VectorSet(self->mins, -16, -16, 0); + VectorSet(self->maxs, 16, 16, 56); + + self->health = 175; + self->gib_health = -70; + self->mass = 200; + + self->pain = chick_pain; + self->die = chick_die; + + self->monsterinfo.stand = chick_stand; + self->monsterinfo.walk = chick_walk; + self->monsterinfo.run = chick_run; + self->monsterinfo.dodge = chick_dodge; + self->monsterinfo.duck = chick_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = chick_sidestep; + self->monsterinfo.attack = chick_attack; + self->monsterinfo.melee = chick_melee; + self->monsterinfo.sight = chick_sight; + self->monsterinfo.blocked = chick_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &chick_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.blindfire = true; + walkmonster_start(self); +} + +/* + * QUAKED monster_chick_heat (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_chick_heat(edict_t *self) +{ + if (!self) + { + return; + } + + SP_monster_chick(self); + + /* have to check this since the regular spawn function can free the entity */ + if (self->inuse) + { + self->s.skinnum = 3; + } +} diff --git a/src/game/monster/chick/chick.h b/src/game/monster/chick/chick.h new file mode 100644 index 000000000..043e4102f --- /dev/null +++ b/src/game/monster/chick/chick.h @@ -0,0 +1,317 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Iron Maiden animations. + * + * ======================================================================= + */ + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak113 12 +#define FRAME_attak114 13 +#define FRAME_attak115 14 +#define FRAME_attak116 15 +#define FRAME_attak117 16 +#define FRAME_attak118 17 +#define FRAME_attak119 18 +#define FRAME_attak120 19 +#define FRAME_attak121 20 +#define FRAME_attak122 21 +#define FRAME_attak123 22 +#define FRAME_attak124 23 +#define FRAME_attak125 24 +#define FRAME_attak126 25 +#define FRAME_attak127 26 +#define FRAME_attak128 27 +#define FRAME_attak129 28 +#define FRAME_attak130 29 +#define FRAME_attak131 30 +#define FRAME_attak132 31 +#define FRAME_attak201 32 +#define FRAME_attak202 33 +#define FRAME_attak203 34 +#define FRAME_attak204 35 +#define FRAME_attak205 36 +#define FRAME_attak206 37 +#define FRAME_attak207 38 +#define FRAME_attak208 39 +#define FRAME_attak209 40 +#define FRAME_attak210 41 +#define FRAME_attak211 42 +#define FRAME_attak212 43 +#define FRAME_attak213 44 +#define FRAME_attak214 45 +#define FRAME_attak215 46 +#define FRAME_attak216 47 +#define FRAME_death101 48 +#define FRAME_death102 49 +#define FRAME_death103 50 +#define FRAME_death104 51 +#define FRAME_death105 52 +#define FRAME_death106 53 +#define FRAME_death107 54 +#define FRAME_death108 55 +#define FRAME_death109 56 +#define FRAME_death110 57 +#define FRAME_death111 58 +#define FRAME_death112 59 +#define FRAME_death201 60 +#define FRAME_death202 61 +#define FRAME_death203 62 +#define FRAME_death204 63 +#define FRAME_death205 64 +#define FRAME_death206 65 +#define FRAME_death207 66 +#define FRAME_death208 67 +#define FRAME_death209 68 +#define FRAME_death210 69 +#define FRAME_death211 70 +#define FRAME_death212 71 +#define FRAME_death213 72 +#define FRAME_death214 73 +#define FRAME_death215 74 +#define FRAME_death216 75 +#define FRAME_death217 76 +#define FRAME_death218 77 +#define FRAME_death219 78 +#define FRAME_death220 79 +#define FRAME_death221 80 +#define FRAME_death222 81 +#define FRAME_death223 82 +#define FRAME_duck01 83 +#define FRAME_duck02 84 +#define FRAME_duck03 85 +#define FRAME_duck04 86 +#define FRAME_duck05 87 +#define FRAME_duck06 88 +#define FRAME_duck07 89 +#define FRAME_pain101 90 +#define FRAME_pain102 91 +#define FRAME_pain103 92 +#define FRAME_pain104 93 +#define FRAME_pain105 94 +#define FRAME_pain201 95 +#define FRAME_pain202 96 +#define FRAME_pain203 97 +#define FRAME_pain204 98 +#define FRAME_pain205 99 +#define FRAME_pain301 100 +#define FRAME_pain302 101 +#define FRAME_pain303 102 +#define FRAME_pain304 103 +#define FRAME_pain305 104 +#define FRAME_pain306 105 +#define FRAME_pain307 106 +#define FRAME_pain308 107 +#define FRAME_pain309 108 +#define FRAME_pain310 109 +#define FRAME_pain311 110 +#define FRAME_pain312 111 +#define FRAME_pain313 112 +#define FRAME_pain314 113 +#define FRAME_pain315 114 +#define FRAME_pain316 115 +#define FRAME_pain317 116 +#define FRAME_pain318 117 +#define FRAME_pain319 118 +#define FRAME_pain320 119 +#define FRAME_pain321 120 +#define FRAME_stand101 121 +#define FRAME_stand102 122 +#define FRAME_stand103 123 +#define FRAME_stand104 124 +#define FRAME_stand105 125 +#define FRAME_stand106 126 +#define FRAME_stand107 127 +#define FRAME_stand108 128 +#define FRAME_stand109 129 +#define FRAME_stand110 130 +#define FRAME_stand111 131 +#define FRAME_stand112 132 +#define FRAME_stand113 133 +#define FRAME_stand114 134 +#define FRAME_stand115 135 +#define FRAME_stand116 136 +#define FRAME_stand117 137 +#define FRAME_stand118 138 +#define FRAME_stand119 139 +#define FRAME_stand120 140 +#define FRAME_stand121 141 +#define FRAME_stand122 142 +#define FRAME_stand123 143 +#define FRAME_stand124 144 +#define FRAME_stand125 145 +#define FRAME_stand126 146 +#define FRAME_stand127 147 +#define FRAME_stand128 148 +#define FRAME_stand129 149 +#define FRAME_stand130 150 +#define FRAME_stand201 151 +#define FRAME_stand202 152 +#define FRAME_stand203 153 +#define FRAME_stand204 154 +#define FRAME_stand205 155 +#define FRAME_stand206 156 +#define FRAME_stand207 157 +#define FRAME_stand208 158 +#define FRAME_stand209 159 +#define FRAME_stand210 160 +#define FRAME_stand211 161 +#define FRAME_stand212 162 +#define FRAME_stand213 163 +#define FRAME_stand214 164 +#define FRAME_stand215 165 +#define FRAME_stand216 166 +#define FRAME_stand217 167 +#define FRAME_stand218 168 +#define FRAME_stand219 169 +#define FRAME_stand220 170 +#define FRAME_stand221 171 +#define FRAME_stand222 172 +#define FRAME_stand223 173 +#define FRAME_stand224 174 +#define FRAME_stand225 175 +#define FRAME_stand226 176 +#define FRAME_stand227 177 +#define FRAME_stand228 178 +#define FRAME_stand229 179 +#define FRAME_stand230 180 +#define FRAME_walk01 181 +#define FRAME_walk02 182 +#define FRAME_walk03 183 +#define FRAME_walk04 184 +#define FRAME_walk05 185 +#define FRAME_walk06 186 +#define FRAME_walk07 187 +#define FRAME_walk08 188 +#define FRAME_walk09 189 +#define FRAME_walk10 190 +#define FRAME_walk11 191 +#define FRAME_walk12 192 +#define FRAME_walk13 193 +#define FRAME_walk14 194 +#define FRAME_walk15 195 +#define FRAME_walk16 196 +#define FRAME_walk17 197 +#define FRAME_walk18 198 +#define FRAME_walk19 199 +#define FRAME_walk20 200 +#define FRAME_walk21 201 +#define FRAME_walk22 202 +#define FRAME_walk23 203 +#define FRAME_walk24 204 +#define FRAME_walk25 205 +#define FRAME_walk26 206 +#define FRAME_walk27 207 +#define FRAME_recln201 208 +#define FRAME_recln202 209 +#define FRAME_recln203 210 +#define FRAME_recln204 211 +#define FRAME_recln205 212 +#define FRAME_recln206 213 +#define FRAME_recln207 214 +#define FRAME_recln208 215 +#define FRAME_recln209 216 +#define FRAME_recln210 217 +#define FRAME_recln211 218 +#define FRAME_recln212 219 +#define FRAME_recln213 220 +#define FRAME_recln214 221 +#define FRAME_recln215 222 +#define FRAME_recln216 223 +#define FRAME_recln217 224 +#define FRAME_recln218 225 +#define FRAME_recln219 226 +#define FRAME_recln220 227 +#define FRAME_recln221 228 +#define FRAME_recln222 229 +#define FRAME_recln223 230 +#define FRAME_recln224 231 +#define FRAME_recln225 232 +#define FRAME_recln226 233 +#define FRAME_recln227 234 +#define FRAME_recln228 235 +#define FRAME_recln229 236 +#define FRAME_recln230 237 +#define FRAME_recln231 238 +#define FRAME_recln232 239 +#define FRAME_recln233 240 +#define FRAME_recln234 241 +#define FRAME_recln235 242 +#define FRAME_recln236 243 +#define FRAME_recln237 244 +#define FRAME_recln238 245 +#define FRAME_recln239 246 +#define FRAME_recln240 247 +#define FRAME_recln101 248 +#define FRAME_recln102 249 +#define FRAME_recln103 250 +#define FRAME_recln104 251 +#define FRAME_recln105 252 +#define FRAME_recln106 253 +#define FRAME_recln107 254 +#define FRAME_recln108 255 +#define FRAME_recln109 256 +#define FRAME_recln110 257 +#define FRAME_recln111 258 +#define FRAME_recln112 259 +#define FRAME_recln113 260 +#define FRAME_recln114 261 +#define FRAME_recln115 262 +#define FRAME_recln116 263 +#define FRAME_recln117 264 +#define FRAME_recln118 265 +#define FRAME_recln119 266 +#define FRAME_recln120 267 +#define FRAME_recln121 268 +#define FRAME_recln122 269 +#define FRAME_recln123 270 +#define FRAME_recln124 271 +#define FRAME_recln125 272 +#define FRAME_recln126 273 +#define FRAME_recln127 274 +#define FRAME_recln128 275 +#define FRAME_recln129 276 +#define FRAME_recln130 277 +#define FRAME_recln131 278 +#define FRAME_recln132 279 +#define FRAME_recln133 280 +#define FRAME_recln134 281 +#define FRAME_recln135 282 +#define FRAME_recln136 283 +#define FRAME_recln137 284 +#define FRAME_recln138 285 +#define FRAME_recln139 286 +#define FRAME_recln140 287 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/demon/demon.c b/src/game/monster/demon/demon.c new file mode 100644 index 000000000..d45aa0224 --- /dev/null +++ b/src/game/monster/demon/demon.c @@ -0,0 +1,410 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "demon.h" + +static int sound_death; +static int sound_hit; +static int sound_jump; +static int sound_land; +static int sound_pain; +static int sound_sight; +static int sound_search; + +// Stand +static mframe_t demon_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}, +}; +mmove_t demon_move_stand = +{ + FRAME_stand1, + FRAME_stand13, + demon_frames_stand, + NULL +}; + +void +demon_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &demon_move_stand; +} + +// Run +static mframe_t demon_frames_run [] = +{ + {ai_run, 20, NULL}, + {ai_run, 15, NULL}, + {ai_run, 36, NULL}, + {ai_run, 20, NULL}, + + {ai_run, 15, NULL}, + {ai_run, 36, NULL} +}; +mmove_t demon_move_run = +{ + FRAME_run1, + FRAME_run6, + demon_frames_run, + NULL +}; + +void +demon_run(edict_t *self) +{ + self->monsterinfo.currentmove = &demon_move_run; +} + +static qboolean +check_demon_jump(edict_t *self) +{ + vec3_t dir; + float distance; + + if (!self->enemy) + return false; + if (self->s.origin[2] + self->mins[2] > self->enemy->s.origin[2] + self->enemy->mins[2] + 0.75 * self->enemy->size[2]) + return false; + if (self->s.origin[2] + self->mins[2] < self->enemy->s.origin[2] + self->enemy->mins[2] + 0.25 * self->enemy->size[2]) + return false; + + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + dir[2] = 0; + distance = VectorLength(dir); + + if (distance < 100) + return false; + + if (distance > 200) + { + if (random() < 0.9) + return false; + } + return true; +}; + +void +demon_jump_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->health < 1) + return; + if (other->takedamage) + { + if (VectorLength(self->velocity) > 400) + { + int damage = 40 + 10 * random(); + + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, damage, 0, 0, 0); + } + } + else + gi.sound(self, CHAN_WEAPON, sound_land, 1, ATTN_NORM, 0); + self->touch = NULL; + + if (!M_CheckBottom(self)) + { + if (self->groundentity) + { + self->monsterinfo.currentmove = &demon_move_run; + self->movetype = MOVETYPE_STEP; + } + return; + } +} + +static void +demon_jump(edict_t *self) +{ + vec3_t forward; + + AngleVectors(self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + VectorScale(forward, 600, self->velocity); + self->velocity[2] = 250; + + self->groundentity = NULL; + self->touch = demon_jump_touch; +} + +static void +demon_roar(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_jump, 1, ATTN_NORM, 0); +} + +// Attack +static mframe_t demon_frames_jump [] = +{ + {ai_charge, 0, demon_roar}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, demon_jump}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t demon_move_jump = +{ + FRAME_leap1, + FRAME_leap12, + demon_frames_jump, + demon_run +}; + +void +demon_attack(edict_t *self) +{ + if (check_demon_jump(self)) + { + self->monsterinfo.currentmove = &demon_move_jump; + } +} + +static void +demon_melee_step(edict_t *self) +{ + vec3_t dir; + static vec3_t aim = {100, 0, -24}; + int damage; + + if (!self->enemy) + return; + VectorSubtract(self->s.origin, self->enemy->s.origin, dir); + + if (VectorLength(dir) > 100.0) + return; + damage = 10 + 5 * random(); + + if (fire_hit(self, aim, damage, damage)) + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); +} + +// Melee +static mframe_t demon_frames_melee [] = +{ + {ai_charge, 4, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 1, NULL}, + + {ai_charge, 14, demon_melee_step}, + {ai_charge, 1, NULL}, + {ai_charge, 6, NULL}, + {ai_charge, 8, NULL}, + + {ai_charge, 4, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 12, demon_melee_step}, + {ai_charge, 5, NULL}, + + {ai_charge, 8, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 4, NULL} +}; +mmove_t demon_move_melee = +{ + FRAME_attacka1, + FRAME_attacka15, + demon_frames_melee, + demon_run +}; + +void +demon_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &demon_move_melee; +} + +// Pain +static mframe_t demon_frames_pain [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t demon_move_pain = +{ + FRAME_pain1, + FRAME_pain6, + demon_frames_pain, + demon_run +}; + +void +demon_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + // decino: No pain animations in Nightmare mode + if (skill->value == SKILL_HARDPLUS) + return; + if (self->touch == demon_jump_touch) + return; + if (self->pain_debounce_time > level.time) + return; + self->pain_debounce_time = level.time + 1.0; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (random() * 200 > damage) + return; + self->monsterinfo.currentmove = &demon_move_pain; +} + +void +demon_dead(edict_t *self) +{ + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +// Death +static mframe_t demon_frames_die [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t demon_move_die = +{ + FRAME_death1, + FRAME_death9, + demon_frames_die, + demon_dead +}; + +void +demon_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 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; + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &demon_move_die; +} + +// Sight +void +demon_sight(edict_t *self, edict_t *other /* unused */) +{ + gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +// Search +void +demon_search(edict_t *self) +{ + gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +/* + * QUAKED monster_demon (1 .5 0) (-32, -32, -24) (32, 32, 64) Ambush Trigger_Spawn Sight + */ +void +SP_monster_demon(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/demon/tris.md2"); + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, 64); + self->health = 300; + + sound_death = gi.soundindex("demon/ddeath.wav"); + sound_hit = gi.soundindex("demon/dhit2.wav"); + sound_jump = gi.soundindex("demon/djump.wav"); + sound_land = gi.soundindex("demon/dland2.wav"); + sound_pain = gi.soundindex("demon/dpain1.wav"); + sound_search = gi.soundindex("demon/idle1.wav"); + sound_sight = gi.soundindex("demon/sight2.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -80; + self->mass = 300; + + self->monsterinfo.stand = demon_stand; + self->monsterinfo.walk = demon_run; + self->monsterinfo.run = demon_run; + self->monsterinfo.attack = demon_attack; + self->monsterinfo.melee = demon_melee; + self->monsterinfo.sight = demon_sight; + self->monsterinfo.search = demon_search; + + self->pain = demon_pain; + self->die = demon_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/demon/demon.h b/src/game/monster/demon/demon.h new file mode 100644 index 000000000..68e8eaee8 --- /dev/null +++ b/src/game/monster/demon/demon.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Demon animations. + * + * ======================================================================= + */ + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_stand8 7 +#define FRAME_stand9 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_walk1 13 +#define FRAME_walk2 14 +#define FRAME_walk3 15 +#define FRAME_walk4 16 +#define FRAME_walk5 17 +#define FRAME_walk6 18 +#define FRAME_walk7 19 +#define FRAME_walk8 20 +#define FRAME_run1 21 +#define FRAME_run2 22 +#define FRAME_run3 23 +#define FRAME_run4 24 +#define FRAME_run5 25 +#define FRAME_run6 26 +#define FRAME_leap1 27 +#define FRAME_leap2 28 +#define FRAME_leap3 29 +#define FRAME_leap4 30 +#define FRAME_leap5 31 +#define FRAME_leap6 32 +#define FRAME_leap7 33 +#define FRAME_leap8 34 +#define FRAME_leap9 35 +#define FRAME_leap10 36 +#define FRAME_leap11 37 +#define FRAME_leap12 38 +#define FRAME_pain1 39 +#define FRAME_pain2 40 +#define FRAME_pain3 41 +#define FRAME_pain4 42 +#define FRAME_pain5 43 +#define FRAME_pain6 44 +#define FRAME_death1 45 +#define FRAME_death2 46 +#define FRAME_death3 47 +#define FRAME_death4 48 +#define FRAME_death5 49 +#define FRAME_death6 50 +#define FRAME_death7 51 +#define FRAME_death8 52 +#define FRAME_death9 53 +#define FRAME_attacka1 54 +#define FRAME_attacka2 55 +#define FRAME_attacka3 56 +#define FRAME_attacka4 57 +#define FRAME_attacka5 58 +#define FRAME_attacka6 59 +#define FRAME_attacka7 60 +#define FRAME_attacka8 61 +#define FRAME_attacka9 62 +#define FRAME_attacka10 63 +#define FRAME_attacka11 64 +#define FRAME_attacka12 65 +#define FRAME_attacka13 66 +#define FRAME_attacka14 67 +#define FRAME_attacka15 68 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/dog/dog.c b/src/game/monster/dog/dog.c new file mode 100644 index 000000000..5e9e559a7 --- /dev/null +++ b/src/game/monster/dog/dog.c @@ -0,0 +1,413 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "dog.h" + +static int sound_melee; +static int sound_death; +static int sound_pain; +static int sound_sight; +static int sound_search; + +void dog_leap(edict_t *self); + +// Stand +static mframe_t dog_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}, +}; +mmove_t dog_move_stand = +{ + FRAME_stand1, + FRAME_stand9, + dog_frames_stand, + NULL +}; + +void +dog_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &dog_move_stand; +} + +// Run +static mframe_t dog_frames_run [] = +{ + {ai_run, 16, NULL}, + {ai_run, 32, NULL}, + {ai_run, 32, NULL}, + {ai_run, 20, NULL}, + {ai_run, 64, NULL}, + + {ai_run, 32, NULL}, + {ai_run, 16, NULL}, + {ai_run, 32, NULL}, + {ai_run, 32, NULL}, + + {ai_run, 20, NULL}, + {ai_run, 64, NULL}, + {ai_run, 32, dog_leap} +}; +mmove_t dog_move_run = +{ + FRAME_run1, + FRAME_run12, + dog_frames_run, + NULL +}; + +void +dog_run(edict_t *self) +{ + self->monsterinfo.currentmove = &dog_move_run; +} + +void +dog_leap_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->health < 1) + return; + if (other->takedamage) + { + if (VectorLength(self->velocity) > 300) + { + int damage = 10 + 10 * random(); + + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, damage, 0, 0, 0); + } + } + self->touch = NULL; + + if (!M_CheckBottom(self)) + { + if (self->groundentity) + { + self->monsterinfo.currentmove = &dog_move_run; + self->movetype = MOVETYPE_STEP; + } + return; + } +} + +static void +dog_leap_step(edict_t *self) +{ + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + VectorScale(forward, 300, self->velocity); + self->velocity[2] = 200; + + self->groundentity = NULL; + self->touch = dog_leap_touch; +} + +// Leap +static mframe_t dog_frames_leap [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, dog_leap_step}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t dog_move_leap = +{ + FRAME_leap1, + FRAME_leap9, + dog_frames_leap, + dog_run +}; + +void +dog_leap(edict_t *self) +{ + self->monsterinfo.currentmove = &dog_move_leap; +} + +static void +dogbite_step(edict_t *self) +{ + vec3_t dir; + static vec3_t aim = {100, 0, -24}; + int damage; + + gi.sound(self, CHAN_VOICE, sound_melee, 1, ATTN_NORM, 0); + + if (!self->enemy) + return; + VectorSubtract(self->s.origin, self->enemy->s.origin, dir); + + if (VectorLength(dir) > 100.0) + return; + damage = (random() + random() + random()) * 8; + + fire_hit(self, aim, damage, damage); +} + +// Melee +static mframe_t dog_frames_melee [] = +{ + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, dogbite_step}, + + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL} +}; +mmove_t dog_move_melee = +{ + FRAME_attack1, + FRAME_attack8, + dog_frames_melee, + dog_run +}; + +void +dog_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &dog_move_melee; +} + +// Sight +void +dog_sight(edict_t *self, edict_t *other /* unused */) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +// Search +void +dog_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +// Pain (1) +static mframe_t dog_frames_pain1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t dog_move_pain1 = +{ + FRAME_pain1, + FRAME_pain6, + dog_frames_pain1, + dog_run +}; + +// Pain (2) +static mframe_t dog_frames_pain2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -4, NULL}, + {ai_move, -12, NULL}, + + {ai_move, -12, NULL}, + {ai_move, -2, NULL}, + {ai_move, 0, NULL}, + {ai_move, -4, NULL}, + + {ai_move, 0, NULL}, + {ai_move, -10, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t dog_move_pain2 = +{ + FRAME_painb1, + FRAME_painb16, + dog_frames_pain2, + dog_run +}; + +void +dog_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + // decino: No pain animations in Nightmare mode + if (skill->value == SKILL_HARDPLUS) + return; + if (random() > 0.5) + self->monsterinfo.currentmove = &dog_move_pain1; + else + self->monsterinfo.currentmove = &dog_move_pain2; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); +} + +void +dog_dead(edict_t *self) +{ + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +// Death (1) +static mframe_t dog_frames_die1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t dog_move_die1 = +{ + FRAME_death1, + FRAME_death9, + dog_frames_die1, + dog_dead +}; + +// Death (2) +static mframe_t dog_frames_die2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t dog_move_die2 = +{ + FRAME_deathb1, + FRAME_deathb9, + dog_frames_die2, + dog_dead +}; + +void +dog_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 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; + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (random() > 0.5) + self->monsterinfo.currentmove = &dog_move_die1; + else + self->monsterinfo.currentmove = &dog_move_die2; +} + +/* + * QUAKED monster_dog (1 .5 0) (-32, -32, -24) (32, 32, 40) Ambush Trigger_Spawn Sight + */ +void +SP_monster_dog(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/dog/tris.md2"); + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, 40); + self->health = 25; + + sound_melee = gi.soundindex("dog/dattack1.wav"); + sound_death = gi.soundindex("dog/ddeath.wav"); + sound_pain = gi.soundindex("dog/dpain1.wav"); + sound_sight = gi.soundindex("dog/dsight.wav"); + sound_search = gi.soundindex("dog/idle.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -35; + self->mass = 30; + + self->monsterinfo.stand = dog_stand; + self->monsterinfo.walk = dog_run; + self->monsterinfo.run = dog_run; + self->monsterinfo.attack = dog_leap; + self->monsterinfo.melee = dog_melee; + self->monsterinfo.sight = dog_sight; + self->monsterinfo.search = dog_search; + + self->pain = dog_pain; + self->die = dog_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/dog/dog.h b/src/game/monster/dog/dog.h new file mode 100644 index 000000000..54faad947 --- /dev/null +++ b/src/game/monster/dog/dog.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Dog animations. + * + * ======================================================================= + */ + +#define FRAME_attack1 0 +#define FRAME_attack2 1 +#define FRAME_attack3 2 +#define FRAME_attack4 3 +#define FRAME_attack5 4 +#define FRAME_attack6 5 +#define FRAME_attack7 6 +#define FRAME_attack8 7 +#define FRAME_death1 8 +#define FRAME_death2 9 +#define FRAME_death3 10 +#define FRAME_death4 11 +#define FRAME_death5 12 +#define FRAME_death6 13 +#define FRAME_death7 14 +#define FRAME_death8 15 +#define FRAME_death9 16 +#define FRAME_deathb1 17 +#define FRAME_deathb2 18 +#define FRAME_deathb3 19 +#define FRAME_deathb4 20 +#define FRAME_deathb5 21 +#define FRAME_deathb6 22 +#define FRAME_deathb7 23 +#define FRAME_deathb8 24 +#define FRAME_deathb9 25 +#define FRAME_pain1 26 +#define FRAME_pain2 27 +#define FRAME_pain3 28 +#define FRAME_pain4 29 +#define FRAME_pain5 30 +#define FRAME_pain6 31 +#define FRAME_painb1 32 +#define FRAME_painb2 33 +#define FRAME_painb3 34 +#define FRAME_painb4 35 +#define FRAME_painb5 36 +#define FRAME_painb6 37 +#define FRAME_painb7 38 +#define FRAME_painb8 39 +#define FRAME_painb9 40 +#define FRAME_painb10 41 +#define FRAME_painb11 42 +#define FRAME_painb12 43 +#define FRAME_painb13 44 +#define FRAME_painb14 45 +#define FRAME_painb15 46 +#define FRAME_painb16 47 +#define FRAME_run1 48 +#define FRAME_run2 49 +#define FRAME_run3 50 +#define FRAME_run4 51 +#define FRAME_run5 52 +#define FRAME_run6 53 +#define FRAME_run7 54 +#define FRAME_run8 55 +#define FRAME_run9 56 +#define FRAME_run10 57 +#define FRAME_run11 58 +#define FRAME_run12 59 +#define FRAME_leap1 60 +#define FRAME_leap2 61 +#define FRAME_leap3 62 +#define FRAME_leap4 63 +#define FRAME_leap5 64 +#define FRAME_leap6 65 +#define FRAME_leap7 66 +#define FRAME_leap8 67 +#define FRAME_leap9 68 +#define FRAME_stand1 69 +#define FRAME_stand2 70 +#define FRAME_stand3 71 +#define FRAME_stand4 72 +#define FRAME_stand5 73 +#define FRAME_stand6 74 +#define FRAME_stand7 75 +#define FRAME_stand8 76 +#define FRAME_stand9 77 +#define FRAME_walk1 78 +#define FRAME_walk2 79 +#define FRAME_walk3 80 +#define FRAME_walk4 81 +#define FRAME_walk5 82 +#define FRAME_walk6 83 +#define FRAME_walk7 84 +#define FRAME_walk8 85 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/enforcer/enforcer.c b/src/game/monster/enforcer/enforcer.c new file mode 100644 index 000000000..1063048bf --- /dev/null +++ b/src/game/monster/enforcer/enforcer.c @@ -0,0 +1,564 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "enforcer.h" + +static int sound_death; +static int sound_hit; +static int sound_attack; +static int sound_search; +static int sound_pain1; +static int sound_pain2; +static int sound_sight1; +static int sound_sight2; +static int sound_sight3; +static int sound_sight4; + +// Stand +static mframe_t enforcer_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}, +}; +mmove_t enforcer_move_stand = +{ + FRAME_stand1, + FRAME_stand7, + enforcer_frames_stand, + NULL +}; + +void +enforcer_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &enforcer_move_stand; +} + +// Walk +static mframe_t enforcer_frames_walk [] = +{ + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL} +}; + +mmove_t enforcer_move_walk = +{ + FRAME_walk1, + FRAME_walk16, + enforcer_frames_walk, + NULL +}; + +void +enforcer_walk(edict_t *self) +{ + self->monsterinfo.currentmove = &enforcer_move_walk; +} + +// Run +static mframe_t enforcer_frames_run [] = +{ + {ai_run, 18, NULL}, + {ai_run, 14, NULL}, + {ai_run, 7, NULL}, + {ai_run, 12, NULL}, + + {ai_run, 14, NULL}, + {ai_run, 14, NULL}, + {ai_run, 7, NULL}, + {ai_run, 11, NULL} +}; +mmove_t enforcer_move_run = +{ + FRAME_run1, + FRAME_run8, + enforcer_frames_run, + NULL +}; + +void +enforcer_run(edict_t *self) +{ + self->monsterinfo.currentmove = &enforcer_move_run; +} + +void +enfbolt_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + { + return; + } + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (other->takedamage) + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, 0); + } + else + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(15); + gi.WritePosition(self->s.origin); + gi.WriteDir((!plane) ? vec3_origin : plane->normal); + gi.WriteByte(226); + gi.multicast(self->s.origin, MULTICAST_PVS); + + gi.sound (self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + } + + G_FreeEdict(self); +} + +static void +fire_enfbolt(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed) +{ + edict_t *bolt; + trace_t tr; + + if (!self) + { + return; + } + + bolt = G_Spawn(); + bolt->svflags = SVF_DEADMONSTER; + VectorCopy(start, bolt->s.origin); + VectorCopy(start, bolt->s.old_origin); + vectoangles(dir, bolt->s.angles); + VectorScale(dir, speed, bolt->velocity); + + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= EF_HYPERBLASTER; + VectorClear(bolt->mins); + VectorClear(bolt->maxs); + + bolt->s.modelindex = gi.modelindex("models/monsters/objects/laser/tris.md2"); + bolt->owner = self; + bolt->touch = enfbolt_touch; + bolt->nextthink = level.time + 5; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "enfbolt"; + gi.linkentity(bolt); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + + if (tr.fraction < 1.0) + { + VectorMA(bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch(bolt, tr.ent, NULL, NULL); + } + gi.sound(self, CHAN_WEAPON, sound_attack, 1, ATTN_NORM, 0); +} + +static void +enforcer_fire_bolt(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + vec3_t offset = {30, 8.5, 16}; + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, offset, forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + + fire_enfbolt(self, start, dir, 15, 600); +} + +// Attack (second half) +static mframe_t enforcer_frames_attack2 [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, enforcer_fire_bolt}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; +mmove_t enforcer_move_attack2 = +{ + FRAME_attack5, + FRAME_attack10, + enforcer_frames_attack2, + enforcer_run +}; + +static void +enforcer_attack_again(edict_t *self) +{ + self->s.frame = FRAME_attack4; + self->monsterinfo.currentmove = &enforcer_move_attack2; +} + +// Attack (first half) +static mframe_t enforcer_frames_attack1 [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, enforcer_fire_bolt}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; +mmove_t enforcer_move_attack1 = +{ + FRAME_attack1, + FRAME_attack8, + enforcer_frames_attack1, + enforcer_attack_again +}; + +void +enforcer_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &enforcer_move_attack1; +} + +// Sight +void +enforcer_sight(edict_t *self, edict_t *other /* unused */) +{ + int r = (int)(random() * 4); + + switch (r) + { + case 0: gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); break; + case 1: gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); break; + case 2: gi.sound(self, CHAN_VOICE, sound_sight3, 1, ATTN_NORM, 0); break; + case 3: gi.sound(self, CHAN_VOICE, sound_sight4, 1, ATTN_NORM, 0); break; + default: gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); break; + } +} + +// Search +void +enforcer_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +// Pain (1) +static mframe_t enforcer_frames_pain1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t enforcer_move_pain1 = +{ + FRAME_paina1, + FRAME_paina4, + enforcer_frames_pain1, + enforcer_run +}; + +// Pain (2) +static mframe_t enforcer_frames_pain2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t enforcer_move_pain2 = +{ + FRAME_painb1, + FRAME_painb5, + enforcer_frames_pain2, + enforcer_run +}; + +// Pain (3) +static mframe_t enforcer_frames_pain3 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t enforcer_move_pain3 = +{ + FRAME_painc1, + FRAME_painc8, + enforcer_frames_pain3, + enforcer_run +}; + +// Pain (4) +static mframe_t enforcer_frames_pain4 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t enforcer_move_pain4 = +{ + FRAME_paind1, + FRAME_paind19, + enforcer_frames_pain4, + enforcer_run +}; + +// Pain +void +enforcer_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + float r; + + // decino: No pain animations in Nightmare mode + if (skill->value == SKILL_HARDPLUS) + return; + if (level.time < self->pain_debounce_time) + return; + r = random(); + + if (r < 0.5) + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + else + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + if (r < 0.2) + { + self->pain_debounce_time = level.time + 1.0; + self->monsterinfo.currentmove = &enforcer_move_pain1; + } + else if (r < 0.4) + { + self->pain_debounce_time = level.time + 1.0; + self->monsterinfo.currentmove = &enforcer_move_pain2; + } + else if (r < 0.7) + { + self->pain_debounce_time = level.time + 1.0; + self->monsterinfo.currentmove = &enforcer_move_pain3; + } + else + { + self->pain_debounce_time = level.time + 2.0; + self->monsterinfo.currentmove = &enforcer_move_pain4; + } +} + +static void +enforcer_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); +} + +// Death (1) +static mframe_t enforcer_frames_death1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 14, NULL}, + + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 3, NULL}, + {ai_move, 5, NULL}, + {ai_move, 5, NULL}, + {ai_move, 5, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t enforcer_move_death1 = +{ + FRAME_death1, + FRAME_death14, + enforcer_frames_death1, + enforcer_dead +}; + +// Death (2) +static mframe_t enforcer_frames_death2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t enforcer_move_death2 = +{ + FRAME_fdeath1, + FRAME_fdeath11, + enforcer_frames_death2, + enforcer_dead +}; + +// Death +void +enforcer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 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; + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (random() < 0.5) + self->monsterinfo.currentmove = &enforcer_move_death1; + else + self->monsterinfo.currentmove = &enforcer_move_death2; +} + +/* + * QUAKED monster_enforcer (1 .5 0) (-16, -16, -24) (16, 16, 40) Ambush Trigger_Spawn Sight + */ +void +SP_monster_enforcer(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/enforcer/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 40); + self->health = 80; + + sound_death = gi.soundindex("enforcer/death1.wav"); + sound_hit = gi.soundindex("enforcer/enfstop.wav"); + sound_attack = gi.soundindex("enforcer/enfire.wav"); + sound_search = gi.soundindex("enforcer/idle1.wav"); + sound_pain1 = gi.soundindex("enforcer/pain1.wav"); + sound_pain2 = gi.soundindex("enforcer/pain2.wav"); + sound_sight1 = gi.soundindex("enforcer/sight1.wav"); + sound_sight2 = gi.soundindex("enforcer/sight2.wav"); + sound_sight3 = gi.soundindex("enforcer/sight3.wav"); + sound_sight4 = gi.soundindex("enforcer/sight4.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -35; + self->mass = 80; + + self->monsterinfo.stand = enforcer_stand; + self->monsterinfo.walk = enforcer_walk; + self->monsterinfo.run = enforcer_run; + self->monsterinfo.attack = enforcer_attack; + self->monsterinfo.sight = enforcer_sight; + self->monsterinfo.search = enforcer_search; + + self->pain = enforcer_pain; + self->die = enforcer_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/enforcer/enforcer.h b/src/game/monster/enforcer/enforcer.h new file mode 100644 index 000000000..a51225924 --- /dev/null +++ b/src/game/monster/enforcer/enforcer.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Enforcer animations. + * + * ======================================================================= + */ + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_walk1 7 +#define FRAME_walk2 8 +#define FRAME_walk3 9 +#define FRAME_walk4 10 +#define FRAME_walk5 11 +#define FRAME_walk6 12 +#define FRAME_walk7 13 +#define FRAME_walk8 14 +#define FRAME_walk9 15 +#define FRAME_walk10 16 +#define FRAME_walk11 17 +#define FRAME_walk12 18 +#define FRAME_walk13 19 +#define FRAME_walk14 20 +#define FRAME_walk15 21 +#define FRAME_walk16 22 +#define FRAME_run1 23 +#define FRAME_run2 24 +#define FRAME_run3 25 +#define FRAME_run4 26 +#define FRAME_run5 27 +#define FRAME_run6 28 +#define FRAME_run7 29 +#define FRAME_run8 30 +#define FRAME_attack1 31 +#define FRAME_attack2 32 +#define FRAME_attack3 33 +#define FRAME_attack4 34 +#define FRAME_attack5 35 +#define FRAME_attack6 36 +#define FRAME_attack7 37 +#define FRAME_attack8 38 +#define FRAME_attack9 39 +#define FRAME_attack10 40 +#define FRAME_death1 41 +#define FRAME_death2 42 +#define FRAME_death3 43 +#define FRAME_death4 44 +#define FRAME_death5 45 +#define FRAME_death6 46 +#define FRAME_death7 47 +#define FRAME_death8 48 +#define FRAME_death9 49 +#define FRAME_death10 50 +#define FRAME_death11 51 +#define FRAME_death12 52 +#define FRAME_death13 53 +#define FRAME_death14 54 +#define FRAME_fdeath1 55 +#define FRAME_fdeath2 56 +#define FRAME_fdeath3 57 +#define FRAME_fdeath4 58 +#define FRAME_fdeath5 59 +#define FRAME_fdeath6 60 +#define FRAME_fdeath7 61 +#define FRAME_fdeath8 62 +#define FRAME_fdeath9 63 +#define FRAME_fdeath10 64 +#define FRAME_fdeath11 65 +#define FRAME_paina1 66 +#define FRAME_paina2 67 +#define FRAME_paina3 68 +#define FRAME_paina4 69 +#define FRAME_painb1 70 +#define FRAME_painb2 71 +#define FRAME_painb3 72 +#define FRAME_painb4 73 +#define FRAME_painb5 74 +#define FRAME_painc1 75 +#define FRAME_painc2 76 +#define FRAME_painc3 77 +#define FRAME_painc4 78 +#define FRAME_painc5 79 +#define FRAME_painc6 80 +#define FRAME_painc7 81 +#define FRAME_painc8 82 +#define FRAME_paind1 83 +#define FRAME_paind2 84 +#define FRAME_paind3 85 +#define FRAME_paind4 86 +#define FRAME_paind5 87 +#define FRAME_paind6 88 +#define FRAME_paind7 89 +#define FRAME_paind8 90 +#define FRAME_paind9 91 +#define FRAME_paind10 92 +#define FRAME_paind11 93 +#define FRAME_paind12 94 +#define FRAME_paind13 95 +#define FRAME_paind14 96 +#define FRAME_paind15 97 +#define FRAME_paind16 98 +#define FRAME_paind17 99 +#define FRAME_paind18 100 +#define FRAME_paind19 101 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/fixbot/fixbot.c b/src/game/monster/fixbot/fixbot.c new file mode 100644 index 000000000..2fd64735f --- /dev/null +++ b/src/game/monster/fixbot/fixbot.c @@ -0,0 +1,1687 @@ +/* + * Copyright (c) ZeniMax Media Inc. + * Licensed under the GNU General Public License 2.0. + */ + +/* ============================================================== + * + * Fixbot + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "fixbot.h" + +#define MZ2_fixbot_BLASTER_1 MZ2_HOVER_BLASTER_1 + +#define FIXBOT_MAX_STUCK_FRAMES 10 +#define FIXBOT_GOAL_TIMEOUT 15 +#define FIXBOT_WELD_GOAL_TIMEOUT 15 + +qboolean visible(edict_t *self, edict_t *other); +qboolean infront(edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_die; +static int sound_weld1; +static int sound_weld2; +static int sound_weld3; + +void fixbot_run(edict_t *self); +void fixbot_stand(edict_t *self); +void fixbot_dead(edict_t *self); +void fixbot_attack(edict_t *self); +void fixbot_fire_blaster(edict_t *self); +void fixbot_fire_welder(edict_t *self); +void fixbot_die(edict_t *self, edict_t *inflictor, edict_t *attacker, + int damage, vec3_t point); + +void M_MoveToGoal(edict_t *ent, float dist); + +void use_scanner(edict_t *self); +void change_to_roam(edict_t *self); +void fly_vertical(edict_t *self); + +extern mmove_t fixbot_move_forward; +extern mmove_t fixbot_move_stand; +extern mmove_t fixbot_move_stand2; +extern mmove_t fixbot_move_roamgoal; + +extern mmove_t fixbot_move_weld_start; +extern mmove_t fixbot_move_weld; +extern mmove_t fixbot_move_weld_end; +extern mmove_t fixbot_move_takeoff; +extern mmove_t fixbot_move_landing; +extern mmove_t fixbot_move_turn; + +extern void roam_goal(edict_t *self); +void ED_CallSpawn(edict_t *ent); + +float +crand(void) +{ + return (rand() & 32767) * (2.0 / 32767) - 1; +} + +static edict_t * +fixbot_FindDeadMonster(edict_t *self) +{ + edict_t *ent = NULL; + edict_t *best = NULL; + + if (!self) + { + return NULL; + } + + while ((ent = findradius(ent, self->s.origin, 1024)) != NULL) + { + if (ent == self) + { + continue; + } + + if (!(ent->svflags & SVF_MONSTER)) + { + continue; + } + + if (ent->monsterinfo.aiflags & AI_GOOD_GUY) + { + continue; + } + + if (ent->owner) + { + continue; + } + + if (ent->health > 0) + { + continue; + } + + if (ent->nextthink) + { + continue; + } + + if (!visible(self, ent)) + { + continue; + } + + if (!best) + { + best = ent; + continue; + } + + if (ent->max_health <= best->max_health) + { + continue; + } + + best = ent; + } + + return best; +} + +int +fixbot_search(edict_t *self) +{ + edict_t *ent; + + if (!self) + { + return 0; + } + + if (!self->goalentity) + { + ent = fixbot_FindDeadMonster(self); + + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->owner = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget(self); + return 1; + } + } + + return 0; +} + +void +bot_goal_think(edict_t *self) +{ + if (!self) + { + return; + } + + /* clean up the bot_goal if the fixbot loses it (to avoid entity leaks) */ + if (!self->owner || !self->owner->inuse || self->owner->goalentity != self) + { + G_FreeEdict(self); + } + else + { + self->nextthink = level.time + FRAMETIME; + } +} + +static edict_t * +make_bot_goal(edict_t *self) +{ + edict_t *ent = G_Spawn(); + + ent->classname = "bot_goal"; + ent->solid = SOLID_BBOX; + ent->owner = self; + + ent->think = bot_goal_think; + ent->nextthink = level.time + FRAMETIME; + ent->touch_debounce_time = level.time + FIXBOT_GOAL_TIMEOUT; + + return ent; +} + +void +landing_goal(edict_t *self) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t end; + edict_t *ent; + + if (!self) + { + return; + } + + ent = make_bot_goal(self); + VectorSet(ent->mins, -32, -32, -24); + VectorSet(ent->maxs, 32, 32, 24); + gi.linkentity(ent); + + AngleVectors(self->s.angles, forward, right, up); + VectorMA(self->s.origin, 32, forward, end); + VectorMA(self->s.origin, -8096, up, end); + + tr = gi.trace(self->s.origin, ent->mins, ent->maxs, + end, self, MASK_MONSTERSOLID); + + VectorCopy(tr.endpos, ent->s.origin); + gi.linkentity(ent); + + self->goalentity = self->enemy = ent; + self->monsterinfo.currentmove = &fixbot_move_landing; +} + +void +takeoff_goal(edict_t *self) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t end; + edict_t *ent; + + if (!self) + { + return; + } + + ent = make_bot_goal(self); + + VectorSet(ent->mins, -32, -32, -24); + VectorSet(ent->maxs, 32, 32, 24); + gi.linkentity(ent); + + AngleVectors(self->s.angles, forward, right, up); + VectorMA(self->s.origin, 32, forward, end); + VectorMA(self->s.origin, 128, up, end); + + tr = gi.trace(self->s.origin, ent->mins, ent->maxs, + end, self, MASK_MONSTERSOLID); + + VectorCopy(tr.endpos, ent->s.origin); + gi.linkentity(ent); + + self->goalentity = self->enemy = ent; + self->monsterinfo.currentmove = &fixbot_move_takeoff; +} + +void +change_to_roam(edict_t *self) +{ + if (!self) + { + return; + } + + if (fixbot_search(self)) + { + return; + } + + self->monsterinfo.currentmove = &fixbot_move_roamgoal; + + if (self->spawnflags & 16) + { + landing_goal(self); + self->monsterinfo.currentmove = &fixbot_move_landing; + self->spawnflags = 32; + } + + if (self->spawnflags & 8) + { + takeoff_goal(self); + self->monsterinfo.currentmove = &fixbot_move_takeoff; + self->spawnflags = 32; + } + + if (self->spawnflags & 4) + { + self->monsterinfo.currentmove = &fixbot_move_roamgoal; + self->spawnflags = 32; + } + + if (!self->spawnflags) + { + self->monsterinfo.currentmove = &fixbot_move_stand2; + } +} + +void +roam_goal(edict_t *self) +{ + trace_t tr; + vec3_t forward; + vec3_t end; + edict_t *ent; + vec3_t dang; + int len, oldlen, i; + vec3_t vec; + vec3_t whichvec; + + if (!self) + { + return; + } + + VectorClear(whichvec); + + oldlen = 0; + + for (i = 0; i < 12; i++) + { + VectorCopy(self->s.angles, dang); + + if (i < 6) + { + dang[YAW] += 30 * i; + } + else + { + dang[YAW] -= 30 * (i - 6); + } + + AngleVectors(dang, forward, NULL, NULL); + VectorMA(self->s.origin, 8192, forward, end); + + tr = gi.trace(self->s.origin, NULL, NULL, end, self, MASK_SHOT); + + VectorSubtract(self->s.origin, tr.endpos, vec); + len = VectorLength(vec); + + if (len > oldlen) + { + oldlen = len; + VectorCopy(tr.endpos, whichvec); + } + } + + ent = make_bot_goal(self); + VectorCopy(whichvec, ent->s.origin); + gi.linkentity(ent); + + self->goalentity = self->enemy = ent; + + self->monsterinfo.currentmove = &fixbot_move_turn; +} + +void +use_scanner(edict_t *self) +{ + edict_t *ent = NULL; + vec3_t vec; + + if (!self) + { + return; + } + + if (self->fly_sound_debounce_time < level.time && + strcmp(self->goalentity->classname, "object_repair") != 0) + { + while ((ent = findradius(ent, self->s.origin, 1024)) != NULL) + { + if (strcmp(ent->classname, "object_repair") != 0) + { + continue; + } + + if (ent->health < 100) + { + continue; + } + + if (!visible(self, ent)) + { + continue; + } + + /* remove the old one */ + if (strcmp(self->goalentity->classname, "bot_goal") == 0) + { + self->goalentity->nextthink = level.time + 0.1; + self->goalentity->think = G_FreeEdict; + } + + self->goalentity = self->enemy = ent; + + break; + } + } + + if (strcmp(self->goalentity->classname, "object_repair") == 0) + { + VectorSubtract(self->s.origin, self->goalentity->s.origin, vec); + + if (VectorLength(vec) < 56) + { + self->monsterinfo.currentmove = &fixbot_move_weld_start; + return; + } + } + else if (strcmp(self->goalentity->classname, "bot_goal") == 0) + { + VectorSubtract(self->s.origin, self->goalentity->s.origin, vec); + + if (self->goalentity->touch_debounce_time < level.time || VectorLength(vec) < 32) + { + self->goalentity->nextthink = level.time + 0.1; + self->goalentity->think = G_FreeEdict; + self->goalentity = self->enemy = NULL; + + self->monsterinfo.currentmove = &fixbot_move_stand; + + return; + } + } + + VectorSubtract(self->s.origin, self->s.old_origin, vec); + + if (VectorLength(vec) == 0) + { + self->count++; + } + else + { + self->count = 0; + } + + if (self->count > FIXBOT_MAX_STUCK_FRAMES) + { + /* bot is stuck, get new goalentity */ + + self->count = 0; + + if (strcmp(self->goalentity->classname, "bot_goal") == 0) + { + self->goalentity->nextthink = level.time + 0.1; + self->goalentity->think = G_FreeEdict; + self->goalentity = self->enemy = NULL; + } + else if (strcmp(self->goalentity->classname, "object_repair") == 0) + { + /* don't try to go for welding targets again for a while */ + self->fly_sound_debounce_time = level.time + FIXBOT_WELD_GOAL_TIMEOUT; + } + + self->monsterinfo.currentmove = &fixbot_move_stand; + } +} + +/* + * When the bot has found a landing pad + * it will proceed to its goalentity + * just above the landing pad and + * decend translated along the z the current + * frames are at 10fps + */ +void +blastoff(edict_t *self, vec3_t start, vec3_t aimdir, int damage, + int kick, int te_impact, int hspread, int vspread) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + if (!self) + { + return; + } + + hspread += (self->s.frame - FRAME_takeoff_01); + vspread += (self->s.frame - FRAME_takeoff_01); + + tr = gi.trace(self->s.origin, NULL, NULL, start, self, MASK_SHOT); + + if (!(tr.fraction < 1.0)) + { + vectoangles(aimdir, dir); + AngleVectors(dir, forward, right, up); + + r = crandom() * hspread; + u = crandom() * vspread; + VectorMA(start, 8192, forward, end); + VectorMA(end, r, right, end); + VectorMA(end, u, up, end); + + if (gi.pointcontents(start) & MASK_WATER) + { + water = true; + VectorCopy(start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace(start, NULL, NULL, end, self, content_mask); + + /* see if we hit water */ + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy(tr.endpos, water_start); + + if (!VectorCompare(start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + { + color = SPLASH_BROWN_WATER; + } + else + { + color = SPLASH_BLUE_WATER; + } + } + else if (tr.contents & CONTENTS_SLIME) + { + color = SPLASH_SLIME; + } + else if (tr.contents & CONTENTS_LAVA) + { + color = SPLASH_LAVA; + } + else + { + color = SPLASH_UNKNOWN; + } + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_SPLASH); + gi.WriteByte(8); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + gi.WriteByte(color); + gi.multicast(tr.endpos, MULTICAST_PVS); + } + + /* change bullet's course when it enters water */ + VectorSubtract(end, start, dir); + vectoangles(dir, dir); + AngleVectors(dir, forward, right, up); + r = crandom() * hspread * 2; + u = crandom() * vspread * 2; + VectorMA(water_start, 8192, forward, end); + VectorMA(end, r, right, end); + VectorMA(end, u, up, end); + } + + /* re-trace ignoring water this time */ + tr = gi.trace(water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + /* send gun puff / flash */ + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, + damage, kick, DAMAGE_BULLET, MOD_BLASTOFF); + } + else + { + if (strncmp(tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(te_impact); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + gi.multicast(tr.endpos, MULTICAST_PVS); + + if (self->client) + { + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + } + + /* if went through water, determine where + the end and make a bubble trail */ + if (water) + { + vec3_t pos; + + VectorSubtract(tr.endpos, water_start, dir); + VectorNormalize(dir); + VectorMA(tr.endpos, -2, dir, pos); + + if (gi.pointcontents(pos) & MASK_WATER) + { + VectorCopy(pos, tr.endpos); + } + else + { + tr = gi.trace(pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + } + + VectorAdd(water_start, tr.endpos, pos); + VectorScale(pos, 0.5, pos); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BUBBLETRAIL); + gi.WritePosition(water_start); + gi.WritePosition(tr.endpos); + gi.multicast(pos, MULTICAST_PVS); + } +} + +void +fly_vertical(edict_t *self) +{ + int i; + vec3_t v; + vec3_t forward, right, up; + vec3_t start; + vec3_t tempvec; + + if (!self) + { + return; + } + + VectorSubtract(self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw(self); + + if ((self->s.frame == FRAME_landing_58) || + (self->s.frame == FRAME_takeoff_16)) + { + self->goalentity->nextthink = level.time + 0.1; + self->goalentity->think = G_FreeEdict; + self->monsterinfo.currentmove = &fixbot_move_stand; + self->goalentity = self->enemy = NULL; + } + + /* kick up some particles */ + VectorCopy(self->s.angles, tempvec); + tempvec[PITCH] += 90; + + AngleVectors(tempvec, forward, right, up); + VectorCopy(self->s.origin, start); + + for (i = 0; i < 10; i++) + { + blastoff(self, start, forward, 2, 1, TE_SHOTGUN, + DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD); + } +} + +void +fly_vertical2(edict_t *self) +{ + vec3_t v; + int len; + + if (!self) + { + return; + } + + VectorSubtract(self->goalentity->s.origin, self->s.origin, v); + len = VectorLength(v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw(self); + + if (len < 32) + { + self->goalentity->nextthink = level.time + 0.1; + self->goalentity->think = G_FreeEdict; + self->monsterinfo.currentmove = &fixbot_move_stand; + self->goalentity = self->enemy = NULL; + } +} + +static mframe_t fixbot_frames_landing[] = { + {ai_move, 0, NULL}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2}, + {ai_move, 0, fly_vertical2} +}; + +mmove_t fixbot_move_landing = { + FRAME_landing_01, + FRAME_landing_58, + fixbot_frames_landing, + NULL +}; + +/* generic ambient stand */ +static mframe_t fixbot_frames_stand[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, change_to_roam} +}; + +mmove_t fixbot_move_stand = { + FRAME_ambient_01, + FRAME_ambient_19, + fixbot_frames_stand, + NULL +}; + +static mframe_t fixbot_frames_stand2[] = { + {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 fixbot_move_stand2 = { + FRAME_ambient_01, + FRAME_ambient_19, + fixbot_frames_stand2, + NULL +}; + +/* + * will need the pickup offset for the front pincers + * object will need to stop forward of the object + * and take the object with it ( this may require a + * variant of liftoff and landing ) + */ +static mframe_t fixbot_frames_pickup[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t fixbot_move_pickup = { + FRAME_pickup_01, + FRAME_pickup_27, + fixbot_frames_pickup, + NULL +}; + +/* generic frame to move bot */ +static mframe_t fixbot_frames_roamgoal[] = { + {ai_move, 0, roam_goal} +}; + +mmove_t fixbot_move_roamgoal = { + FRAME_freeze_01, + FRAME_freeze_01, + fixbot_frames_roamgoal, + NULL +}; + +void +ai_facing(edict_t *self, float dist) +{ + vec3_t v; + + if (!self) + { + return; + } + + if (infront(self, self->goalentity)) + { + self->monsterinfo.currentmove = &fixbot_move_forward; + } + else + { + VectorSubtract(self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw(self); + } +} + +static mframe_t fixbot_frames_turn[] = { + {ai_facing, 0, NULL} +}; + +mmove_t fixbot_move_turn = { + FRAME_freeze_01, + FRAME_freeze_01, + fixbot_frames_turn, + NULL +}; + +void +go_roam(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &fixbot_move_stand; +} + +/* takeoff */ +static mframe_t fixbot_frames_takeoff[] = { + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical}, + {ai_move, 0.01, fly_vertical} +}; + +mmove_t fixbot_move_takeoff = { + FRAME_takeoff_01, + FRAME_takeoff_16, + fixbot_frames_takeoff, + NULL +}; + +/* findout what this is */ +static mframe_t fixbot_frames_paina[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t fixbot_move_paina = { + FRAME_paina_01, + FRAME_paina_06, + fixbot_frames_paina, + fixbot_run +}; + +/* findout what this is */ +static mframe_t fixbot_frames_painb[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t fixbot_move_painb = { + FRAME_painb_01, + FRAME_painb_08, + fixbot_frames_painb, + fixbot_run +}; + +/* + * backup from pain + * call a generic painsound + * some spark effects + */ +static mframe_t fixbot_frames_pain3[] = { + {ai_move, -1, NULL} +}; + +mmove_t fixbot_move_pain3 = { + FRAME_freeze_01, + FRAME_freeze_01, + fixbot_frames_pain3, + fixbot_run +}; + +/* + * bot has compleated landing + * and is now on the grownd + * ( may need second land if the + * bot is releasing jib into jib vat ) + */ +static mframe_t fixbot_frames_land[] = { + {ai_move, 0, NULL} +}; + +mmove_t fixbot_move_land = { + FRAME_freeze_01, + FRAME_freeze_01, + fixbot_frames_land, + NULL +}; + +void +ai_movetogoal(edict_t *self, float dist) +{ + if (!self) + { + return; + } + + M_MoveToGoal(self, dist); +} + +static mframe_t fixbot_frames_forward[] = { + {ai_movetogoal, 5, use_scanner} +}; + +mmove_t fixbot_move_forward = { + FRAME_freeze_01, + FRAME_freeze_01, + fixbot_frames_forward, + NULL +}; + +static mframe_t fixbot_frames_walk[] = { + {ai_walk, 5, NULL} +}; + +mmove_t fixbot_move_walk = { + FRAME_freeze_01, + FRAME_freeze_01, + fixbot_frames_walk, + NULL +}; + +static mframe_t fixbot_frames_run[] = { + {ai_run, 10, NULL} +}; + +mmove_t fixbot_move_run = { + FRAME_freeze_01, + FRAME_freeze_01, + fixbot_frames_run, + NULL +}; + +static mframe_t fixbot_frames_death1[] = { + {ai_move, 0, NULL} + +}; +mmove_t fixbot_move_death1 = { + FRAME_freeze_01, + FRAME_freeze_01, + fixbot_frames_death1, + fixbot_dead +}; + +static mframe_t fixbot_frames_backward[] = { + {ai_move, 0, NULL} +}; + +mmove_t fixbot_move_backward = { + FRAME_freeze_01, + FRAME_freeze_01, + fixbot_frames_backward, + NULL +}; + +static mframe_t fixbot_frames_start_attack[] = { + {ai_charge, 0, NULL} +}; + +mmove_t fixbot_move_start_attack = { + FRAME_freeze_01, + FRAME_freeze_01, + fixbot_frames_start_attack, + fixbot_attack +}; + +static mframe_t fixbot_frames_attack1[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, -10, fixbot_fire_blaster} +}; + +mmove_t fixbot_move_attack1 = { + FRAME_shoot_01, + FRAME_shoot_06, + fixbot_frames_attack1, + NULL +}; + +int +check_telefrag(edict_t *self) +{ + vec3_t end, up; + trace_t tr; + + if (!self) + { + return 0; + } + + AngleVectors(self->enemy->s.angles, NULL, NULL, up); + VectorMA(self->enemy->s.origin, 48, up, end); + + tr = gi.trace(self->enemy->s.origin, self->enemy->mins, self->enemy->maxs, + end, self, MASK_MONSTERSOLID); + + if (tr.ent && tr.ent->takedamage) + { + tr.ent->health = 0; + + T_Damage(tr.ent, self, self, + vec3_origin, vec3_origin, vec3_origin, + 10000, 0, 0, MOD_UNKNOWN); + + return 0; + } + + return 1; +} + +void +fixbot_fire_laser(edict_t *self) +{ + vec3_t forward, right, up; + vec3_t tempang, start; + vec3_t dir, angles, end; + edict_t *ent; + + if (!self) + { + return; + } + + /* critter dun got blown up while bein' fixed */ + if (self->enemy->health <= self->enemy->gib_health) + { + self->monsterinfo.currentmove = &fixbot_move_stand; + self->monsterinfo.aiflags &= ~AI_MEDIC; + return; + } + + gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), + 1, ATTN_STATIC, 0); + + VectorCopy(self->s.origin, start); + VectorCopy(self->enemy->s.origin, end); + VectorSubtract(end, start, dir); + vectoangles(dir, angles); + + ent = G_Spawn(); + VectorCopy(self->s.origin, ent->s.origin); + VectorCopy(angles, tempang); + AngleVectors(tempang, forward, right, up); + VectorCopy(tempang, ent->s.angles); + VectorCopy(ent->s.origin, start); + + VectorMA(start, 16, forward, start); + + VectorCopy(start, ent->s.origin); + ent->enemy = self->enemy; + ent->owner = self; + ent->dmg = -1; + monster_dabeam(ent); + + if (self->enemy->health > (self->enemy->mass / 10)) + { + if (check_telefrag(self)) + { + self->enemy->spawnflags = 0; + self->enemy->monsterinfo.aiflags = 0; + self->enemy->target = NULL; + self->enemy->targetname = NULL; + self->enemy->combattarget = NULL; + self->enemy->deathtarget = NULL; + self->enemy->owner = self; + ED_CallSpawn(self->enemy); + self->enemy->owner = NULL; + self->s.origin[2] += 1; + + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; + + self->monsterinfo.currentmove = &fixbot_move_stand; + self->monsterinfo.aiflags &= ~AI_MEDIC; + } + } + else + { + self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; + } +} + +static mframe_t fixbot_frames_laserattack[] = { + {ai_charge, 0, fixbot_fire_laser}, + {ai_charge, 0, fixbot_fire_laser}, + {ai_charge, 0, fixbot_fire_laser}, + {ai_charge, 0, fixbot_fire_laser}, + {ai_charge, 0, fixbot_fire_laser}, + {ai_charge, 0, fixbot_fire_laser} +}; + +mmove_t fixbot_move_laserattack = { + FRAME_shoot_01, + FRAME_shoot_06, + fixbot_frames_laserattack, + NULL +}; + +/* need to get forward translation + data for the charge attack */ +static mframe_t fixbot_frames_attack2[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, -10, NULL}, + {ai_charge, -10, NULL}, + {ai_charge, -10, NULL}, + {ai_charge, -10, NULL}, + {ai_charge, -10, NULL}, + {ai_charge, -10, NULL}, + {ai_charge, -10, NULL}, + {ai_charge, -10, NULL}, + {ai_charge, -10, NULL}, + {ai_charge, -10, NULL}, + + {ai_charge, 0, fixbot_fire_blaster}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL} +}; + +mmove_t fixbot_move_attack2 = { + FRAME_charging_01, + FRAME_charging_31, + fixbot_frames_attack2, + fixbot_run +}; + +void +weldstate(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.frame == FRAME_weldstart_10) + { + self->monsterinfo.currentmove = &fixbot_move_weld; + } + else if (self->s.frame == FRAME_weldmiddle_07) + { + if (self->goalentity->health < 0) + { + self->enemy->owner = NULL; + self->monsterinfo.currentmove = &fixbot_move_weld_end; + } + else + { + self->goalentity->health -= 10; + } + } + else + { + self->goalentity = self->enemy = NULL; + self->monsterinfo.currentmove = &fixbot_move_stand; + } +} + +void +ai_move2(edict_t *self, float dist) +{ + vec3_t v; + + if (!self) + { + return; + } + + if (dist) + { + M_walkmove(self, self->s.angles[YAW], dist); + } + + VectorSubtract(self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw(self); +} + +static mframe_t fixbot_frames_weld_start[] = { + {ai_move2, 0, NULL}, + {ai_move2, 0, NULL}, + {ai_move2, 0, NULL}, + {ai_move2, 0, NULL}, + {ai_move2, 0, NULL}, + {ai_move2, 0, NULL}, + {ai_move2, 0, NULL}, + {ai_move2, 0, NULL}, + {ai_move2, 0, NULL}, + {ai_move2, 0, weldstate} +}; + +mmove_t fixbot_move_weld_start = { + FRAME_weldstart_01, + FRAME_weldstart_10, + fixbot_frames_weld_start, + NULL +}; + +static mframe_t fixbot_frames_weld[] = { + {ai_move2, 0, fixbot_fire_welder}, + {ai_move2, 0, fixbot_fire_welder}, + {ai_move2, 0, fixbot_fire_welder}, + {ai_move2, 0, fixbot_fire_welder}, + {ai_move2, 0, fixbot_fire_welder}, + {ai_move2, 0, fixbot_fire_welder}, + {ai_move2, 0, weldstate} +}; + +mmove_t fixbot_move_weld = { + FRAME_weldmiddle_01, + FRAME_weldmiddle_07, + fixbot_frames_weld, + NULL +}; + +static mframe_t fixbot_frames_weld_end[] = { + {ai_move2, -2, NULL}, + {ai_move2, -2, NULL}, + {ai_move2, -2, NULL}, + {ai_move2, -2, NULL}, + {ai_move2, -2, NULL}, + {ai_move2, -2, NULL}, + {ai_move2, -2, weldstate} +}; + +mmove_t fixbot_move_weld_end = { + FRAME_weldend_01, + FRAME_weldend_07, + fixbot_frames_weld_end, + NULL +}; + +void +fixbot_fire_welder(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t vec; + float r; + + if (!self) + { + return; + } + + if (!self->enemy) + { + return; + } + + vec[0] = 24.0; + vec[1] = -0.8; + vec[2] = -10.0; + + AngleVectors(self->s.angles, forward, right, up); + G_ProjectSource(self->s.origin, vec, forward, right, start); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(10); + gi.WritePosition(start); + gi.WriteDir(vec3_origin); + gi.WriteByte(0xe0 + (rand() & 7)); + gi.multicast(self->s.origin, MULTICAST_PVS); + + if (random() > 0.8) + { + r = random(); + + if (r < 0.33) + { + gi.sound(self, CHAN_VOICE, sound_weld1, 1, ATTN_IDLE, 0); + } + else if (r < 0.66) + { + gi.sound(self, CHAN_VOICE, sound_weld2, 1, ATTN_IDLE, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_weld3, 1, ATTN_IDLE, 0); + } + } +} + +void +fixbot_fire_blaster(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t end; + vec3_t dir; + + if (!self) + { + return; + } + + if (!visible(self, self->enemy)) + { + self->monsterinfo.currentmove = &fixbot_move_run; + } + + AngleVectors(self->s.angles, forward, right, up); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_fixbot_BLASTER_1], + forward, right, start); + + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, dir); + + monster_fire_blaster(self, start, dir, 15, 1000, MZ2_fixbot_BLASTER_1, + EF_BLASTER); +} + +void +fixbot_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &fixbot_move_stand; +} + +void +fixbot_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &fixbot_move_stand; + } + else + { + self->monsterinfo.currentmove = &fixbot_move_run; + } +} + +void +fixbot_walk(edict_t *self) +{ + vec3_t vec; + int len; + + if (!self) + { + return; + } + + if (strcmp(self->goalentity->classname, "object_repair") == 0) + { + VectorSubtract(self->s.origin, self->goalentity->s.origin, vec); + len = VectorLength(vec); + + if (len < 32) + { + self->monsterinfo.currentmove = &fixbot_move_weld_start; + return; + } + } + + self->monsterinfo.currentmove = &fixbot_move_walk; +} + +void +fixbot_start_attack(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &fixbot_move_start_attack; +} + +void +fixbot_attack(edict_t *self) +{ + vec3_t vec; + int len; + + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (!visible(self, self->goalentity)) + { + return; + } + + VectorSubtract(self->s.origin, self->enemy->s.origin, vec); + len = VectorLength(vec); + + if (len > 128) + { + return; + } + else + { + self->monsterinfo.currentmove = &fixbot_move_laserattack; + } + } + else + { + self->monsterinfo.currentmove = &fixbot_move_attack2; + } +} + +void +fixbot_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) +{ + if (!self) + { + return; + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 3; + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + + if (damage <= 10) + { + self->monsterinfo.currentmove = &fixbot_move_pain3; + } + else if (damage <= 25) + { + self->monsterinfo.currentmove = &fixbot_move_painb; + } + else + { + self->monsterinfo.currentmove = &fixbot_move_paina; + } +} + +void +fixbot_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +void +fixbot_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage /* unused */, vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + BecomeExplosion1(self); +} + +/* + * QUAKED monster_fixbot (1 .5 0) (-32 -32 -24) (32 32 24) Ambush Trigger_Spawn Fixit Takeoff Landing + */ +void +SP_monster_fixbot(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("flyer/flypain1.wav"); + sound_die = gi.soundindex("flyer/flydeth1.wav"); + + sound_weld1 = gi.soundindex("misc/welder1.wav"); + sound_weld2 = gi.soundindex("misc/welder2.wav"); + sound_weld3 = gi.soundindex("misc/welder3.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/fixbot/tris.md2"); + + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, 24); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 150; + self->mass = 150; + self->viewheight = 16; + + self->pain = fixbot_pain; + self->die = fixbot_die; + + self->monsterinfo.stand = fixbot_stand; + self->monsterinfo.walk = fixbot_walk; + self->monsterinfo.run = fixbot_run; + self->monsterinfo.attack = fixbot_attack; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &fixbot_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start(self); +} diff --git a/src/game/monster/fixbot/fixbot.h b/src/game/monster/fixbot/fixbot.h new file mode 100644 index 000000000..9a8897f26 --- /dev/null +++ b/src/game/monster/fixbot/fixbot.h @@ -0,0 +1,241 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Fixbot animations + * + * ======================================================================= + */ + +#define FRAME_charging_01 0 +#define FRAME_charging_02 1 +#define FRAME_charging_03 2 +#define FRAME_charging_04 3 +#define FRAME_charging_05 4 +#define FRAME_charging_06 5 +#define FRAME_charging_07 6 +#define FRAME_charging_08 7 +#define FRAME_charging_09 8 +#define FRAME_charging_10 9 +#define FRAME_charging_11 10 +#define FRAME_charging_12 11 +#define FRAME_charging_13 12 +#define FRAME_charging_14 13 +#define FRAME_charging_15 14 +#define FRAME_charging_16 15 +#define FRAME_charging_17 16 +#define FRAME_charging_18 17 +#define FRAME_charging_19 18 +#define FRAME_charging_20 19 +#define FRAME_charging_21 20 +#define FRAME_charging_22 21 +#define FRAME_charging_23 22 +#define FRAME_charging_24 23 +#define FRAME_charging_25 24 +#define FRAME_charging_26 25 +#define FRAME_charging_27 26 +#define FRAME_charging_28 27 +#define FRAME_charging_29 28 +#define FRAME_charging_30 29 +#define FRAME_charging_31 30 +#define FRAME_landing_01 31 +#define FRAME_landing_02 32 +#define FRAME_landing_03 33 +#define FRAME_landing_04 34 +#define FRAME_landing_05 35 +#define FRAME_landing_06 36 +#define FRAME_landing_07 37 +#define FRAME_landing_08 38 +#define FRAME_landing_09 39 +#define FRAME_landing_10 40 +#define FRAME_landing_11 41 +#define FRAME_landing_12 42 +#define FRAME_landing_13 43 +#define FRAME_landing_14 44 +#define FRAME_landing_15 45 +#define FRAME_landing_16 46 +#define FRAME_landing_17 47 +#define FRAME_landing_18 48 +#define FRAME_landing_19 49 +#define FRAME_landing_20 50 +#define FRAME_landing_21 51 +#define FRAME_landing_22 52 +#define FRAME_landing_23 53 +#define FRAME_landing_24 54 +#define FRAME_landing_25 55 +#define FRAME_landing_26 56 +#define FRAME_landing_27 57 +#define FRAME_landing_28 58 +#define FRAME_landing_29 59 +#define FRAME_landing_30 60 +#define FRAME_landing_31 61 +#define FRAME_landing_32 62 +#define FRAME_landing_33 63 +#define FRAME_landing_34 64 +#define FRAME_landing_35 65 +#define FRAME_landing_36 66 +#define FRAME_landing_37 67 +#define FRAME_landing_38 68 +#define FRAME_landing_39 69 +#define FRAME_landing_40 70 +#define FRAME_landing_41 71 +#define FRAME_landing_42 72 +#define FRAME_landing_43 73 +#define FRAME_landing_44 74 +#define FRAME_landing_45 75 +#define FRAME_landing_46 76 +#define FRAME_landing_47 77 +#define FRAME_landing_48 78 +#define FRAME_landing_49 79 +#define FRAME_landing_50 80 +#define FRAME_landing_51 81 +#define FRAME_landing_52 82 +#define FRAME_landing_53 83 +#define FRAME_landing_54 84 +#define FRAME_landing_55 85 +#define FRAME_landing_56 86 +#define FRAME_landing_57 87 +#define FRAME_landing_58 88 +#define FRAME_pushback_01 89 +#define FRAME_pushback_02 90 +#define FRAME_pushback_03 91 +#define FRAME_pushback_04 92 +#define FRAME_pushback_05 93 +#define FRAME_pushback_06 94 +#define FRAME_pushback_07 95 +#define FRAME_pushback_08 96 +#define FRAME_pushback_09 97 +#define FRAME_pushback_10 98 +#define FRAME_pushback_11 99 +#define FRAME_pushback_12 100 +#define FRAME_pushback_13 101 +#define FRAME_pushback_14 102 +#define FRAME_pushback_15 103 +#define FRAME_pushback_16 104 +#define FRAME_takeoff_01 105 +#define FRAME_takeoff_02 106 +#define FRAME_takeoff_03 107 +#define FRAME_takeoff_04 108 +#define FRAME_takeoff_05 109 +#define FRAME_takeoff_06 110 +#define FRAME_takeoff_07 111 +#define FRAME_takeoff_08 112 +#define FRAME_takeoff_09 113 +#define FRAME_takeoff_10 114 +#define FRAME_takeoff_11 115 +#define FRAME_takeoff_12 116 +#define FRAME_takeoff_13 117 +#define FRAME_takeoff_14 118 +#define FRAME_takeoff_15 119 +#define FRAME_takeoff_16 120 +#define FRAME_ambient_01 121 +#define FRAME_ambient_02 122 +#define FRAME_ambient_03 123 +#define FRAME_ambient_04 124 +#define FRAME_ambient_05 125 +#define FRAME_ambient_06 126 +#define FRAME_ambient_07 127 +#define FRAME_ambient_08 128 +#define FRAME_ambient_09 129 +#define FRAME_ambient_10 130 +#define FRAME_ambient_11 131 +#define FRAME_ambient_12 132 +#define FRAME_ambient_13 133 +#define FRAME_ambient_14 134 +#define FRAME_ambient_15 135 +#define FRAME_ambient_16 136 +#define FRAME_ambient_17 137 +#define FRAME_ambient_18 138 +#define FRAME_ambient_19 139 +#define FRAME_paina_01 140 +#define FRAME_paina_02 141 +#define FRAME_paina_03 142 +#define FRAME_paina_04 143 +#define FRAME_paina_05 144 +#define FRAME_paina_06 145 +#define FRAME_painb_01 146 +#define FRAME_painb_02 147 +#define FRAME_painb_03 148 +#define FRAME_painb_04 149 +#define FRAME_painb_05 150 +#define FRAME_painb_06 151 +#define FRAME_painb_07 152 +#define FRAME_painb_08 153 +#define FRAME_pickup_01 154 +#define FRAME_pickup_02 155 +#define FRAME_pickup_03 156 +#define FRAME_pickup_04 157 +#define FRAME_pickup_05 158 +#define FRAME_pickup_06 159 +#define FRAME_pickup_07 160 +#define FRAME_pickup_08 161 +#define FRAME_pickup_09 162 +#define FRAME_pickup_10 163 +#define FRAME_pickup_11 164 +#define FRAME_pickup_12 165 +#define FRAME_pickup_13 166 +#define FRAME_pickup_14 167 +#define FRAME_pickup_15 168 +#define FRAME_pickup_16 169 +#define FRAME_pickup_17 170 +#define FRAME_pickup_18 171 +#define FRAME_pickup_19 172 +#define FRAME_pickup_20 173 +#define FRAME_pickup_21 174 +#define FRAME_pickup_22 175 +#define FRAME_pickup_23 176 +#define FRAME_pickup_24 177 +#define FRAME_pickup_25 178 +#define FRAME_pickup_26 179 +#define FRAME_pickup_27 180 +#define FRAME_freeze_01 181 +#define FRAME_shoot_01 182 +#define FRAME_shoot_02 183 +#define FRAME_shoot_03 184 +#define FRAME_shoot_04 185 +#define FRAME_shoot_05 186 +#define FRAME_shoot_06 187 +#define FRAME_weldstart_01 188 +#define FRAME_weldstart_02 189 +#define FRAME_weldstart_03 190 +#define FRAME_weldstart_04 191 +#define FRAME_weldstart_05 192 +#define FRAME_weldstart_06 193 +#define FRAME_weldstart_07 194 +#define FRAME_weldstart_08 195 +#define FRAME_weldstart_09 196 +#define FRAME_weldstart_10 197 +#define FRAME_weldmiddle_01 198 +#define FRAME_weldmiddle_02 199 +#define FRAME_weldmiddle_03 200 +#define FRAME_weldmiddle_04 201 +#define FRAME_weldmiddle_05 202 +#define FRAME_weldmiddle_06 203 +#define FRAME_weldmiddle_07 204 +#define FRAME_weldend_01 205 +#define FRAME_weldend_02 206 +#define FRAME_weldend_03 207 +#define FRAME_weldend_04 208 +#define FRAME_weldend_05 209 +#define FRAME_weldend_06 210 +#define FRAME_weldend_07 211 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/flipper/flipper.c b/src/game/monster/flipper/flipper.c new file mode 100644 index 000000000..c974efdd5 --- /dev/null +++ b/src/game/monster/flipper/flipper.c @@ -0,0 +1,566 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Baracuda Shark. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "flipper.h" + +#define FLIPPER_RUN_SPEED 24 + +static int sound_chomp; +static int sound_attack; +static int sound_pain1; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_search; +static int sound_sight; + +void flipper_stand(edict_t *self); + +static mframe_t flipper_frames_stand[] = { + {ai_stand, 0, NULL} +}; + +mmove_t flipper_move_stand = +{ + FRAME_flphor01, + FRAME_flphor01, + flipper_frames_stand, + NULL +}; + +void +flipper_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &flipper_move_stand; +} + +static mframe_t flipper_frames_run[] = { + {ai_run, FLIPPER_RUN_SPEED, NULL}, /* 6 */ + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, /* 10 */ + + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, /* 20 */ + + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL}, + {ai_run, FLIPPER_RUN_SPEED, NULL} /* 29 */ +}; + +mmove_t flipper_move_run_loop = +{ + FRAME_flpver06, + FRAME_flpver29, + flipper_frames_run, + NULL +}; + +void +flipper_run_loop(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &flipper_move_run_loop; +} + +static mframe_t flipper_frames_run_start[] = { + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL} +}; + +mmove_t flipper_move_run_start = +{ + FRAME_flpver01, + FRAME_flpver06, + flipper_frames_run_start, + flipper_run_loop +}; + +void +flipper_run(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &flipper_move_run_start; +} + +/* Standard Swimming */ +static mframe_t flipper_frames_walk[] = { + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL} +}; + +mmove_t flipper_move_walk = +{ + FRAME_flphor01, + FRAME_flphor24, + flipper_frames_walk, + NULL +}; + +void +flipper_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &flipper_move_walk; +} + +static mframe_t flipper_frames_start_run[] = { + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, flipper_run} +}; + +mmove_t flipper_move_start_run = +{ + FRAME_flphor01, + FRAME_flphor05, + flipper_frames_start_run, + NULL +}; + +void +flipper_start_run(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &flipper_move_start_run; +} + +static mframe_t flipper_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flipper_move_pain2 = +{ + FRAME_flppn101, + FRAME_flppn105, + flipper_frames_pain2, + flipper_run +}; + +static mframe_t flipper_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flipper_move_pain1 = +{ + FRAME_flppn201, + FRAME_flppn205, + flipper_frames_pain1, + flipper_run +}; + +void +flipper_bite(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, 0, 0); + fire_hit(self, aim, 5, 0); +} + +void +flipper_preattack(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_chomp, 1, ATTN_NORM, 0); +} + +static mframe_t flipper_frames_attack[] = { + {ai_charge, 0, flipper_preattack}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, flipper_bite}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, flipper_bite}, + {ai_charge, 0, NULL} +}; + +mmove_t flipper_move_attack = +{ + FRAME_flpbit01, + FRAME_flpbit20, + flipper_frames_attack, + flipper_run +}; + +void +flipper_melee(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &flipper_move_attack; +} + +void +flipper_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + int n; + + if (!self) + { + return; + } + + 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; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + n = (randk() + 1) % 2; + + if (n == 0) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flipper_move_pain1; + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flipper_move_pain2; + } +} + +void +flipper_dead(edict_t *self) +{ + vec3_t p; + trace_t tr; + + if (!self) + { + return; + } + + /* original dead bbox was wrong - and make sure the bbox adjustment stays in solidity */ + + p[0] = self->s.origin[0]; + p[1] = self->s.origin[1]; + p[2] = self->s.origin[2] - 8; + + tr = gi.trace(self->s.origin, self->mins, self->maxs, p, self, self->clipmask); + + self->mins[2] = tr.endpos[2] - self->s.origin[2]; + + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +static mframe_t flipper_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flipper_move_death = +{ + FRAME_flpdth01, + FRAME_flpdth56, + flipper_frames_death, + flipper_dead +}; + +void +flipper_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +flipper_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage, vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), + 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 < 2; n++) + { + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", + damage, GIB_ORGANIC); + } + + ThrowHead(self, "models/objects/gibs/sm_meat/tris.md2", + damage, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + { + return; + } + + /* regular death */ + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &flipper_move_death; +} + +/* + * QUAKED monster_flipper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_flipper(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("flipper/flppain1.wav"); + sound_pain2 = gi.soundindex("flipper/flppain2.wav"); + sound_death = gi.soundindex("flipper/flpdeth1.wav"); + sound_chomp = gi.soundindex("flipper/flpatck1.wav"); + sound_attack = gi.soundindex("flipper/flpatck2.wav"); + sound_idle = gi.soundindex("flipper/flpidle1.wav"); + sound_search = gi.soundindex("flipper/flpsrch1.wav"); + sound_sight = gi.soundindex("flipper/flpsght1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/flipper/tris.md2"); + VectorSet(self->mins, -16, -16, 0); + VectorSet(self->maxs, 16, 16, 32); + + self->health = 50; + self->gib_health = -30; + self->mass = 100; + + self->pain = flipper_pain; + self->die = flipper_die; + + self->monsterinfo.stand = flipper_stand; + self->monsterinfo.walk = flipper_walk; + self->monsterinfo.run = flipper_start_run; + self->monsterinfo.melee = flipper_melee; + self->monsterinfo.sight = flipper_sight; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &flipper_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + swimmonster_start(self); +} diff --git a/src/game/monster/flipper/flipper.h b/src/game/monster/flipper/flipper.h new file mode 100644 index 000000000..fe9ea299f --- /dev/null +++ b/src/game/monster/flipper/flipper.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Baracuda Shark animations. + * + * ======================================================================= + */ + +#define FRAME_flpbit01 0 +#define FRAME_flpbit02 1 +#define FRAME_flpbit03 2 +#define FRAME_flpbit04 3 +#define FRAME_flpbit05 4 +#define FRAME_flpbit06 5 +#define FRAME_flpbit07 6 +#define FRAME_flpbit08 7 +#define FRAME_flpbit09 8 +#define FRAME_flpbit10 9 +#define FRAME_flpbit11 10 +#define FRAME_flpbit12 11 +#define FRAME_flpbit13 12 +#define FRAME_flpbit14 13 +#define FRAME_flpbit15 14 +#define FRAME_flpbit16 15 +#define FRAME_flpbit17 16 +#define FRAME_flpbit18 17 +#define FRAME_flpbit19 18 +#define FRAME_flpbit20 19 +#define FRAME_flptal01 20 +#define FRAME_flptal02 21 +#define FRAME_flptal03 22 +#define FRAME_flptal04 23 +#define FRAME_flptal05 24 +#define FRAME_flptal06 25 +#define FRAME_flptal07 26 +#define FRAME_flptal08 27 +#define FRAME_flptal09 28 +#define FRAME_flptal10 29 +#define FRAME_flptal11 30 +#define FRAME_flptal12 31 +#define FRAME_flptal13 32 +#define FRAME_flptal14 33 +#define FRAME_flptal15 34 +#define FRAME_flptal16 35 +#define FRAME_flptal17 36 +#define FRAME_flptal18 37 +#define FRAME_flptal19 38 +#define FRAME_flptal20 39 +#define FRAME_flptal21 40 +#define FRAME_flphor01 41 +#define FRAME_flphor02 42 +#define FRAME_flphor03 43 +#define FRAME_flphor04 44 +#define FRAME_flphor05 45 +#define FRAME_flphor06 46 +#define FRAME_flphor07 47 +#define FRAME_flphor08 48 +#define FRAME_flphor09 49 +#define FRAME_flphor10 50 +#define FRAME_flphor11 51 +#define FRAME_flphor12 52 +#define FRAME_flphor13 53 +#define FRAME_flphor14 54 +#define FRAME_flphor15 55 +#define FRAME_flphor16 56 +#define FRAME_flphor17 57 +#define FRAME_flphor18 58 +#define FRAME_flphor19 59 +#define FRAME_flphor20 60 +#define FRAME_flphor21 61 +#define FRAME_flphor22 62 +#define FRAME_flphor23 63 +#define FRAME_flphor24 64 +#define FRAME_flpver01 65 +#define FRAME_flpver02 66 +#define FRAME_flpver03 67 +#define FRAME_flpver04 68 +#define FRAME_flpver05 69 +#define FRAME_flpver06 70 +#define FRAME_flpver07 71 +#define FRAME_flpver08 72 +#define FRAME_flpver09 73 +#define FRAME_flpver10 74 +#define FRAME_flpver11 75 +#define FRAME_flpver12 76 +#define FRAME_flpver13 77 +#define FRAME_flpver14 78 +#define FRAME_flpver15 79 +#define FRAME_flpver16 80 +#define FRAME_flpver17 81 +#define FRAME_flpver18 82 +#define FRAME_flpver19 83 +#define FRAME_flpver20 84 +#define FRAME_flpver21 85 +#define FRAME_flpver22 86 +#define FRAME_flpver23 87 +#define FRAME_flpver24 88 +#define FRAME_flpver25 89 +#define FRAME_flpver26 90 +#define FRAME_flpver27 91 +#define FRAME_flpver28 92 +#define FRAME_flpver29 93 +#define FRAME_flppn101 94 +#define FRAME_flppn102 95 +#define FRAME_flppn103 96 +#define FRAME_flppn104 97 +#define FRAME_flppn105 98 +#define FRAME_flppn201 99 +#define FRAME_flppn202 100 +#define FRAME_flppn203 101 +#define FRAME_flppn204 102 +#define FRAME_flppn205 103 +#define FRAME_flpdth01 104 +#define FRAME_flpdth02 105 +#define FRAME_flpdth03 106 +#define FRAME_flpdth04 107 +#define FRAME_flpdth05 108 +#define FRAME_flpdth06 109 +#define FRAME_flpdth07 110 +#define FRAME_flpdth08 111 +#define FRAME_flpdth09 112 +#define FRAME_flpdth10 113 +#define FRAME_flpdth11 114 +#define FRAME_flpdth12 115 +#define FRAME_flpdth13 116 +#define FRAME_flpdth14 117 +#define FRAME_flpdth15 118 +#define FRAME_flpdth16 119 +#define FRAME_flpdth17 120 +#define FRAME_flpdth18 121 +#define FRAME_flpdth19 122 +#define FRAME_flpdth20 123 +#define FRAME_flpdth21 124 +#define FRAME_flpdth22 125 +#define FRAME_flpdth23 126 +#define FRAME_flpdth24 127 +#define FRAME_flpdth25 128 +#define FRAME_flpdth26 129 +#define FRAME_flpdth27 130 +#define FRAME_flpdth28 131 +#define FRAME_flpdth29 132 +#define FRAME_flpdth30 133 +#define FRAME_flpdth31 134 +#define FRAME_flpdth32 135 +#define FRAME_flpdth33 136 +#define FRAME_flpdth34 137 +#define FRAME_flpdth35 138 +#define FRAME_flpdth36 139 +#define FRAME_flpdth37 140 +#define FRAME_flpdth38 141 +#define FRAME_flpdth39 142 +#define FRAME_flpdth40 143 +#define FRAME_flpdth41 144 +#define FRAME_flpdth42 145 +#define FRAME_flpdth43 146 +#define FRAME_flpdth44 147 +#define FRAME_flpdth45 148 +#define FRAME_flpdth46 149 +#define FRAME_flpdth47 150 +#define FRAME_flpdth48 151 +#define FRAME_flpdth49 152 +#define FRAME_flpdth50 153 +#define FRAME_flpdth51 154 +#define FRAME_flpdth52 155 +#define FRAME_flpdth53 156 +#define FRAME_flpdth54 157 +#define FRAME_flpdth55 158 +#define FRAME_flpdth56 159 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/float/float.c b/src/game/monster/float/float.c new file mode 100644 index 000000000..5ab8fc226 --- /dev/null +++ b/src/game/monster/float/float.c @@ -0,0 +1,910 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Mechanic. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "float.h" + +static int sound_attack2; +static int sound_attack3; +static int sound_death1; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; + +void floater_dead(edict_t *self); +void floater_die(edict_t *self, edict_t *inflictor, edict_t *attacker, + int damage, vec3_t point); +void floater_run(edict_t *self); +void floater_wham(edict_t *self); +void floater_zap(edict_t *self); + +void +floater_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +floater_idle(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void +floater_fire_blaster(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if (!self || !self->enemy || !self->enemy->inuse) + { + return; + } + + if ((self->s.frame == FRAME_attak104) || (self->s.frame == FRAME_attak107)) + { + effect = EF_HYPERBLASTER; + } + else + { + effect = 0; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_FLOAT_BLASTER_1], + forward, right, start); + + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, dir); + + monster_fire_blaster(self, start, dir, 1, 1000, MZ2_FLOAT_BLASTER_1, effect); +} + +static mframe_t floater_frames_stand1[] = { + {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}, + {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 floater_move_stand1 = +{ + FRAME_stand101, + FRAME_stand152, + floater_frames_stand1, + NULL +}; + +static mframe_t floater_frames_stand2[] = { + {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}, + {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 floater_move_stand2 = +{ + FRAME_stand201, + FRAME_stand252, + floater_frames_stand2, + NULL +}; + +void +floater_stand(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() <= 0.5) + { + self->monsterinfo.currentmove = &floater_move_stand1; + } + else + { + self->monsterinfo.currentmove = &floater_move_stand2; + } +} + +static mframe_t floater_frames_activate[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t floater_move_activate = +{ + FRAME_actvat01, + FRAME_actvat31, + floater_frames_activate, + NULL +}; + +static mframe_t floater_frames_attack1[] = { + {ai_charge, 0, NULL}, /* Blaster attack */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, floater_fire_blaster}, /* BOOM (0, -25.8, 32.5) -- LOOP Starts */ + {ai_charge, 0, floater_fire_blaster}, + {ai_charge, 0, floater_fire_blaster}, + {ai_charge, 0, floater_fire_blaster}, + {ai_charge, 0, floater_fire_blaster}, + {ai_charge, 0, floater_fire_blaster}, + {ai_charge, 0, floater_fire_blaster}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} /* -- LOOP Ends */ +}; + +mmove_t floater_move_attack1 = +{ + FRAME_attak101, + FRAME_attak114, + floater_frames_attack1, + floater_run +}; + +/* circle strafe frames */ +static mframe_t floater_frames_attack1a[] = +{ + {ai_charge, 10, NULL}, // Blaster attack + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, floater_fire_blaster}, // BOOM (0, -25.8, 32.5) -- LOOP Starts + {ai_charge, 10, floater_fire_blaster}, + {ai_charge, 10, floater_fire_blaster}, + {ai_charge, 10, floater_fire_blaster}, + {ai_charge, 10, floater_fire_blaster}, + {ai_charge, 10, floater_fire_blaster}, + {ai_charge, 10, floater_fire_blaster}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL} // -- LOOP Ends +}; + +mmove_t floater_move_attack1a = +{ + FRAME_attak101, + FRAME_attak114, + floater_frames_attack1a, + floater_run +}; + +static mframe_t floater_frames_attack2[] = { + {ai_charge, 0, NULL}, /* Claws */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, floater_wham}, /* WHAM (0, -45, 29}.6) -- LOOP Starts */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, /* -- LOOP Ends */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t floater_move_attack2 = +{ + FRAME_attak201, + FRAME_attak225, + floater_frames_attack2, + floater_run +}; + +static mframe_t floater_frames_attack3[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, floater_zap}, /* -- LOOP Starts */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, /* -- LOOP Ends */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t floater_move_attack3 = +{ + FRAME_attak301, + FRAME_attak334, + floater_frames_attack3, + floater_run +}; + +static mframe_t floater_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t floater_move_death = +{ + FRAME_death01, + FRAME_death13, + floater_frames_death, + floater_dead +}; + +static mframe_t floater_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t floater_move_pain1 = +{ + FRAME_pain101, + FRAME_pain107, + floater_frames_pain1, + floater_run +}; + +static mframe_t floater_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t floater_move_pain2 = +{ + FRAME_pain201, + FRAME_pain208, + floater_frames_pain2, + floater_run +}; + +static mframe_t floater_frames_pain3[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t floater_move_pain3 = +{ + FRAME_pain301, + FRAME_pain312, + floater_frames_pain3, + floater_run +}; + +static mframe_t floater_frames_walk[] = { + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL} +}; + +mmove_t floater_move_walk = +{ + FRAME_stand101, + FRAME_stand152, + floater_frames_walk, + NULL +}; + +static mframe_t floater_frames_run[] = { + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL}, + {ai_run, 13, NULL} +}; + +mmove_t floater_move_run = +{ + FRAME_stand101, + FRAME_stand152, + floater_frames_run, + NULL +}; + +void +floater_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &floater_move_stand1; + } + else + { + self->monsterinfo.currentmove = &floater_move_run; + } +} + +void +floater_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &floater_move_walk; +} + +void +floater_wham(edict_t *self) +{ + static vec3_t aim = {MELEE_DISTANCE, 0, 0}; + + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_attack3, 1, ATTN_NORM, 0); + fire_hit(self, aim, 5 + randk() % 6, -50); +} + +void +floater_zap(edict_t *self) +{ + vec3_t forward, right; + vec3_t origin; + vec3_t dir; + vec3_t offset; + + if (!self) + { + return; + } + + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + + AngleVectors(self->s.angles, forward, right, NULL); + VectorSet(offset, 18.5, -0.9, 10); + G_ProjectSource(self->s.origin, offset, forward, right, origin); + + gi.sound(self, CHAN_WEAPON, sound_attack2, 1, ATTN_NORM, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_SPLASH); + gi.WriteByte(32); + gi.WritePosition(origin); + gi.WriteDir(dir); + gi.WriteByte(1); /* sparks */ + gi.multicast(origin, MULTICAST_PVS); + + if (range(self, self->enemy) == RANGE_MELEE && infront(self, self->enemy) && + visible(self, self->enemy)) + { + T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, + vec3_origin, 5 + randk() % 6, -10, DAMAGE_ENERGY, + MOD_UNKNOWN); + } +} + +void +floater_attack(edict_t *self) +{ + float chance; + + if (!self) + { + return; + } + + // 0% chance of circle in easy + // 50% chance in normal + // 75% chance in hard + // 86.67% chance in nightmare + if (skill->value == SKILL_EASY) + { + chance = 0; + } + else + { + chance = 1.0 - (0.5/(float)(skill->value)); + } + + if (random() > chance) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + self->monsterinfo.currentmove = &floater_move_attack1; + } + else // circle strafe + { + if (random () <= 0.5) // switch directions + { + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + } + + self->monsterinfo.attack_state = AS_SLIDING; + self->monsterinfo.currentmove = &floater_move_attack1a; + } +} + +void +floater_melee(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &floater_move_attack3; + } + else + { + self->monsterinfo.currentmove = &floater_move_attack2; + } +} + +void +floater_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + int n; + + if (!self) + { + return; + } + + 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; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + n = (randk() + 1) % 3; + + if (n == 0) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &floater_move_pain1; + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &floater_move_pain2; + } +} + +void +floater_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +void +floater_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage /* unused */, vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + BecomeExplosion1(self); +} + +qboolean +floater_blocked(edict_t *self, float dist) +{ + return false; +} + +/* + * QUAKED monster_floater (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_floater(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_attack2 = gi.soundindex("floater/fltatck2.wav"); + sound_attack3 = gi.soundindex("floater/fltatck3.wav"); + sound_death1 = gi.soundindex("floater/fltdeth1.wav"); + sound_idle = gi.soundindex("floater/fltidle1.wav"); + sound_pain1 = gi.soundindex("floater/fltpain1.wav"); + sound_pain2 = gi.soundindex("floater/fltpain2.wav"); + sound_sight = gi.soundindex("floater/fltsght1.wav"); + + gi.soundindex("floater/fltatck1.wav"); + + self->s.sound = gi.soundindex("floater/fltsrch1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/float/tris.md2"); + VectorSet(self->mins, -24, -24, -24); + VectorSet(self->maxs, 24, 24, 32); + + self->health = 200; + self->gib_health = -80; + self->mass = 300; + + self->pain = floater_pain; + self->die = floater_die; + + self->monsterinfo.stand = floater_stand; + self->monsterinfo.walk = floater_walk; + self->monsterinfo.run = floater_run; + self->monsterinfo.attack = floater_attack; + self->monsterinfo.melee = floater_melee; + self->monsterinfo.sight = floater_sight; + self->monsterinfo.idle = floater_idle; + + gi.linkentity(self); + + if (random() <= 0.5) + { + self->monsterinfo.currentmove = &floater_move_stand1; + } + else + { + self->monsterinfo.currentmove = &floater_move_stand2; + } + + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start(self); +} diff --git a/src/game/monster/float/float.h b/src/game/monster/float/float.h new file mode 100644 index 000000000..ac1b6f434 --- /dev/null +++ b/src/game/monster/float/float.h @@ -0,0 +1,277 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Mechanic animations. + * + * ======================================================================= + */ + +#define FRAME_actvat01 0 +#define FRAME_actvat02 1 +#define FRAME_actvat03 2 +#define FRAME_actvat04 3 +#define FRAME_actvat05 4 +#define FRAME_actvat06 5 +#define FRAME_actvat07 6 +#define FRAME_actvat08 7 +#define FRAME_actvat09 8 +#define FRAME_actvat10 9 +#define FRAME_actvat11 10 +#define FRAME_actvat12 11 +#define FRAME_actvat13 12 +#define FRAME_actvat14 13 +#define FRAME_actvat15 14 +#define FRAME_actvat16 15 +#define FRAME_actvat17 16 +#define FRAME_actvat18 17 +#define FRAME_actvat19 18 +#define FRAME_actvat20 19 +#define FRAME_actvat21 20 +#define FRAME_actvat22 21 +#define FRAME_actvat23 22 +#define FRAME_actvat24 23 +#define FRAME_actvat25 24 +#define FRAME_actvat26 25 +#define FRAME_actvat27 26 +#define FRAME_actvat28 27 +#define FRAME_actvat29 28 +#define FRAME_actvat30 29 +#define FRAME_actvat31 30 +#define FRAME_attak101 31 +#define FRAME_attak102 32 +#define FRAME_attak103 33 +#define FRAME_attak104 34 +#define FRAME_attak105 35 +#define FRAME_attak106 36 +#define FRAME_attak107 37 +#define FRAME_attak108 38 +#define FRAME_attak109 39 +#define FRAME_attak110 40 +#define FRAME_attak111 41 +#define FRAME_attak112 42 +#define FRAME_attak113 43 +#define FRAME_attak114 44 +#define FRAME_attak201 45 +#define FRAME_attak202 46 +#define FRAME_attak203 47 +#define FRAME_attak204 48 +#define FRAME_attak205 49 +#define FRAME_attak206 50 +#define FRAME_attak207 51 +#define FRAME_attak208 52 +#define FRAME_attak209 53 +#define FRAME_attak210 54 +#define FRAME_attak211 55 +#define FRAME_attak212 56 +#define FRAME_attak213 57 +#define FRAME_attak214 58 +#define FRAME_attak215 59 +#define FRAME_attak216 60 +#define FRAME_attak217 61 +#define FRAME_attak218 62 +#define FRAME_attak219 63 +#define FRAME_attak220 64 +#define FRAME_attak221 65 +#define FRAME_attak222 66 +#define FRAME_attak223 67 +#define FRAME_attak224 68 +#define FRAME_attak225 69 +#define FRAME_attak301 70 +#define FRAME_attak302 71 +#define FRAME_attak303 72 +#define FRAME_attak304 73 +#define FRAME_attak305 74 +#define FRAME_attak306 75 +#define FRAME_attak307 76 +#define FRAME_attak308 77 +#define FRAME_attak309 78 +#define FRAME_attak310 79 +#define FRAME_attak311 80 +#define FRAME_attak312 81 +#define FRAME_attak313 82 +#define FRAME_attak314 83 +#define FRAME_attak315 84 +#define FRAME_attak316 85 +#define FRAME_attak317 86 +#define FRAME_attak318 87 +#define FRAME_attak319 88 +#define FRAME_attak320 89 +#define FRAME_attak321 90 +#define FRAME_attak322 91 +#define FRAME_attak323 92 +#define FRAME_attak324 93 +#define FRAME_attak325 94 +#define FRAME_attak326 95 +#define FRAME_attak327 96 +#define FRAME_attak328 97 +#define FRAME_attak329 98 +#define FRAME_attak330 99 +#define FRAME_attak331 100 +#define FRAME_attak332 101 +#define FRAME_attak333 102 +#define FRAME_attak334 103 +#define FRAME_death01 104 +#define FRAME_death02 105 +#define FRAME_death03 106 +#define FRAME_death04 107 +#define FRAME_death05 108 +#define FRAME_death06 109 +#define FRAME_death07 110 +#define FRAME_death08 111 +#define FRAME_death09 112 +#define FRAME_death10 113 +#define FRAME_death11 114 +#define FRAME_death12 115 +#define FRAME_death13 116 +#define FRAME_pain101 117 +#define FRAME_pain102 118 +#define FRAME_pain103 119 +#define FRAME_pain104 120 +#define FRAME_pain105 121 +#define FRAME_pain106 122 +#define FRAME_pain107 123 +#define FRAME_pain201 124 +#define FRAME_pain202 125 +#define FRAME_pain203 126 +#define FRAME_pain204 127 +#define FRAME_pain205 128 +#define FRAME_pain206 129 +#define FRAME_pain207 130 +#define FRAME_pain208 131 +#define FRAME_pain301 132 +#define FRAME_pain302 133 +#define FRAME_pain303 134 +#define FRAME_pain304 135 +#define FRAME_pain305 136 +#define FRAME_pain306 137 +#define FRAME_pain307 138 +#define FRAME_pain308 139 +#define FRAME_pain309 140 +#define FRAME_pain310 141 +#define FRAME_pain311 142 +#define FRAME_pain312 143 +#define FRAME_stand101 144 +#define FRAME_stand102 145 +#define FRAME_stand103 146 +#define FRAME_stand104 147 +#define FRAME_stand105 148 +#define FRAME_stand106 149 +#define FRAME_stand107 150 +#define FRAME_stand108 151 +#define FRAME_stand109 152 +#define FRAME_stand110 153 +#define FRAME_stand111 154 +#define FRAME_stand112 155 +#define FRAME_stand113 156 +#define FRAME_stand114 157 +#define FRAME_stand115 158 +#define FRAME_stand116 159 +#define FRAME_stand117 160 +#define FRAME_stand118 161 +#define FRAME_stand119 162 +#define FRAME_stand120 163 +#define FRAME_stand121 164 +#define FRAME_stand122 165 +#define FRAME_stand123 166 +#define FRAME_stand124 167 +#define FRAME_stand125 168 +#define FRAME_stand126 169 +#define FRAME_stand127 170 +#define FRAME_stand128 171 +#define FRAME_stand129 172 +#define FRAME_stand130 173 +#define FRAME_stand131 174 +#define FRAME_stand132 175 +#define FRAME_stand133 176 +#define FRAME_stand134 177 +#define FRAME_stand135 178 +#define FRAME_stand136 179 +#define FRAME_stand137 180 +#define FRAME_stand138 181 +#define FRAME_stand139 182 +#define FRAME_stand140 183 +#define FRAME_stand141 184 +#define FRAME_stand142 185 +#define FRAME_stand143 186 +#define FRAME_stand144 187 +#define FRAME_stand145 188 +#define FRAME_stand146 189 +#define FRAME_stand147 190 +#define FRAME_stand148 191 +#define FRAME_stand149 192 +#define FRAME_stand150 193 +#define FRAME_stand151 194 +#define FRAME_stand152 195 +#define FRAME_stand201 196 +#define FRAME_stand202 197 +#define FRAME_stand203 198 +#define FRAME_stand204 199 +#define FRAME_stand205 200 +#define FRAME_stand206 201 +#define FRAME_stand207 202 +#define FRAME_stand208 203 +#define FRAME_stand209 204 +#define FRAME_stand210 205 +#define FRAME_stand211 206 +#define FRAME_stand212 207 +#define FRAME_stand213 208 +#define FRAME_stand214 209 +#define FRAME_stand215 210 +#define FRAME_stand216 211 +#define FRAME_stand217 212 +#define FRAME_stand218 213 +#define FRAME_stand219 214 +#define FRAME_stand220 215 +#define FRAME_stand221 216 +#define FRAME_stand222 217 +#define FRAME_stand223 218 +#define FRAME_stand224 219 +#define FRAME_stand225 220 +#define FRAME_stand226 221 +#define FRAME_stand227 222 +#define FRAME_stand228 223 +#define FRAME_stand229 224 +#define FRAME_stand230 225 +#define FRAME_stand231 226 +#define FRAME_stand232 227 +#define FRAME_stand233 228 +#define FRAME_stand234 229 +#define FRAME_stand235 230 +#define FRAME_stand236 231 +#define FRAME_stand237 232 +#define FRAME_stand238 233 +#define FRAME_stand239 234 +#define FRAME_stand240 235 +#define FRAME_stand241 236 +#define FRAME_stand242 237 +#define FRAME_stand243 238 +#define FRAME_stand244 239 +#define FRAME_stand245 240 +#define FRAME_stand246 241 +#define FRAME_stand247 242 +#define FRAME_stand248 243 +#define FRAME_stand249 244 +#define FRAME_stand250 245 +#define FRAME_stand251 246 +#define FRAME_stand252 247 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/flyer/flyer.c b/src/game/monster/flyer/flyer.c new file mode 100644 index 000000000..582cd690e --- /dev/null +++ b/src/game/monster/flyer/flyer.c @@ -0,0 +1,1167 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Flyer. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "flyer.h" + +qboolean visible(edict_t *self, edict_t *other); + +static int nextmove; /* Used for start/stop frames */ + +static int sound_sight; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_slash; +static int sound_sproing; +static int sound_die; + +void flyer_check_melee(edict_t *self); +void flyer_loop_melee(edict_t *self); +void flyer_melee(edict_t *self); +void flyer_setstart(edict_t *self); +void flyer_stand(edict_t *self); +void flyer_nextmove(edict_t *self); + +void flyer_kamikaze(edict_t *self); +void flyer_kamikaze_check(edict_t *self); +void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, + int damage, vec3_t point); + +void +flyer_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +flyer_idle(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void +flyer_pop_blades(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0); +} + +static mframe_t flyer_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}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL} +}; + +mmove_t flyer_move_stand = +{ + FRAME_stand01, + FRAME_stand45, + flyer_frames_stand, + NULL +}; + +static mframe_t flyer_frames_walk[] = { + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL} +}; + +mmove_t flyer_move_walk = +{ + FRAME_stand01, + FRAME_stand45, + flyer_frames_walk, + NULL +}; + +static mframe_t flyer_frames_run[] = { + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL} +}; + +mmove_t flyer_move_run = +{ + FRAME_stand01, + FRAME_stand45, + flyer_frames_run, + NULL +}; + +static mframe_t flyer_frames_kamizake[] = { + {ai_charge, 40, flyer_kamikaze_check}, + {ai_charge, 40, flyer_kamikaze_check}, + {ai_charge, 40, flyer_kamikaze_check}, + {ai_charge, 40, flyer_kamikaze_check}, + {ai_charge, 40, flyer_kamikaze_check} +}; + +mmove_t flyer_move_kamikaze = { + FRAME_rollr02, + FRAME_rollr06, + flyer_frames_kamizake, + flyer_kamikaze +}; + +void +flyer_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->mass > 50) + { + self->monsterinfo.currentmove = &flyer_move_kamikaze; + } + else + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &flyer_move_stand; + } + else + { + self->monsterinfo.currentmove = &flyer_move_run; + } +} + +void +flyer_walk(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->mass > 50) + { + flyer_run(self); + } + else + { + self->monsterinfo.currentmove = &flyer_move_walk; + } +} + +void +flyer_stand(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->mass > 50) + { + flyer_run(self); + } + else + { + self->monsterinfo.currentmove = &flyer_move_stand; + } +} + +void +flyer_kamikaze_explode(edict_t *self) +{ + vec3_t dir; + + if (!self) + { + return; + } + + if (self->monsterinfo.commander && self->monsterinfo.commander->inuse && + !strcmp(self->monsterinfo.commander->classname, "monster_carrier")) + { + self->monsterinfo.commander->monsterinfo.monster_slots++; + } + + if (self->enemy) + { + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + T_Damage(self->enemy, self, self, dir, self->s.origin, + vec3_origin, (int)50, (int)50, DAMAGE_RADIUS, MOD_UNKNOWN); + } + + flyer_die(self, NULL, NULL, 0, dir); +} + +void +flyer_kamikaze(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &flyer_move_kamikaze; +} + +void +flyer_kamikaze_check(edict_t *self) +{ + float dist; + + if (!self) + { + return; + } + + /* this needed because we could have gone away before we get here (blocked code) */ + if (!self->inuse) + { + return; + } + + if ((!self->enemy) || (!self->enemy->inuse)) + { + flyer_kamikaze_explode(self); + return; + } + + self->goalentity = self->enemy; + + dist = realrange(self, self->enemy); + + if (dist < 90) + { + flyer_kamikaze_explode(self); + } +} + +static mframe_t flyer_frames_start[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, flyer_nextmove} +}; + +mmove_t flyer_move_start = +{ + FRAME_start01, + FRAME_start06, + flyer_frames_start, + NULL +}; + +static mframe_t flyer_frames_stop[] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, flyer_nextmove} +}; + +mmove_t flyer_move_stop = +{ + FRAME_stop01, + FRAME_stop07, + flyer_frames_stop, + NULL +}; + +void +flyer_stop(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &flyer_move_stop; +} + +void +flyer_start(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &flyer_move_start; +} + +static mframe_t flyer_frames_rollright[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flyer_move_rollright = +{ + FRAME_rollr01, + FRAME_rollr09, + flyer_frames_rollright, + NULL +}; + +static mframe_t flyer_frames_rollleft[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flyer_move_rollleft = +{ + FRAME_rollf01, + FRAME_rollf09, + flyer_frames_rollleft, + NULL +}; + +static mframe_t flyer_frames_pain3[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flyer_move_pain3 = +{ + FRAME_pain301, + FRAME_pain304, + flyer_frames_pain3, + flyer_run +}; + +static mframe_t flyer_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flyer_move_pain2 = +{ + FRAME_pain201, + FRAME_pain204, + flyer_frames_pain2, + flyer_run +}; + +static mframe_t flyer_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flyer_move_pain1 = +{ + FRAME_pain101, + FRAME_pain109, + flyer_frames_pain1, + flyer_run +}; + +static mframe_t flyer_frames_defense[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* Hold this frame */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flyer_move_defense = +{ + FRAME_defens01, + FRAME_defens06, + flyer_frames_defense, + NULL +}; + +static mframe_t flyer_frames_bankright[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flyer_move_bankright = +{ + FRAME_bankr01, + FRAME_bankr07, + flyer_frames_bankright, + NULL +}; + +static mframe_t flyer_frames_bankleft[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t flyer_move_bankleft = +{ + FRAME_bankl01, + FRAME_bankl07, + flyer_frames_bankleft, + NULL +}; + +void +flyer_fire(edict_t *self, int flash_number) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if (!self) + { + return; + } + + if (!self->enemy || !self->enemy->inuse) + { + return; + } + + if ((self->s.frame == FRAME_attak204) || + (self->s.frame == FRAME_attak207) || + (self->s.frame == FRAME_attak210)) + { + effect = EF_HYPERBLASTER; + } + else + { + effect = 0; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, dir); + + monster_fire_blaster(self, start, dir, 1, 1000, flash_number, effect); +} + +void +flyer_fireleft(edict_t *self) +{ + if (!self) + { + return; + } + + flyer_fire(self, MZ2_FLYER_BLASTER_1); +} + +void +flyer_fireright(edict_t *self) +{ + if (!self) + { + return; + } + + flyer_fire(self, MZ2_FLYER_BLASTER_2); +} + +static mframe_t flyer_frames_attack2[] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, -10, flyer_fireleft}, /* left gun */ + {ai_charge, -10, flyer_fireright}, /* right gun */ + {ai_charge, -10, flyer_fireleft}, /* left gun */ + {ai_charge, -10, flyer_fireright}, /* right gun */ + {ai_charge, -10, flyer_fireleft}, /* left gun */ + {ai_charge, -10, flyer_fireright}, /* right gun */ + {ai_charge, -10, flyer_fireleft}, /* left gun */ + {ai_charge, -10, flyer_fireright}, /* right gun */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t flyer_move_attack2 = +{ + FRAME_attak201, + FRAME_attak217, + flyer_frames_attack2, + flyer_run +}; + +/* circle strafe frames */ +static mframe_t flyer_frames_attack3[] = +{ + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, flyer_fireleft}, /* left gun */ + {ai_charge, 10, flyer_fireright}, /* right gun */ + {ai_charge, 10, flyer_fireleft}, /* left gun */ + {ai_charge, 10, flyer_fireright}, /* right gun */ + {ai_charge, 10, flyer_fireleft}, /* left gun */ + {ai_charge, 10, flyer_fireright}, /* right gun */ + {ai_charge, 10, flyer_fireleft}, /* left gun */ + {ai_charge, 10, flyer_fireright}, /* right gun */ + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, NULL} +}; + +mmove_t flyer_move_attack3 = { + FRAME_attak201, + FRAME_attak217, + flyer_frames_attack3, + flyer_run +}; + +void +flyer_slash_left(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->mins[0], 0); + fire_hit(self, aim, 5, 0); + gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0); +} + +void +flyer_slash_right(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 0); + fire_hit(self, aim, 5, 0); + gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0); +} + +static mframe_t flyer_frames_start_melee[] = { + {ai_charge, 0, flyer_pop_blades}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t flyer_move_start_melee = +{ + FRAME_attak101, + FRAME_attak106, + flyer_frames_start_melee, + flyer_loop_melee +}; + +static mframe_t flyer_frames_end_melee[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t flyer_move_end_melee = +{ + FRAME_attak119, + FRAME_attak121, + flyer_frames_end_melee, + flyer_run +}; + +static mframe_t flyer_frames_loop_melee[] = { + {ai_charge, 0, NULL}, /* Loop Start */ + {ai_charge, 0, NULL}, + {ai_charge, 0, flyer_slash_left}, /* Left Wing Strike */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, flyer_slash_right}, /* Right Wing Strike */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} /* Loop Ends */ +}; + +mmove_t flyer_move_loop_melee = +{ + FRAME_attak107, + FRAME_attak118, + flyer_frames_loop_melee, + flyer_check_melee +}; + +void +flyer_loop_melee(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &flyer_move_loop_melee; +} + +void +flyer_attack(edict_t *self) +{ + float chance; + + if (!self) + { + return; + } + + if (self->mass > 50) + { + flyer_run(self); + return; + } + + if (skill->value == SKILL_EASY) + { + chance = 0; + } + else + { + chance = 1.0 - (0.5 / (float)(skill->value)); + } + + if (random() > chance) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + self->monsterinfo.currentmove = &flyer_move_attack2; + } + else /* circle strafe */ + { + if (random() <= 0.5) /* switch directions */ + { + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + } + + self->monsterinfo.attack_state = AS_SLIDING; + self->monsterinfo.currentmove = &flyer_move_attack3; + } +} + +void +flyer_setstart(edict_t *self) +{ + if (!self) + { + return; + } + + nextmove = ACTION_run; + self->monsterinfo.currentmove = &flyer_move_start; +} + +void +flyer_nextmove(edict_t *self) +{ + if (!self) + { + return; + } + + if (nextmove == ACTION_attack1) + { + self->monsterinfo.currentmove = &flyer_move_start_melee; + } + else if (nextmove == ACTION_attack2) + { + self->monsterinfo.currentmove = &flyer_move_attack2; + } + else if (nextmove == ACTION_run) + { + self->monsterinfo.currentmove = &flyer_move_run; + } +} + +void +flyer_melee(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->mass > 50) + { + flyer_run(self); + } + else + { + self->monsterinfo.currentmove = &flyer_move_start_melee; + } +} + +void +flyer_check_melee(edict_t *self) +{ + if (!self) + { + return; + } + + if (range(self, self->enemy) == RANGE_MELEE) + { + if (random() <= 0.8) + { + self->monsterinfo.currentmove = &flyer_move_loop_melee; + } + else + { + self->monsterinfo.currentmove = &flyer_move_end_melee; + } + } + else + { + self->monsterinfo.currentmove = &flyer_move_end_melee; + } +} + +void +flyer_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + int n; + + if (!self) + { + return; + } + + /* kamikaze's don't feel pain */ + if (self->mass != 50) + { + return; + } + + 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; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + n = randk() % 3; + + if (n == 0) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain1; + } + else if (n == 1) + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain2; + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &flyer_move_pain3; + } +} + +void +flyer_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage /* unused */, + vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + BecomeExplosion1(self); +} + +/* kamikaze code .. blow up if blocked */ +int +flyer_blocked(edict_t *self, float dist) +{ + vec3_t origin; + + if (!self) + { + return 0; + } + + /* kamikaze = 100, normal = 50 */ + if (self->mass == 100) + { + flyer_kamikaze_check(self); + + /* if the above didn't blow us up (i.e. I got blocked by the player) */ + if (self->inuse) + { + if (self->monsterinfo.commander && + self->monsterinfo.commander->inuse && + !strcmp(self->monsterinfo.commander->classname, "monster_carrier")) + { + self->monsterinfo.commander->monsterinfo.monster_slots++; + } + + VectorMA(self->s.origin, -0.02, self->velocity, origin); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_ROCKET_EXPLOSION); + gi.WritePosition(origin); + gi.multicast(self->s.origin, MULTICAST_PHS); + + G_FreeEdict(self); + } + + return true; + } + + /* we're a normal flyer */ + + return false; +} + +/* + * QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_flyer(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + /* fix a map bug in jail5.bsp */ + if (!Q_stricmp(level.mapname, "jail5") && (self->s.origin[2] == -104)) + { + self->targetname = self->target; + self->target = NULL; + } + + sound_sight = gi.soundindex("flyer/flysght1.wav"); + sound_idle = gi.soundindex("flyer/flysrch1.wav"); + sound_pain1 = gi.soundindex("flyer/flypain1.wav"); + sound_pain2 = gi.soundindex("flyer/flypain2.wav"); + sound_slash = gi.soundindex("flyer/flyatck2.wav"); + sound_sproing = gi.soundindex("flyer/flyatck1.wav"); + sound_die = gi.soundindex("flyer/flydeth1.wav"); + + gi.soundindex("flyer/flyatck3.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/flyer/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 16); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->s.sound = gi.soundindex("flyer/flyidle1.wav"); + + self->health = 50; + self->mass = 50; + + self->pain = flyer_pain; + self->die = flyer_die; + + self->monsterinfo.stand = flyer_stand; + self->monsterinfo.walk = flyer_walk; + self->monsterinfo.run = flyer_run; + self->monsterinfo.attack = flyer_attack; + self->monsterinfo.melee = flyer_melee; + self->monsterinfo.sight = flyer_sight; + self->monsterinfo.idle = flyer_idle; + self->monsterinfo.blocked = (void *)flyer_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &flyer_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start(self); +} + +/* suicide fliers */ +void +SP_monster_kamikaze(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_sight = gi.soundindex("flyer/flysght1.wav"); + sound_idle = gi.soundindex("flyer/flysrch1.wav"); + sound_pain1 = gi.soundindex("flyer/flypain1.wav"); + sound_pain2 = gi.soundindex("flyer/flypain2.wav"); + sound_slash = gi.soundindex("flyer/flyatck2.wav"); + sound_sproing = gi.soundindex("flyer/flyatck1.wav"); + sound_die = gi.soundindex("flyer/flydeth1.wav"); + + gi.soundindex("flyer/flyatck3.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/flyer/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 16); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->s.sound = gi.soundindex("flyer/flyidle1.wav"); + + self->s.effects |= EF_ROCKET; + + self->health = 50; + self->mass = 100; + + self->pain = flyer_pain; + self->die = flyer_die; + + self->monsterinfo.stand = flyer_stand; + self->monsterinfo.walk = flyer_walk; + self->monsterinfo.run = flyer_run; + self->monsterinfo.attack = flyer_attack; + self->monsterinfo.melee = flyer_melee; + self->monsterinfo.sight = flyer_sight; + self->monsterinfo.idle = flyer_idle; + + self->monsterinfo.blocked = (void *)flyer_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &flyer_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start(self); +} diff --git a/src/game/monster/flyer/flyer.h b/src/game/monster/flyer/flyer.h new file mode 100644 index 000000000..2d7aa6893 --- /dev/null +++ b/src/game/monster/flyer/flyer.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Flyer animations. + * + * ======================================================================= + */ + +#define ACTION_nothing 0 +#define ACTION_attack1 1 +#define ACTION_attack2 2 +#define ACTION_run 3 +#define ACTION_walk 4 + +#define FRAME_start01 0 +#define FRAME_start02 1 +#define FRAME_start03 2 +#define FRAME_start04 3 +#define FRAME_start05 4 +#define FRAME_start06 5 +#define FRAME_stop01 6 +#define FRAME_stop02 7 +#define FRAME_stop03 8 +#define FRAME_stop04 9 +#define FRAME_stop05 10 +#define FRAME_stop06 11 +#define FRAME_stop07 12 +#define FRAME_stand01 13 +#define FRAME_stand02 14 +#define FRAME_stand03 15 +#define FRAME_stand04 16 +#define FRAME_stand05 17 +#define FRAME_stand06 18 +#define FRAME_stand07 19 +#define FRAME_stand08 20 +#define FRAME_stand09 21 +#define FRAME_stand10 22 +#define FRAME_stand11 23 +#define FRAME_stand12 24 +#define FRAME_stand13 25 +#define FRAME_stand14 26 +#define FRAME_stand15 27 +#define FRAME_stand16 28 +#define FRAME_stand17 29 +#define FRAME_stand18 30 +#define FRAME_stand19 31 +#define FRAME_stand20 32 +#define FRAME_stand21 33 +#define FRAME_stand22 34 +#define FRAME_stand23 35 +#define FRAME_stand24 36 +#define FRAME_stand25 37 +#define FRAME_stand26 38 +#define FRAME_stand27 39 +#define FRAME_stand28 40 +#define FRAME_stand29 41 +#define FRAME_stand30 42 +#define FRAME_stand31 43 +#define FRAME_stand32 44 +#define FRAME_stand33 45 +#define FRAME_stand34 46 +#define FRAME_stand35 47 +#define FRAME_stand36 48 +#define FRAME_stand37 49 +#define FRAME_stand38 50 +#define FRAME_stand39 51 +#define FRAME_stand40 52 +#define FRAME_stand41 53 +#define FRAME_stand42 54 +#define FRAME_stand43 55 +#define FRAME_stand44 56 +#define FRAME_stand45 57 +#define FRAME_attak101 58 +#define FRAME_attak102 59 +#define FRAME_attak103 60 +#define FRAME_attak104 61 +#define FRAME_attak105 62 +#define FRAME_attak106 63 +#define FRAME_attak107 64 +#define FRAME_attak108 65 +#define FRAME_attak109 66 +#define FRAME_attak110 67 +#define FRAME_attak111 68 +#define FRAME_attak112 69 +#define FRAME_attak113 70 +#define FRAME_attak114 71 +#define FRAME_attak115 72 +#define FRAME_attak116 73 +#define FRAME_attak117 74 +#define FRAME_attak118 75 +#define FRAME_attak119 76 +#define FRAME_attak120 77 +#define FRAME_attak121 78 +#define FRAME_attak201 79 +#define FRAME_attak202 80 +#define FRAME_attak203 81 +#define FRAME_attak204 82 +#define FRAME_attak205 83 +#define FRAME_attak206 84 +#define FRAME_attak207 85 +#define FRAME_attak208 86 +#define FRAME_attak209 87 +#define FRAME_attak210 88 +#define FRAME_attak211 89 +#define FRAME_attak212 90 +#define FRAME_attak213 91 +#define FRAME_attak214 92 +#define FRAME_attak215 93 +#define FRAME_attak216 94 +#define FRAME_attak217 95 +#define FRAME_bankl01 96 +#define FRAME_bankl02 97 +#define FRAME_bankl03 98 +#define FRAME_bankl04 99 +#define FRAME_bankl05 100 +#define FRAME_bankl06 101 +#define FRAME_bankl07 102 +#define FRAME_bankr01 103 +#define FRAME_bankr02 104 +#define FRAME_bankr03 105 +#define FRAME_bankr04 106 +#define FRAME_bankr05 107 +#define FRAME_bankr06 108 +#define FRAME_bankr07 109 +#define FRAME_rollf01 110 +#define FRAME_rollf02 111 +#define FRAME_rollf03 112 +#define FRAME_rollf04 113 +#define FRAME_rollf05 114 +#define FRAME_rollf06 115 +#define FRAME_rollf07 116 +#define FRAME_rollf08 117 +#define FRAME_rollf09 118 +#define FRAME_rollr01 119 +#define FRAME_rollr02 120 +#define FRAME_rollr03 121 +#define FRAME_rollr04 122 +#define FRAME_rollr05 123 +#define FRAME_rollr06 124 +#define FRAME_rollr07 125 +#define FRAME_rollr08 126 +#define FRAME_rollr09 127 +#define FRAME_defens01 128 +#define FRAME_defens02 129 +#define FRAME_defens03 130 +#define FRAME_defens04 131 +#define FRAME_defens05 132 +#define FRAME_defens06 133 +#define FRAME_pain101 134 +#define FRAME_pain102 135 +#define FRAME_pain103 136 +#define FRAME_pain104 137 +#define FRAME_pain105 138 +#define FRAME_pain106 139 +#define FRAME_pain107 140 +#define FRAME_pain108 141 +#define FRAME_pain109 142 +#define FRAME_pain201 143 +#define FRAME_pain202 144 +#define FRAME_pain203 145 +#define FRAME_pain204 146 +#define FRAME_pain301 147 +#define FRAME_pain302 148 +#define FRAME_pain303 149 +#define FRAME_pain304 150 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/gekk/gekk.c b/src/game/monster/gekk/gekk.c new file mode 100644 index 000000000..5edfd3bbe --- /dev/null +++ b/src/game/monster/gekk/gekk.c @@ -0,0 +1,2033 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Gekk (only found in Xatrix). + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "gekk.h" + +#define SPAWNFLAG_CHANT 8 + +static int sound_swing; +static int sound_hit; +static int sound_hit2; +static int sound_death; +static int sound_pain1; +static int sound_sight; +static int sound_search; +static int sound_step1; +static int sound_step2; +static int sound_step3; +static int sound_thud; +static int sound_chantlow; +static int sound_chantmid; +static int sound_chanthigh; + +mmove_t gekk_move_attack1; +mmove_t gekk_move_attack2; +mmove_t gekk_move_swim_start; +mmove_t gekk_move_swim_loop; +mmove_t gekk_move_spit; +mmove_t gekk_move_run_start; +mmove_t gekk_move_run; +qboolean gekk_check_jump(edict_t *self); + +void gekk_swim(edict_t *self); +void gekk_jump_takeoff(edict_t *self); +void gekk_jump_takeoff2(edict_t *self); +void gekk_check_landing(edict_t *self); +void gekk_stop_skid(edict_t *self); +void water_to_land(edict_t *self); +void land_to_water(edict_t *self); +void gekk_check_underwater(edict_t *self); +void gekk_bite(edict_t *self); +void gekk_hit_left(edict_t *self); +void gekk_hit_right(edict_t *self); +void gekk_run_start(edict_t *self); +void gekk_walk(edict_t *self); + +qboolean +gekk_check_melee(edict_t *self) +{ + if (!self) + { + return false; + } + + if (!self->enemy || (self->enemy->health <= 0)) + { + return false; + } + + if (range(self, self->enemy) == RANGE_MELEE) + { + return true; + } + + return false; +} + +qboolean +gekk_check_jump(edict_t *self) +{ + vec3_t v; + float distance; + + if (!self) + { + return false; + } + + if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2])) + { + return false; + } + + if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2])) + { + return false; + } + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + distance = VectorLength(v); + + if (distance < 100) + { + return false; + } + + if (distance > 100) + { + if (random() < 0.9) + { + return false; + } + } + + return true; +} + +qboolean +gekk_check_jump_close(edict_t *self) +{ + vec3_t v; + float distance; + + if (!self) + { + return false; + } + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + + distance = VectorLength(v); + + if (distance < 100) + { + if (self->s.origin[2] < self->enemy->s.origin[2]) + { + return true; + } + else + { + return false; + } + } + + return true; +} + +qboolean +gekk_checkattack(edict_t *self) +{ + if (!self) + { + return false; + } + + if (!self->enemy || (self->enemy->health <= 0)) + { + return false; + } + + if (gekk_check_melee(self)) + { + self->monsterinfo.attack_state = AS_MELEE; + return true; + } + + if (gekk_check_jump(self)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (gekk_check_jump_close(self) && !self->waterlevel) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return false; +} + +void +gekk_step(edict_t *self) +{ + int n; + + if (!self) + { + return; + } + + n = (rand() + 1) % 3; + + if (n == 0) + { + gi.sound(self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0); + } + else if (n == 1) + { + gi.sound(self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0); + } +} + +void +gekk_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +gekk_search(edict_t *self) +{ + float r; + + if (!self) + { + return; + } + + if (self->spawnflags & SPAWNFLAG_CHANT) + { + r = random(); + + if (r < 0.33) + { + gi.sound(self, CHAN_VOICE, sound_chantlow, 1, ATTN_NORM, 0); + } + else if (r < 0.66) + { + gi.sound(self, CHAN_VOICE, sound_chantmid, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_chanthigh, 1, ATTN_NORM, 0); + } + } + else + { + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); + } + + self->health += 10 + (10 * random()); + + if (self->health > self->max_health) + { + self->health = self->max_health; + } + + if (self->health < (self->max_health / 4)) + { + self->s.skinnum = 2; + } + else if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + else + { + self->s.skinnum = 0; + } +} + +void +gekk_swing(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); +} + +void +gekk_face(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gekk_move_run; +} + +void +ai_stand2(edict_t *self, float dist) +{ + if (!self) + { + return; + } + + if (self->spawnflags & SPAWNFLAG_CHANT) + { + ai_move(self, dist); + + if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && + (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle(self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } + } + else if (self->enemy) + { + ai_move(self, dist); + } + else + { + ai_stand(self, dist); + } +} + +static mframe_t gekk_frames_stand[] = { + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, /* 10 */ + + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, /* 20 */ + + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, /* 30 */ + + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + + {ai_stand2, 0, gekk_check_underwater}, +}; + +mmove_t gekk_move_stand = { + FRAME_stand_01, + FRAME_stand_39, + gekk_frames_stand, + NULL +}; + +static mframe_t gekk_frames_standunderwater[] = { + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + + {ai_stand2, 0, gekk_check_underwater} +}; + +mmove_t gekk_move_standunderwater = { + FRAME_amb_01, + FRAME_amb_04, + gekk_frames_standunderwater, + NULL +}; + +void +gekk_swim_loop(edict_t *self) +{ + if (!self) + { + return; + } + + self->flags |= FL_SWIM; + self->monsterinfo.currentmove = &gekk_move_swim_loop; +} + +static mframe_t gekk_frames_swim[] = { + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + + {ai_run, 16, gekk_swim} +}; + +mmove_t gekk_move_swim_loop = { + FRAME_amb_01, + FRAME_amb_04, + gekk_frames_swim, + gekk_swim_loop +}; + +static mframe_t gekk_frames_swim_start[] = { + {ai_run, 14, NULL}, + {ai_run, 14, NULL}, + {ai_run, 14, NULL}, + {ai_run, 14, NULL}, + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + {ai_run, 18, NULL}, + {ai_run, 18, gekk_hit_left}, + {ai_run, 18, NULL}, + + {ai_run, 20, NULL}, + {ai_run, 20, NULL}, + {ai_run, 22, NULL}, + {ai_run, 22, NULL}, + {ai_run, 24, gekk_hit_right}, + {ai_run, 24, NULL}, + {ai_run, 26, NULL}, + {ai_run, 26, NULL}, + {ai_run, 24, NULL}, + {ai_run, 24, NULL}, + + {ai_run, 22, gekk_bite}, + {ai_run, 22, NULL}, + {ai_run, 22, NULL}, + {ai_run, 22, NULL}, + {ai_run, 22, NULL}, + {ai_run, 22, NULL}, + {ai_run, 22, NULL}, + {ai_run, 22, NULL}, + {ai_run, 18, NULL}, + {ai_run, 18, NULL}, + + {ai_run, 18, NULL}, + {ai_run, 18, NULL} +}; + +mmove_t gekk_move_swim_start = { + FRAME_swim_01, + FRAME_swim_32, + gekk_frames_swim_start, + gekk_swim_loop +}; + +void +gekk_swim(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->enemy) + { + return; + } + + if (!self->enemy->waterlevel && (random() > 0.7)) + { + water_to_land(self); + } +} + +void +gekk_stand(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_standunderwater; + } + else + { + self->monsterinfo.currentmove = &gekk_move_stand; + } +} + +void +gekk_idle_loop(edict_t *self) +{ + if (!self) + { + return; + } + + if ((random() > 0.75) && (self->health < self->max_health)) + { + self->monsterinfo.nextframe = FRAME_idle_01; + } +} + +static mframe_t gekk_frames_idle[] = { + {ai_stand2, 0, gekk_search}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + {ai_stand2, 0, NULL}, + + {ai_stand2, 0, NULL}, + {ai_stand2, 0, gekk_idle_loop} +}; + +mmove_t gekk_move_idle = { + FRAME_idle_01, + FRAME_idle_32, + gekk_frames_idle, + gekk_stand +}; + +mmove_t gekk_move_idle2 = { + FRAME_idle_01, + FRAME_idle_32, + gekk_frames_idle, + gekk_face +}; + +void +gekk_idle(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_idle; + } + else + { + self->monsterinfo.currentmove = &gekk_move_swim_start; + } +} + +static mframe_t gekk_frames_walk[] = { + {ai_walk, 3.849, gekk_check_underwater}, /* frame 0 */ + {ai_walk, 19.606, NULL}, /* frame 1 */ + {ai_walk, 25.583, NULL}, /* frame 2 */ + {ai_walk, 34.625, gekk_step}, /* frame 3 */ + {ai_walk, 27.365, NULL}, /* frame 4 */ + {ai_walk, 28.480, NULL}, /* frame 5 */ +}; + +mmove_t gekk_move_walk = { + FRAME_run_01, + FRAME_run_06, + gekk_frames_walk, + NULL +}; + +void +gekk_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gekk_move_walk; +} + +void +gekk_run_start(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_swim_start; + } + else + { + self->monsterinfo.currentmove = &gekk_move_run_start; + } +} + +void +gekk_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_swim_start; + return; + } + else + { + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &gekk_move_stand; + } + else + { + self->monsterinfo.currentmove = &gekk_move_run; + } + } +} + +static mframe_t gekk_frames_run[] = { + {ai_run, 3.849, gekk_check_underwater}, /* frame 0 */ + {ai_run, 19.606, NULL}, /* frame 1 */ + {ai_run, 25.583, NULL}, /* frame 2 */ + {ai_run, 34.625, gekk_step}, /* frame 3 */ + {ai_run, 27.365, NULL}, /* frame 4 */ + {ai_run, 28.480, NULL}, /* frame 5 */ +}; + +mmove_t gekk_move_run = { + FRAME_run_01, + FRAME_run_06, + gekk_frames_run, + NULL +}; + +static mframe_t gekk_frames_run_st[] = { + {ai_run, 0.212, NULL}, /* frame 0 */ + {ai_run, 19.753, NULL}, /* frame 1 */ +}; + +mmove_t gekk_move_run_start = { + FRAME_stand_01, + FRAME_stand_02, + gekk_frames_run_st, + gekk_run +}; + +void +gekk_hit_left(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->mins[0], 8); + + if (fire_hit(self, aim, (15 + (rand() % 5)), 100)) + { + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + } +} + +void +gekk_hit_right(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 8); + + if (fire_hit(self, aim, (15 + (rand() % 5)), 100)) + { + gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + } +} + +void +gekk_check_refire(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->enemy || !self->enemy->inuse || (self->enemy->health <= 0)) + { + return; + } + + if (random() < (skill->value * 0.1)) + { + if (range(self, self->enemy) == RANGE_MELEE) + { + if (self->s.frame == FRAME_clawatk3_09) + { + self->monsterinfo.currentmove = &gekk_move_attack2; + } + else if (self->s.frame == FRAME_clawatk5_09) + { + self->monsterinfo.currentmove = &gekk_move_attack1; + } + } + } +} + +void +loogie_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t normal; + + if (!self || !other) + { + return; + } + + if (other == self->owner) + { + return; + } + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict(self); + return; + } + + if (self->owner->client) + { + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + } + + if (other->takedamage) + { + get_normal_vector(plane, normal); + + T_Damage(other, self, self->owner, self->velocity, self->s.origin, + normal, self->dmg, 1, DAMAGE_ENERGY, + MOD_GEKK); + } + + G_FreeEdict(self); +} + +void +fire_loogie(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed) +{ + edict_t *loogie; + trace_t tr; + + if (!self) + { + return; + } + + VectorNormalize(dir); + + loogie = G_Spawn(); + VectorCopy(start, loogie->s.origin); + VectorCopy(start, loogie->s.old_origin); + vectoangles(dir, loogie->s.angles); + VectorScale(dir, speed, loogie->velocity); + loogie->movetype = MOVETYPE_FLYMISSILE; + loogie->clipmask = MASK_SHOT; + loogie->solid = SOLID_BBOX; + loogie->s.effects |= RF_FULLBRIGHT; + VectorClear(loogie->mins); + VectorClear(loogie->maxs); + + loogie->s.modelindex = gi.modelindex("models/objects/loogy/tris.md2"); + loogie->owner = self; + loogie->touch = loogie_touch; + loogie->nextthink = level.time + 2; + loogie->think = G_FreeEdict; + loogie->dmg = damage; + gi.linkentity(loogie); + + tr = gi.trace(self->s.origin, NULL, NULL, loogie->s.origin, + loogie, MASK_SHOT); + + if (tr.fraction < 1.0) + { + VectorMA(loogie->s.origin, -10, dir, loogie->s.origin); + loogie->touch(loogie, tr.ent, NULL, NULL); + } +} + +void +loogie(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t end; + vec3_t dir; + vec3_t gekkoffset; + + if (!self) + { + return; + } + + VectorSet(gekkoffset, -18, -0.8, 24); + + if (!self->enemy || (self->enemy->health <= 0)) + { + return; + } + + AngleVectors(self->s.angles, forward, right, up); + G_ProjectSource(self->s.origin, gekkoffset, forward, right, start); + + VectorMA(start, 2, up, start); + + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, dir); + + fire_loogie(self, start, dir, 5, 550); +} + +void +reloogie(edict_t *self) +{ + if (!self) + { + return; + } + + if ((random() > 0.8) && (self->health < self->max_health)) + { + self->monsterinfo.currentmove = &gekk_move_idle2; + return; + } + + if (self->enemy->health >= 0) + { + if ((random() > 0.7) && (range(self, self->enemy) == RANGE_NEAR)) + { + self->monsterinfo.currentmove = &gekk_move_spit; + } + } +} + +static mframe_t gekk_frames_spit[] = { + {ai_charge, 0.000, NULL}, + {ai_charge, 0.000, NULL}, + {ai_charge, 0.000, NULL}, + {ai_charge, 0.000, NULL}, + {ai_charge, 0.000, NULL}, + + {ai_charge, 0.000, loogie}, + {ai_charge, 0.000, reloogie} +}; + +mmove_t gekk_move_spit = { + FRAME_spit_01, + FRAME_spit_07, + gekk_frames_spit, + gekk_run_start +}; + +static mframe_t gekk_frames_attack1[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, gekk_hit_left}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, gekk_check_refire} +}; + +mmove_t gekk_move_attack1 = { + FRAME_clawatk3_01, + FRAME_clawatk3_09, + gekk_frames_attack1, + gekk_run_start +}; + +static mframe_t gekk_frames_attack2[] = { + {ai_charge, 0.000, NULL}, + {ai_charge, 0.000, NULL}, + {ai_charge, 0.000, gekk_hit_left}, + + {ai_charge, 0.000, NULL}, + {ai_charge, 0.000, NULL}, + {ai_charge, 0.000, gekk_hit_right}, + + {ai_charge, 0.000, NULL}, + {ai_charge, 0.000, NULL}, + {ai_charge, 0.000, gekk_check_refire} +}; + +mmove_t gekk_move_attack2 = { + FRAME_clawatk5_01, + FRAME_clawatk5_09, + gekk_frames_attack2, + gekk_run_start +}; + +void +gekk_check_underwater(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->waterlevel) + { + land_to_water(self); + } +} + +static mframe_t gekk_frames_leapatk[] = { + {ai_charge, 0.000, NULL}, /* frame 0 */ + {ai_charge, -0.387, NULL}, /* frame 1 */ + {ai_charge, -1.113, NULL}, /* frame 2 */ + {ai_charge, -0.237, NULL}, /* frame 3 */ + {ai_charge, 6.720, gekk_jump_takeoff}, /* frame 4 last frame on ground */ + {ai_charge, 6.414, NULL}, /* frame 5 leaves ground */ + {ai_charge, 0.163, NULL}, /* frame 6 */ + {ai_charge, 28.316, NULL}, /* frame 7 */ + {ai_charge, 24.198, NULL}, /* frame 8 */ + {ai_charge, 31.742, NULL}, /* frame 9 */ + {ai_charge, 35.977, gekk_check_landing}, /* frame 10 last frame in air */ + {ai_charge, 12.303, gekk_stop_skid}, /* frame 11 feet back on ground */ + {ai_charge, 20.122, gekk_stop_skid}, /* frame 12 */ + {ai_charge, -1.042, gekk_stop_skid}, /* frame 13 */ + {ai_charge, 2.556, gekk_stop_skid}, /* frame 14 */ + {ai_charge, 0.544, gekk_stop_skid}, /* frame 15 */ + {ai_charge, 1.862, gekk_stop_skid}, /* frame 16 */ + {ai_charge, 1.224, gekk_stop_skid}, /* frame 17 */ + + {ai_charge, -0.457, gekk_check_underwater}, /* frame 18 */ +}; + +mmove_t gekk_move_leapatk = { + FRAME_leapatk_01, + FRAME_leapatk_19, + gekk_frames_leapatk, + gekk_run_start +}; + +static mframe_t gekk_frames_leapatk2[] = { + {ai_charge, 0.000, NULL}, /* frame 0 */ + {ai_charge, -0.387, NULL}, /* frame 1 */ + {ai_charge, -1.113, NULL}, /* frame 2 */ + {ai_charge, -0.237, NULL}, /* frame 3 */ + {ai_charge, 6.720, gekk_jump_takeoff2}, /* frame 4 last frame on ground */ + {ai_charge, 6.414, NULL}, /* frame 5 leaves ground */ + {ai_charge, 0.163, NULL}, /* frame 6 */ + {ai_charge, 28.316, NULL}, /* frame 7 */ + {ai_charge, 24.198, NULL}, /* frame 8 */ + {ai_charge, 31.742, NULL}, /* frame 9 */ + {ai_charge, 35.977, gekk_check_landing}, /* frame 10 last frame in air */ + {ai_charge, 12.303, gekk_stop_skid}, /* frame 11 feet back on ground */ + {ai_charge, 20.122, gekk_stop_skid}, /* frame 12 */ + {ai_charge, -1.042, gekk_stop_skid}, /* frame 13 */ + {ai_charge, 2.556, gekk_stop_skid}, /* frame 14 */ + {ai_charge, 0.544, gekk_stop_skid}, /* frame 15 */ + {ai_charge, 1.862, gekk_stop_skid}, /* frame 16 */ + {ai_charge, 1.224, gekk_stop_skid}, /* frame 17 */ + + {ai_charge, -0.457, gekk_check_underwater}, /* frame 18 */ +}; + +mmove_t gekk_move_leapatk2 = { + FRAME_leapatk_01, + FRAME_leapatk_19, + gekk_frames_leapatk2, + gekk_run_start +}; + +void +gekk_bite(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, 0, 0); + fire_hit(self, aim, 5, 0); +} + +void +gekk_preattack(edict_t *self) +{ + /* Unused but PITA to remove */ +} + +static mframe_t gekk_frames_attack[] = { + {ai_charge, 16, gekk_preattack}, + {ai_charge, 16, NULL}, + {ai_charge, 16, NULL}, + {ai_charge, 16, NULL}, + {ai_charge, 16, gekk_bite}, + {ai_charge, 16, NULL}, + {ai_charge, 16, NULL}, + {ai_charge, 16, NULL}, + {ai_charge, 16, NULL}, + {ai_charge, 16, gekk_bite}, + + {ai_charge, 16, NULL}, + {ai_charge, 16, NULL}, + {ai_charge, 16, NULL}, + {ai_charge, 16, gekk_hit_left}, + {ai_charge, 16, NULL}, + {ai_charge, 16, NULL}, + {ai_charge, 16, NULL}, + {ai_charge, 16, NULL}, + {ai_charge, 16, gekk_hit_right}, + {ai_charge, 16, NULL}, + + {ai_charge, 16, NULL} +}; + +mmove_t gekk_move_attack = { + FRAME_attack_01, + FRAME_attack_21, + gekk_frames_attack, + gekk_run_start +}; + +void +gekk_melee(edict_t *self) +{ + float r; + + if (!self) + { + return; + } + + if (self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_attack; + } + else + { + r = random(); + + if (r > 0.66) + { + self->monsterinfo.currentmove = &gekk_move_attack1; + } + else + { + self->monsterinfo.currentmove = &gekk_move_attack2; + } + } +} + +void +gekk_jump_touch(edict_t *self, edict_t *other, cplane_t *plane /* unsued */, + csurface_t *surf /* unused */) +{ + if (!self) + { + return; + } + + if (self->health <= 0) + { + self->touch = NULL; + return; + } + + if (other && other->takedamage) + { + if (VectorLength(self->velocity) > 200) + { + vec3_t point; + vec3_t normal; + int damage; + + VectorCopy(self->velocity, normal); + VectorNormalize(normal); + VectorMA(self->s.origin, self->maxs[0], normal, point); + damage = 10 + 10 * random(); + T_Damage(other, self, self, self->velocity, point, + normal, damage, damage, 0, MOD_GEKK); + } + } + + if (!M_CheckBottom(self)) + { + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_leapatk_11; + self->monsterinfo.aiflags &= ~AI_IGNORE_PAIN; + self->touch = NULL; + } + + return; + } + + self->monsterinfo.aiflags &= ~AI_IGNORE_PAIN; + self->touch = NULL; +} + +void +gekk_jump_takeoff(edict_t *self) +{ + vec3_t forward; + + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors(self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + + /* high jump */ + if (gekk_check_jump(self)) + { + VectorScale(forward, 700, self->velocity); + self->velocity[2] = 250; + } + else + { + VectorScale(forward, 250, self->velocity); + self->velocity[2] = 400; + } + + self->groundentity = NULL; + self->monsterinfo.aiflags |= AI_IGNORE_PAIN; + self->monsterinfo.attack_finished = level.time + 3; + self->touch = gekk_jump_touch; +} + +void +gekk_jump_takeoff2(edict_t *self) +{ + vec3_t forward; + + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors(self->s.angles, forward, NULL, NULL); + self->s.origin[2] = self->enemy->s.origin[2]; + + if (gekk_check_jump(self)) + { + VectorScale(forward, 300, self->velocity); + self->velocity[2] = 250; + } + else + { + VectorScale(forward, 150, self->velocity); + self->velocity[2] = 300; + } + + self->groundentity = NULL; + self->monsterinfo.aiflags |= AI_IGNORE_PAIN; + self->monsterinfo.attack_finished = level.time + 3; + self->touch = gekk_jump_touch; +} + +void +gekk_stop_skid(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->groundentity) + { + VectorClear(self->velocity); + } +} + +void +gekk_check_landing(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->groundentity) + { + gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.attack_finished = 0; + self->monsterinfo.aiflags &= ~AI_IGNORE_PAIN; + + VectorClear(self->velocity); + + return; + } + + if (level.time > self->monsterinfo.attack_finished) + { + self->monsterinfo.nextframe = FRAME_leapatk_11; + } + else + { + self->monsterinfo.nextframe = FRAME_leapatk_12; + } +} + +void +gekk_jump(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->flags & FL_SWIM || self->waterlevel) + { + return; + } + else + { + { + if ((random() > 0.5) && (range(self, self->enemy) >= RANGE_NEAR)) + { + self->monsterinfo.currentmove = &gekk_move_spit; + } + else if (random() > 0.8) + { + self->monsterinfo.currentmove = &gekk_move_spit; + } + else + { + self->monsterinfo.currentmove = &gekk_move_leapatk; + } + } + } +} + +static mframe_t gekk_frames_pain[] = { + {ai_move, 0.000, NULL}, /* frame 0 */ + {ai_move, 0.000, NULL}, /* frame 1 */ + {ai_move, 0.000, NULL}, /* frame 2 */ + {ai_move, 0.000, NULL}, /* frame 3 */ + {ai_move, 0.000, NULL}, /* frame 4 */ + {ai_move, 0.000, NULL}, /* frame 5 */ +}; + +mmove_t gekk_move_pain = { + FRAME_pain_01, + FRAME_pain_06, + gekk_frames_pain, + gekk_run_start +}; + +static mframe_t gekk_frames_pain1[] = { + {ai_move, 0.000, NULL}, /* frame 0 */ + {ai_move, 0.000, NULL}, /* frame 1 */ + {ai_move, 0.000, NULL}, /* frame 2 */ + {ai_move, 0.000, NULL}, /* frame 3 */ + {ai_move, 0.000, NULL}, /* frame 4 */ + {ai_move, 0.000, NULL}, /* frame 5 */ + {ai_move, 0.000, NULL}, /* frame 6 */ + {ai_move, 0.000, NULL}, /* frame 7 */ + {ai_move, 0.000, NULL}, /* frame 8 */ + {ai_move, 0.000, NULL}, /* frame 9 */ + + {ai_move, 0.000, gekk_check_underwater} +}; + +mmove_t gekk_move_pain1 = { + FRAME_pain3_01, + FRAME_pain3_11, + gekk_frames_pain1, + gekk_run_start +}; + +static mframe_t gekk_frames_pain2[] = { + {ai_move, 0.000, NULL}, /* frame 0 */ + {ai_move, 0.000, NULL}, /* frame 1 */ + {ai_move, 0.000, NULL}, /* frame 2 */ + {ai_move, 0.000, NULL}, /* frame 3 */ + {ai_move, 0.000, NULL}, /* frame 4 */ + {ai_move, 0.000, NULL}, /* frame 5 */ + {ai_move, 0.000, NULL}, /* frame 6 */ + {ai_move, 0.000, NULL}, /* frame 7 */ + {ai_move, 0.000, NULL}, /* frame 8 */ + {ai_move, 0.000, NULL}, /* frame 9 */ + + {ai_move, 0.000, NULL}, /* frame 10 */ + {ai_move, 0.000, NULL}, /* frame 11 */ + {ai_move, 0.000, gekk_check_underwater}, +}; + +mmove_t gekk_move_pain2 = { + FRAME_pain4_01, + FRAME_pain4_13, + gekk_frames_pain2, + gekk_run_start +}; + +void +gekk_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) +{ + float r; + + if (!self) + { + return; + } + + if (self->spawnflags & SPAWNFLAG_CHANT) + { + self->spawnflags &= ~SPAWNFLAG_CHANT; + return; + } + + if (self->health < (self->max_health / 4)) + { + self->s.skinnum = 2; + } + else 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, sound_pain1, 1, ATTN_NORM, 0); + + if (self->waterlevel) + { + if ((!self->flags) & FL_SWIM) + { + self->flags |= FL_SWIM; + } + + self->monsterinfo.currentmove = &gekk_move_pain; + } + else + { + r = random(); + + if (r > 0.5) + { + self->monsterinfo.currentmove = &gekk_move_pain1; + } + else + { + self->monsterinfo.currentmove = &gekk_move_pain2; + } + } +} + +void +gekk_dead(edict_t *self) +{ + if (!self) + { + return; + } + + /* fix this because of no blocking problem */ + if (self->waterlevel) + { + return; + } + else + { + 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); + } +} + +void +gekk_gibfest(edict_t *self) +{ + int damage = 20; + + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + ThrowGibACID(self, "models/objects/gekkgib/pelvis/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/torso/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/claw/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC); + + ThrowHeadACID(self, "models/objects/gekkgib/head/tris.md2", damage, GIB_ORGANIC); + + self->deadflag = DEAD_DEAD; +} + +void +isgibfest(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() > 0.9) + { + gekk_gibfest(self); + } +} + +static mframe_t gekk_frames_death1[] = { + {ai_move, -5.151, NULL}, /* frame 0 */ + {ai_move, -12.223, NULL}, /* frame 1 */ + {ai_move, -11.484, NULL}, /* frame 2 */ + {ai_move, -17.952, NULL}, /* frame 3 */ + {ai_move, -6.953, NULL}, /* frame 4 */ + {ai_move, -7.393, NULL}, /* frame 5 */ + {ai_move, -10.713, NULL}, /* frame 6 */ + {ai_move, -17.464, NULL}, /* frame 7 */ + {ai_move, -11.678, NULL}, /* frame 8 */ + {ai_move, -11.678, NULL} /* frame 9 */ +}; + +mmove_t gekk_move_death1 = { + FRAME_death1_01, + FRAME_death1_10, + gekk_frames_death1, + gekk_dead +}; + +static mframe_t gekk_frames_death3[] = { + {ai_move, 0.000, NULL}, /* frame 0 */ + {ai_move, 0.022, NULL}, /* frame 1 */ + {ai_move, 0.169, NULL}, /* frame 2 */ + {ai_move, -0.710, NULL}, /* frame 3 */ + {ai_move, -13.446, NULL}, /* frame 4 */ + {ai_move, -7.654, isgibfest}, /* frame 5 */ + {ai_move, -31.951, NULL}, /* frame 6 */ +}; + +mmove_t gekk_move_death3 = { + FRAME_death3_01, + FRAME_death3_07, + gekk_frames_death3, + gekk_dead +}; + +static mframe_t gekk_frames_death4[] = { + {ai_move, 5.103, NULL}, /* frame 0 */ + {ai_move, -4.808, NULL}, /* frame 1 */ + {ai_move, -10.509, NULL}, /* frame 2 */ + {ai_move, -9.899, NULL}, /* frame 3 */ + {ai_move, 4.033, isgibfest}, /* frame 4 */ + {ai_move, -5.197, NULL}, /* frame 5 */ + {ai_move, -0.919, NULL}, /* frame 6 */ + {ai_move, -8.821, NULL}, /* frame 7 */ + {ai_move, -5.626, NULL}, /* frame 8 */ + {ai_move, -8.865, isgibfest}, /* frame 9 */ + {ai_move, -0.845, NULL}, /* frame 10 */ + {ai_move, 1.986, NULL}, /* frame 11 */ + {ai_move, 0.170, NULL}, /* frame 12 */ + {ai_move, 1.339, isgibfest}, /* frame 13 */ + {ai_move, -0.922, NULL}, /* frame 14 */ + {ai_move, 0.818, NULL}, /* frame 15 */ + {ai_move, -1.288, NULL}, /* frame 16 */ + {ai_move, -1.408, isgibfest}, /* frame 17 */ + {ai_move, -7.787, NULL}, /* frame 18 */ + {ai_move, -3.995, NULL}, /* frame 19 */ + {ai_move, -4.604, NULL}, /* frame 20 */ + {ai_move, -1.715, isgibfest}, /* frame 21 */ + {ai_move, -0.564, NULL}, /* frame 22 */ + {ai_move, -0.597, NULL}, /* frame 23 */ + {ai_move, 0.074, NULL}, /* frame 24 */ + {ai_move, -0.309, isgibfest}, /* frame 25 */ + {ai_move, -0.395, NULL}, /* frame 26 */ + {ai_move, -0.501, NULL}, /* frame 27 */ + {ai_move, -0.325, NULL}, /* frame 28 */ + {ai_move, -0.931, isgibfest}, /* frame 29 */ + {ai_move, -1.433, NULL}, /* frame 30 */ + {ai_move, -1.626, NULL}, /* frame 31 */ + {ai_move, 4.680, NULL}, /* frame 32 */ + {ai_move, 0.560, NULL}, /* frame 33 */ + {ai_move, -0.549, gekk_gibfest} /* frame 34 */ +}; + +mmove_t gekk_move_death4 = { + FRAME_death4_01, + FRAME_death4_35, + gekk_frames_death4, + gekk_dead +}; + +static mframe_t gekk_frames_wdeath[] = { + {ai_move, 0.000, NULL}, /* frame 0 */ + {ai_move, 0.000, NULL}, /* frame 1 */ + {ai_move, 0.000, NULL}, /* frame 2 */ + {ai_move, 0.000, NULL}, /* frame 3 */ + {ai_move, 0.000, NULL}, /* frame 4 */ + {ai_move, 0.000, NULL}, /* frame 5 */ + {ai_move, 0.000, NULL}, /* frame 6 */ + {ai_move, 0.000, NULL}, /* frame 7 */ + {ai_move, 0.000, NULL}, /* frame 8 */ + {ai_move, 0.000, NULL}, /* frame 9 */ + {ai_move, 0.000, NULL}, /* frame 10 */ + {ai_move, 0.000, NULL}, /* frame 11 */ + {ai_move, 0.000, NULL}, /* frame 12 */ + {ai_move, 0.000, NULL}, /* frame 13 */ + {ai_move, 0.000, NULL}, /* frame 14 */ + {ai_move, 0.000, NULL}, /* frame 15 */ + {ai_move, 0.000, NULL}, /* frame 16 */ + {ai_move, 0.000, NULL}, /* frame 17 */ + {ai_move, 0.000, NULL}, /* frame 18 */ + {ai_move, 0.000, NULL}, /* frame 19 */ + {ai_move, 0.000, NULL}, /* frame 20 */ + {ai_move, 0.000, NULL}, /* frame 21 */ + {ai_move, 0.000, NULL}, /* frame 22 */ + {ai_move, 0.000, NULL}, /* frame 23 */ + {ai_move, 0.000, NULL}, /* frame 24 */ + {ai_move, 0.000, NULL}, /* frame 25 */ + {ai_move, 0.000, NULL}, /* frame 26 */ + {ai_move, 0.000, NULL}, /* frame 27 */ + {ai_move, 0.000, NULL}, /* frame 28 */ + {ai_move, 0.000, NULL}, /* frame 29 */ + {ai_move, 0.000, NULL}, /* frame 30 */ + {ai_move, 0.000, NULL}, /* frame 31 */ + {ai_move, 0.000, NULL}, /* frame 32 */ + {ai_move, 0.000, NULL}, /* frame 33 */ + {ai_move, 0.000, NULL}, /* frame 34 */ + {ai_move, 0.000, NULL}, /* frame 35 */ + {ai_move, 0.000, NULL}, /* frame 36 */ + {ai_move, 0.000, NULL}, /* frame 37 */ + {ai_move, 0.000, NULL}, /* frame 38 */ + {ai_move, 0.000, NULL}, /* frame 39 */ + {ai_move, 0.000, NULL}, /* frame 40 */ + {ai_move, 0.000, NULL}, /* frame 41 */ + {ai_move, 0.000, NULL}, /* frame 42 */ + {ai_move, 0.000, NULL}, /* frame 43 */ + {ai_move, 0.000, NULL} /* frame 44 */ +}; + +mmove_t gekk_move_wdeath = { + FRAME_wdeath_01, + FRAME_wdeath_45, + gekk_frames_wdeath, + gekk_dead +}; + +void +gekk_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage, vec3_t point /* unused */) +{ + float r; + + if (!self) + { + return; + } + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + ThrowGibACID(self, "models/objects/gekkgib/pelvis/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/torso/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/claw/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC); + ThrowGibACID(self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC); + + ThrowHeadACID(self, "models/objects/gekkgib/head/tris.md2", damage, GIB_ORGANIC); + + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum = 2; + + if (self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_wdeath; + } + else + { + r = random(); + + if (r > 0.66) + { + self->monsterinfo.currentmove = &gekk_move_death1; + } + else if (r > 0.33) + { + self->monsterinfo.currentmove = &gekk_move_death3; + } + else + { + self->monsterinfo.currentmove = &gekk_move_death4; + } + } +} + +void +gekk_duck_down(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + return; + } + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity(self); +} + +void +gekk_duck_up(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + +void +gekk_duck_hold(edict_t *self) +{ + if (!self) + { + return; + } + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +static mframe_t gekk_frames_lduck[] = { + {ai_move, 0.000, NULL}, /* frame 0 */ + {ai_move, 0.000, NULL}, /* frame 1 */ + {ai_move, 0.000, NULL}, /* frame 2 */ + {ai_move, 0.000, NULL}, /* frame 3 */ + {ai_move, 0.000, NULL}, /* frame 4 */ + {ai_move, 0.000, NULL}, /* frame 5 */ + {ai_move, 0.000, NULL}, /* frame 6 */ + {ai_move, 0.000, NULL}, /* frame 7 */ + {ai_move, 0.000, NULL}, /* frame 8 */ + {ai_move, 0.000, NULL}, /* frame 9 */ + {ai_move, 0.000, NULL}, /* frame 10 */ + {ai_move, 0.000, NULL}, /* frame 11 */ + {ai_move, 0.000, NULL} /* frame 12 */ +}; + +mmove_t gekk_move_lduck = { + FRAME_lduck_01, + FRAME_lduck_13, + gekk_frames_lduck, + gekk_run_start +}; + +static mframe_t gekk_frames_rduck[] = { + {ai_move, 0.000, NULL}, /* frame 0 */ + {ai_move, 0.000, NULL}, /* frame 1 */ + {ai_move, 0.000, NULL}, /* frame 2 */ + {ai_move, 0.000, NULL}, /* frame 3 */ + {ai_move, 0.000, NULL}, /* frame 4 */ + {ai_move, 0.000, NULL}, /* frame 5 */ + {ai_move, 0.000, NULL}, /* frame 6 */ + {ai_move, 0.000, NULL}, /* frame 7 */ + {ai_move, 0.000, NULL}, /* frame 8 */ + {ai_move, 0.000, NULL}, /* frame 9 */ + {ai_move, 0.000, NULL}, /* frame 10 */ + {ai_move, 0.000, NULL}, /* frame 11 */ + {ai_move, 0.000, NULL} /* frame 12 */ +}; + +mmove_t gekk_move_rduck = { + FRAME_rduck_01, + FRAME_rduck_13, + gekk_frames_rduck, + gekk_run_start +}; + +void +gekk_dodge(edict_t *self, edict_t *attacker, float eta, + trace_t *tr /* unused */) +{ + float r; + + if (!self || !attacker) + { + return; + } + + r = random(); + + if (r > 0.25) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + } + + if (self->waterlevel) + { + self->monsterinfo.currentmove = &gekk_move_attack; + return; + } + + if (skill->value == SKILL_EASY) + { + r = random(); + + if (r > 0.5) + { + self->monsterinfo.currentmove = &gekk_move_lduck; + } + else + { + self->monsterinfo.currentmove = &gekk_move_rduck; + } + + return; + } + + self->monsterinfo.pausetime = level.time + eta + 0.3; + r = random(); + + if (skill->value == SKILL_MEDIUM) + { + if (r > 0.33) + { + r = random(); + + if (r > 0.5) + { + self->monsterinfo.currentmove = &gekk_move_lduck; + } + else + { + self->monsterinfo.currentmove = &gekk_move_rduck; + } + } + else + { + r = random(); + + if (r > 0.66) + { + self->monsterinfo.currentmove = &gekk_move_attack1; + } + else + { + self->monsterinfo.currentmove = &gekk_move_attack2; + } + } + + return; + } + + if (skill->value == SKILL_HARD) + { + if (r > 0.66) + { + r = random(); + + if (r > 0.5) + { + self->monsterinfo.currentmove = &gekk_move_lduck; + } + else + { + self->monsterinfo.currentmove = &gekk_move_rduck; + } + } + else + { + r = random(); + + if (r > 0.66) + { + self->monsterinfo.currentmove = &gekk_move_attack1; + } + else + { + self->monsterinfo.currentmove = &gekk_move_attack2; + } + } + + return; + } + + r = random(); + + if (r > 0.66) + { + self->monsterinfo.currentmove = &gekk_move_attack1; + } + else + { + self->monsterinfo.currentmove = &gekk_move_attack2; + } +} + +/* + * QUAKED monster_gekk (1 .5 0) (-24 -24 -24) (24 24 24) Ambush Trigger_Spawn Sight Chant + */ +void +SP_monster_gekk(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_swing = gi.soundindex("gek/gk_atck1.wav"); + sound_hit = gi.soundindex("gek/gk_atck2.wav"); + sound_hit2 = gi.soundindex("gek/gk_atck3.wav"); + sound_death = gi.soundindex("gek/gk_deth1.wav"); + sound_pain1 = gi.soundindex("gek/gk_pain1.wav"); + sound_sight = gi.soundindex("gek/gk_sght1.wav"); + sound_search = gi.soundindex("gek/gk_idle1.wav"); + sound_step1 = gi.soundindex("gek/gk_step1.wav"); + sound_step2 = gi.soundindex("gek/gk_step2.wav"); + sound_step3 = gi.soundindex("gek/gk_step3.wav"); + sound_thud = gi.soundindex("mutant/thud1.wav"); + + sound_chantlow = gi.soundindex("gek/gek_low.wav"); + sound_chantmid = gi.soundindex("gek/gek_mid.wav"); + sound_chanthigh = gi.soundindex("gek/gek_high.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gekk/tris.md2"); + VectorSet(self->mins, -24, -24, -24); + VectorSet(self->maxs, 24, 24, 24); + + gi.modelindex("models/objects/gekkgib/pelvis/tris.md2"); + gi.modelindex("models/objects/gekkgib/arm/tris.md2"); + gi.modelindex("models/objects/gekkgib/torso/tris.md2"); + gi.modelindex("models/objects/gekkgib/claw/tris.md2"); + gi.modelindex("models/objects/gekkgib/leg/tris.md2"); + gi.modelindex("models/objects/gekkgib/head/tris.md2"); + + self->health = 125; + self->gib_health = -30; + self->mass = 300; + + self->pain = gekk_pain; + self->die = gekk_die; + + self->monsterinfo.stand = gekk_stand; + + self->monsterinfo.walk = gekk_walk; + self->monsterinfo.run = gekk_run_start; + self->monsterinfo.dodge = gekk_dodge; + self->monsterinfo.attack = gekk_jump; + self->monsterinfo.melee = gekk_melee; + self->monsterinfo.sight = gekk_sight; + + self->monsterinfo.search = gekk_search; + self->monsterinfo.idle = gekk_idle; + self->monsterinfo.checkattack = gekk_checkattack; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &gekk_move_stand; + + self->monsterinfo.scale = MODEL_SCALE; + walkmonster_start(self); +} + +void +water_to_land(edict_t *self) +{ + if (!self) + { + return; + } + + self->flags &= ~FL_SWIM; + self->yaw_speed = 20; + self->viewheight = 25; + + self->monsterinfo.currentmove = &gekk_move_leapatk2; + + VectorSet(self->mins, -24, -24, -24); + VectorSet(self->maxs, 24, 24, 24); +} + +void +land_to_water(edict_t *self) +{ + if (!self) + { + return; + } + + self->flags |= FL_SWIM; + self->yaw_speed = 10; + self->viewheight = 10; + + self->monsterinfo.currentmove = &gekk_move_swim_start; + + VectorSet(self->mins, -24, -24, -24); + VectorSet(self->maxs, 24, 24, 16); +} diff --git a/src/game/monster/gekk/gekk.h b/src/game/monster/gekk/gekk.h new file mode 100644 index 000000000..8d926661f --- /dev/null +++ b/src/game/monster/gekk/gekk.h @@ -0,0 +1,379 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Gekk animations. + * + * ======================================================================= + */ + +#define FRAME_stand_01 0 +#define FRAME_stand_02 1 +#define FRAME_stand_03 2 +#define FRAME_stand_04 3 +#define FRAME_stand_05 4 +#define FRAME_stand_06 5 +#define FRAME_stand_07 6 +#define FRAME_stand_08 7 +#define FRAME_stand_09 8 +#define FRAME_stand_10 9 +#define FRAME_stand_11 10 +#define FRAME_stand_12 11 +#define FRAME_stand_13 12 +#define FRAME_stand_14 13 +#define FRAME_stand_15 14 +#define FRAME_stand_16 15 +#define FRAME_stand_17 16 +#define FRAME_stand_18 17 +#define FRAME_stand_19 18 +#define FRAME_stand_20 19 +#define FRAME_stand_21 20 +#define FRAME_stand_22 21 +#define FRAME_stand_23 22 +#define FRAME_stand_24 23 +#define FRAME_stand_25 24 +#define FRAME_stand_26 25 +#define FRAME_stand_27 26 +#define FRAME_stand_28 27 +#define FRAME_stand_29 28 +#define FRAME_stand_30 29 +#define FRAME_stand_31 30 +#define FRAME_stand_32 31 +#define FRAME_stand_33 32 +#define FRAME_stand_34 33 +#define FRAME_stand_35 34 +#define FRAME_stand_36 35 +#define FRAME_stand_37 36 +#define FRAME_stand_38 37 +#define FRAME_stand_39 38 +#define FRAME_run_01 39 +#define FRAME_run_02 40 +#define FRAME_run_03 41 +#define FRAME_run_04 42 +#define FRAME_run_05 43 +#define FRAME_run_06 44 +#define FRAME_clawatk3_01 45 +#define FRAME_clawatk3_02 46 +#define FRAME_clawatk3_03 47 +#define FRAME_clawatk3_04 48 +#define FRAME_clawatk3_05 49 +#define FRAME_clawatk3_06 50 +#define FRAME_clawatk3_07 51 +#define FRAME_clawatk3_08 52 +#define FRAME_clawatk3_09 53 +#define FRAME_clawatk4_01 54 +#define FRAME_clawatk4_02 55 +#define FRAME_clawatk4_03 56 +#define FRAME_clawatk4_04 57 +#define FRAME_clawatk4_05 58 +#define FRAME_clawatk4_06 59 +#define FRAME_clawatk4_07 60 +#define FRAME_clawatk4_08 61 +#define FRAME_clawatk5_01 62 +#define FRAME_clawatk5_02 63 +#define FRAME_clawatk5_03 64 +#define FRAME_clawatk5_04 65 +#define FRAME_clawatk5_05 66 +#define FRAME_clawatk5_06 67 +#define FRAME_clawatk5_07 68 +#define FRAME_clawatk5_08 69 +#define FRAME_clawatk5_09 70 +#define FRAME_leapatk_01 71 +#define FRAME_leapatk_02 72 +#define FRAME_leapatk_03 73 +#define FRAME_leapatk_04 74 +#define FRAME_leapatk_05 75 +#define FRAME_leapatk_06 76 +#define FRAME_leapatk_07 77 +#define FRAME_leapatk_08 78 +#define FRAME_leapatk_09 79 +#define FRAME_leapatk_10 80 +#define FRAME_leapatk_11 81 +#define FRAME_leapatk_12 82 +#define FRAME_leapatk_13 83 +#define FRAME_leapatk_14 84 +#define FRAME_leapatk_15 85 +#define FRAME_leapatk_16 86 +#define FRAME_leapatk_17 87 +#define FRAME_leapatk_18 88 +#define FRAME_leapatk_19 89 +#define FRAME_pain3_01 90 +#define FRAME_pain3_02 91 +#define FRAME_pain3_03 92 +#define FRAME_pain3_04 93 +#define FRAME_pain3_05 94 +#define FRAME_pain3_06 95 +#define FRAME_pain3_07 96 +#define FRAME_pain3_08 97 +#define FRAME_pain3_09 98 +#define FRAME_pain3_10 99 +#define FRAME_pain3_11 100 +#define FRAME_pain4_01 101 +#define FRAME_pain4_02 102 +#define FRAME_pain4_03 103 +#define FRAME_pain4_04 104 +#define FRAME_pain4_05 105 +#define FRAME_pain4_06 106 +#define FRAME_pain4_07 107 +#define FRAME_pain4_08 108 +#define FRAME_pain4_09 109 +#define FRAME_pain4_10 110 +#define FRAME_pain4_11 111 +#define FRAME_pain4_12 112 +#define FRAME_pain4_13 113 +#define FRAME_death1_01 114 +#define FRAME_death1_02 115 +#define FRAME_death1_03 116 +#define FRAME_death1_04 117 +#define FRAME_death1_05 118 +#define FRAME_death1_06 119 +#define FRAME_death1_07 120 +#define FRAME_death1_08 121 +#define FRAME_death1_09 122 +#define FRAME_death1_10 123 +#define FRAME_death2_01 124 +#define FRAME_death2_02 125 +#define FRAME_death2_03 126 +#define FRAME_death2_04 127 +#define FRAME_death2_05 128 +#define FRAME_death2_06 129 +#define FRAME_death2_07 130 +#define FRAME_death2_08 131 +#define FRAME_death2_09 132 +#define FRAME_death2_10 133 +#define FRAME_death2_11 134 +#define FRAME_death3_01 135 +#define FRAME_death3_02 136 +#define FRAME_death3_03 137 +#define FRAME_death3_04 138 +#define FRAME_death3_05 139 +#define FRAME_death3_06 140 +#define FRAME_death3_07 141 +#define FRAME_death4_01 142 +#define FRAME_death4_02 143 +#define FRAME_death4_03 144 +#define FRAME_death4_04 145 +#define FRAME_death4_05 146 +#define FRAME_death4_06 147 +#define FRAME_death4_07 148 +#define FRAME_death4_08 149 +#define FRAME_death4_09 150 +#define FRAME_death4_10 151 +#define FRAME_death4_11 152 +#define FRAME_death4_12 153 +#define FRAME_death4_13 154 +#define FRAME_death4_14 155 +#define FRAME_death4_15 156 +#define FRAME_death4_16 157 +#define FRAME_death4_17 158 +#define FRAME_death4_18 159 +#define FRAME_death4_19 160 +#define FRAME_death4_20 161 +#define FRAME_death4_21 162 +#define FRAME_death4_22 163 +#define FRAME_death4_23 164 +#define FRAME_death4_24 165 +#define FRAME_death4_25 166 +#define FRAME_death4_26 167 +#define FRAME_death4_27 168 +#define FRAME_death4_28 169 +#define FRAME_death4_29 170 +#define FRAME_death4_30 171 +#define FRAME_death4_31 172 +#define FRAME_death4_32 173 +#define FRAME_death4_33 174 +#define FRAME_death4_34 175 +#define FRAME_death4_35 176 +#define FRAME_rduck_01 177 +#define FRAME_rduck_02 178 +#define FRAME_rduck_03 179 +#define FRAME_rduck_04 180 +#define FRAME_rduck_05 181 +#define FRAME_rduck_06 182 +#define FRAME_rduck_07 183 +#define FRAME_rduck_08 184 +#define FRAME_rduck_09 185 +#define FRAME_rduck_10 186 +#define FRAME_rduck_11 187 +#define FRAME_rduck_12 188 +#define FRAME_rduck_13 189 +#define FRAME_lduck_01 190 +#define FRAME_lduck_02 191 +#define FRAME_lduck_03 192 +#define FRAME_lduck_04 193 +#define FRAME_lduck_05 194 +#define FRAME_lduck_06 195 +#define FRAME_lduck_07 196 +#define FRAME_lduck_08 197 +#define FRAME_lduck_09 198 +#define FRAME_lduck_10 199 +#define FRAME_lduck_11 200 +#define FRAME_lduck_12 201 +#define FRAME_lduck_13 202 +#define FRAME_idle_01 203 +#define FRAME_idle_02 204 +#define FRAME_idle_03 205 +#define FRAME_idle_04 206 +#define FRAME_idle_05 207 +#define FRAME_idle_06 208 +#define FRAME_idle_07 209 +#define FRAME_idle_08 210 +#define FRAME_idle_09 211 +#define FRAME_idle_10 212 +#define FRAME_idle_11 213 +#define FRAME_idle_12 214 +#define FRAME_idle_13 215 +#define FRAME_idle_14 216 +#define FRAME_idle_15 217 +#define FRAME_idle_16 218 +#define FRAME_idle_17 219 +#define FRAME_idle_18 220 +#define FRAME_idle_19 221 +#define FRAME_idle_20 222 +#define FRAME_idle_21 223 +#define FRAME_idle_22 224 +#define FRAME_idle_23 225 +#define FRAME_idle_24 226 +#define FRAME_idle_25 227 +#define FRAME_idle_26 228 +#define FRAME_idle_27 229 +#define FRAME_idle_28 230 +#define FRAME_idle_29 231 +#define FRAME_idle_30 232 +#define FRAME_idle_31 233 +#define FRAME_idle_32 234 +#define FRAME_spit_01 235 +#define FRAME_spit_02 236 +#define FRAME_spit_03 237 +#define FRAME_spit_04 238 +#define FRAME_spit_05 239 +#define FRAME_spit_06 240 +#define FRAME_spit_07 241 +#define FRAME_amb_01 242 +#define FRAME_amb_02 243 +#define FRAME_amb_03 244 +#define FRAME_amb_04 245 +#define FRAME_wdeath_01 246 +#define FRAME_wdeath_02 247 +#define FRAME_wdeath_03 248 +#define FRAME_wdeath_04 249 +#define FRAME_wdeath_05 250 +#define FRAME_wdeath_06 251 +#define FRAME_wdeath_07 252 +#define FRAME_wdeath_08 253 +#define FRAME_wdeath_09 254 +#define FRAME_wdeath_10 255 +#define FRAME_wdeath_11 256 +#define FRAME_wdeath_12 257 +#define FRAME_wdeath_13 258 +#define FRAME_wdeath_14 259 +#define FRAME_wdeath_15 260 +#define FRAME_wdeath_16 261 +#define FRAME_wdeath_17 262 +#define FRAME_wdeath_18 263 +#define FRAME_wdeath_19 264 +#define FRAME_wdeath_20 265 +#define FRAME_wdeath_21 266 +#define FRAME_wdeath_22 267 +#define FRAME_wdeath_23 268 +#define FRAME_wdeath_24 269 +#define FRAME_wdeath_25 270 +#define FRAME_wdeath_26 271 +#define FRAME_wdeath_27 272 +#define FRAME_wdeath_28 273 +#define FRAME_wdeath_29 274 +#define FRAME_wdeath_30 275 +#define FRAME_wdeath_31 276 +#define FRAME_wdeath_32 277 +#define FRAME_wdeath_33 278 +#define FRAME_wdeath_34 279 +#define FRAME_wdeath_35 280 +#define FRAME_wdeath_36 281 +#define FRAME_wdeath_37 282 +#define FRAME_wdeath_38 283 +#define FRAME_wdeath_39 284 +#define FRAME_wdeath_40 285 +#define FRAME_wdeath_41 286 +#define FRAME_wdeath_42 287 +#define FRAME_wdeath_43 288 +#define FRAME_wdeath_44 289 +#define FRAME_wdeath_45 290 +#define FRAME_swim_01 291 +#define FRAME_swim_02 292 +#define FRAME_swim_03 293 +#define FRAME_swim_04 294 +#define FRAME_swim_05 295 +#define FRAME_swim_06 296 +#define FRAME_swim_07 297 +#define FRAME_swim_08 298 +#define FRAME_swim_09 299 +#define FRAME_swim_10 300 +#define FRAME_swim_11 301 +#define FRAME_swim_12 302 +#define FRAME_swim_13 303 +#define FRAME_swim_14 304 +#define FRAME_swim_15 305 +#define FRAME_swim_16 306 +#define FRAME_swim_17 307 +#define FRAME_swim_18 308 +#define FRAME_swim_19 309 +#define FRAME_swim_20 310 +#define FRAME_swim_21 311 +#define FRAME_swim_22 312 +#define FRAME_swim_23 313 +#define FRAME_swim_24 314 +#define FRAME_swim_25 315 +#define FRAME_swim_26 316 +#define FRAME_swim_27 317 +#define FRAME_swim_28 318 +#define FRAME_swim_29 319 +#define FRAME_swim_30 320 +#define FRAME_swim_31 321 +#define FRAME_swim_32 322 +#define FRAME_attack_01 323 +#define FRAME_attack_02 324 +#define FRAME_attack_03 325 +#define FRAME_attack_04 326 +#define FRAME_attack_05 327 +#define FRAME_attack_06 328 +#define FRAME_attack_07 329 +#define FRAME_attack_08 330 +#define FRAME_attack_09 331 +#define FRAME_attack_10 332 +#define FRAME_attack_11 333 +#define FRAME_attack_12 334 +#define FRAME_attack_13 335 +#define FRAME_attack_14 336 +#define FRAME_attack_15 337 +#define FRAME_attack_16 338 +#define FRAME_attack_17 339 +#define FRAME_attack_18 340 +#define FRAME_attack_19 341 +#define FRAME_attack_20 342 +#define FRAME_attack_21 343 +#define FRAME_pain_01 344 +#define FRAME_pain_02 345 +#define FRAME_pain_03 346 +#define FRAME_pain_04 347 +#define FRAME_pain_05 348 +#define FRAME_pain_06 349 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/gladiator/gladb.c b/src/game/monster/gladiator/gladb.c new file mode 100644 index 000000000..0bf0dbb2f --- /dev/null +++ b/src/game/monster/gladiator/gladb.c @@ -0,0 +1,560 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Gladiator B. (Special variant of gladiator only found in Xatrix) + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "gladiator.h" + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_gun; +static int sound_cleaver_swing; +static int sound_cleaver_hit; +static int sound_cleaver_miss; +static int sound_idle; +static int sound_search; +static int sound_sight; + +void +gladb_idle(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void +gladb_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +gladb_search(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void +gladb_cleaver_swing(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0); +} + +static mframe_t gladb_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} +}; + +mmove_t gladb_move_stand = { + FRAME_stand1, + FRAME_stand7, + gladb_frames_stand, + NULL +}; + +void +gladb_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gladb_move_stand; +} + +static mframe_t gladb_frames_walk[] = { + {ai_walk, 15, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 12, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 1, NULL}, + {ai_walk, 8, NULL} +}; + +mmove_t gladb_move_walk = { + FRAME_walk1, + FRAME_walk16, + gladb_frames_walk, + NULL +}; + +void +gladb_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gladb_move_walk; +} + +static mframe_t gladb_frames_run[] = { + {ai_run, 23, NULL}, + {ai_run, 14, NULL}, + {ai_run, 14, NULL}, + {ai_run, 21, NULL}, + {ai_run, 12, NULL}, + {ai_run, 13, NULL} +}; + +mmove_t gladb_move_run = { + FRAME_run1, + FRAME_run6, + gladb_frames_run, + NULL +}; + +void +gladb_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &gladb_move_stand; + } + else + { + self->monsterinfo.currentmove = &gladb_move_run; + } +} + +void +GladbMelee(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->mins[0], -4); + + if (fire_hit(self, aim, (20 + (rand() % 5)), 300)) + { + gi.sound(self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0); + } +} + +static mframe_t gladb_frames_attack_melee[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, gladb_cleaver_swing}, + {ai_charge, 0, NULL}, + {ai_charge, 0, GladbMelee}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, gladb_cleaver_swing}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, GladbMelee}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t gladb_move_attack_melee = { + FRAME_melee1, + FRAME_melee17, + gladb_frames_attack_melee, + gladb_run +}; + +void +gladb_melee(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gladb_move_attack_melee; +} + +void +gladbGun(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], + forward, right, start); + + /* calc direction to where we targted */ + VectorSubtract(self->pos1, start, dir); + VectorNormalize(dir); + + fire_plasma(self, start, dir, 100, 725, 60, 60); +} + +void +gladbGun_check(edict_t *self) +{ + if (!self) + { + return; + } + + if (skill->value == SKILL_HARDPLUS) + { + gladbGun(self); + } +} + +static mframe_t gladb_frames_attack_gun[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, gladbGun}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, gladbGun}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, gladbGun_check} +}; + +mmove_t gladb_move_attack_gun = { + FRAME_attack1, + FRAME_attack9, + gladb_frames_attack_gun, + gladb_run +}; + +void +gladb_attack(edict_t *self) +{ + float range; + vec3_t v; + + if (!self) + { + return; + } + + /* a small safe zone + but not for stand-ground ones since players can + abuse it by standing still inside this range + */ + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + { + VectorSubtract(self->s.origin, self->enemy->s.origin, v); + range = VectorLength(v); + + if (range <= (MELEE_DISTANCE + 32)) + { + return; + } + } + + /* charge up the railgun */ + gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); + VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */ + self->pos1[2] += self->enemy->viewheight; + self->monsterinfo.currentmove = &gladb_move_attack_gun; +} + +static mframe_t gladb_frames_pain[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t gladb_move_pain = { + FRAME_pain1, + FRAME_pain6, + gladb_frames_pain, gladb_run +}; + +static mframe_t gladb_frames_pain_air[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t gladb_move_pain_air = { + FRAME_painup1, + FRAME_painup7, + gladb_frames_pain_air, + gladb_run +}; + +void +gladb_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && + (self->monsterinfo.currentmove == &gladb_move_pain)) + { + self->monsterinfo.currentmove = &gladb_move_pain_air; + } + + return; + } + + self->pain_debounce_time = level.time + 3; + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + + if (self->velocity[2] > 100) + { + self->monsterinfo.currentmove = &gladb_move_pain_air; + } + else + { + self->monsterinfo.currentmove = &gladb_move_pain; + } +} + +void +gladb_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +static mframe_t gladb_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t gladb_move_death = { + FRAME_death1, + FRAME_death22, + gladb_frames_death, + gladb_dead +}; + +void +gladb_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage /*unused */, + vec3_t point) +{ + int n; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 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, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &gladb_move_death; +} + +/* + * QUAKED monster_gladb (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight + */ +void +SP_monster_gladb(edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("gladiator/pain.wav"); + sound_pain2 = gi.soundindex("gladiator/gldpain2.wav"); + sound_die = gi.soundindex("gladiator/glddeth2.wav"); + sound_gun = gi.soundindex("weapons/plasshot.wav"); + + sound_cleaver_swing = gi.soundindex("gladiator/melee1.wav"); + sound_cleaver_hit = gi.soundindex("gladiator/melee2.wav"); + sound_cleaver_miss = gi.soundindex("gladiator/melee3.wav"); + sound_idle = gi.soundindex("gladiator/gldidle1.wav"); + sound_search = gi.soundindex("gladiator/gldsrch1.wav"); + sound_sight = gi.soundindex("gladiator/sight.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gladb/tris.md2"); + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, 64); + + self->health = 800; + self->gib_health = -175; + self->mass = 350; + + self->pain = gladb_pain; + self->die = gladb_die; + + self->monsterinfo.stand = gladb_stand; + self->monsterinfo.walk = gladb_walk; + self->monsterinfo.run = gladb_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = gladb_attack; + self->monsterinfo.melee = gladb_melee; + self->monsterinfo.sight = gladb_sight; + self->monsterinfo.idle = gladb_idle; + self->monsterinfo.search = gladb_search; + + gi.linkentity(self); + self->monsterinfo.currentmove = &gladb_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 400; + + walkmonster_start(self); +} diff --git a/src/game/monster/gladiator/gladiator.c b/src/game/monster/gladiator/gladiator.c new file mode 100644 index 000000000..52b475cf3 --- /dev/null +++ b/src/game/monster/gladiator/gladiator.c @@ -0,0 +1,614 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Gladiator. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "gladiator.h" + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_gun; +static int sound_cleaver_swing; +static int sound_cleaver_hit; +static int sound_cleaver_miss; +static int sound_idle; +static int sound_search; +static int sound_sight; + +static int sound_step; +static int sound_step2; + +void +gladiator_footstep(edict_t *self) +{ + if (!g_monsterfootsteps->value) + return; + + // Lazy loading for savegame compatibility. + if (sound_step == 0 || sound_step2 == 0) + { + sound_step = gi.soundindex("gladiator/step1.wav"); + sound_step2 = gi.soundindex("gladiator/step2.wav"); + } + + if (randk() % 2 == 0) + { + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + } +} + + +void +gladiator_idle(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void +gladiator_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +gladiator_search(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void +gladiator_cleaver_swing(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0); +} + +static mframe_t gladiator_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} +}; + +mmove_t gladiator_move_stand = +{ + FRAME_stand1, + FRAME_stand7, + gladiator_frames_stand, + NULL +}; + +void +gladiator_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gladiator_move_stand; +} + +static mframe_t gladiator_frames_walk[] = { + {ai_walk, 15, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 2, gladiator_footstep}, + {ai_walk, 0, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 12, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 2, gladiator_footstep}, + {ai_walk, 2, NULL}, + {ai_walk, 1, NULL}, + {ai_walk, 8, NULL} +}; + +mmove_t gladiator_move_walk = +{ + FRAME_walk1, + FRAME_walk16, + gladiator_frames_walk, + NULL +}; + +void +gladiator_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gladiator_move_walk; +} + +static mframe_t gladiator_frames_run[] = { + {ai_run, 23, NULL}, + {ai_run, 14, NULL}, + {ai_run, 14, gladiator_footstep}, + {ai_run, 21, NULL}, + {ai_run, 12, NULL}, + {ai_run, 13, gladiator_footstep} +}; + +mmove_t gladiator_move_run = +{ + FRAME_run1, + FRAME_run6, + gladiator_frames_run, + NULL +}; + +void +gladiator_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &gladiator_move_stand; + } + else + { + self->monsterinfo.currentmove = &gladiator_move_run; + } +} + +void +GaldiatorMelee(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->mins[0], -4); + + if (fire_hit(self, aim, (20 + (randk() % 5)), 300)) + { + gi.sound(self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0); + } +} + +static mframe_t gladiator_frames_attack_melee[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, gladiator_cleaver_swing}, + {ai_charge, 0, NULL}, + {ai_charge, 0, GaldiatorMelee}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, gladiator_cleaver_swing}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, GaldiatorMelee}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t gladiator_move_attack_melee = +{ + FRAME_melee1, + FRAME_melee17, + gladiator_frames_attack_melee, + gladiator_run +}; + +void +gladiator_melee(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gladiator_move_attack_melee; +} + +void +GladiatorGun(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], + forward, right, start); + + /* calc direction to where we targted */ + VectorSubtract(self->pos1, start, dir); + VectorNormalize(dir); + + monster_fire_railgun(self, start, dir, 50, 100, MZ2_GLADIATOR_RAILGUN_1); +} + +static mframe_t gladiator_frames_attack_gun[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, GladiatorGun}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t gladiator_move_attack_gun = +{ + FRAME_attack1, + FRAME_attack9, + gladiator_frames_attack_gun, + gladiator_run +}; + +void +gladiator_attack(edict_t *self) +{ + float range; + vec3_t v; + + if (!self) + { + return; + } + + /* a small safe zone + but not for stand-ground ones since players can + abuse it by standing still inside this range + */ + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + { + VectorSubtract(self->s.origin, self->enemy->s.origin, v); + range = VectorLength(v); + + if (range <= (MELEE_DISTANCE + 32)) + { + return; + } + } + + /* charge up the railgun */ + gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0); + VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */ + self->pos1[2] += self->enemy->viewheight; + self->monsterinfo.currentmove = &gladiator_move_attack_gun; +} + +static mframe_t gladiator_frames_pain[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t gladiator_move_pain = +{ + FRAME_pain1, + FRAME_pain6, + gladiator_frames_pain, + gladiator_run +}; + +static mframe_t gladiator_frames_pain_air[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t gladiator_move_pain_air = +{ + FRAME_painup1, + FRAME_painup7, + gladiator_frames_pain_air, + gladiator_run +}; + +void +gladiator_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && + (self->monsterinfo.currentmove == &gladiator_move_pain)) + { + self->monsterinfo.currentmove = &gladiator_move_pain_air; + } + + return; + } + + self->pain_debounce_time = level.time + 3; + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (self->velocity[2] > 100) + { + self->monsterinfo.currentmove = &gladiator_move_pain_air; + } + else + { + self->monsterinfo.currentmove = &gladiator_move_pain; + } +} + +void +gladiator_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +static mframe_t gladiator_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t gladiator_move_death = +{ + FRAME_death1, + FRAME_death22, + gladiator_frames_death, + gladiator_dead +}; + +void +gladiator_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage /*unused */, + vec3_t point) +{ + int n; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex( + "misc/udeath.wav"), 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, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &gladiator_move_death; +} + +qboolean +gladiator_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + return false; +} + +/* + * QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight + */ +void +SP_monster_gladiator(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + + sound_pain1 = gi.soundindex("gladiator/pain.wav"); + sound_pain2 = gi.soundindex("gladiator/gldpain2.wav"); + sound_die = gi.soundindex("gladiator/glddeth2.wav"); + sound_gun = gi.soundindex("gladiator/railgun.wav"); + sound_cleaver_swing = gi.soundindex("gladiator/melee1.wav"); + sound_cleaver_hit = gi.soundindex("gladiator/melee2.wav"); + sound_cleaver_miss = gi.soundindex("gladiator/melee3.wav"); + sound_idle = gi.soundindex("gladiator/gldidle1.wav"); + sound_search = gi.soundindex("gladiator/gldsrch1.wav"); + sound_sight = gi.soundindex("gladiator/sight.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gladiatr/tris.md2"); + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, 64); + + self->health = 400; + self->gib_health = -175; + self->mass = 400; + + self->pain = gladiator_pain; + self->die = gladiator_die; + + self->monsterinfo.stand = gladiator_stand; + self->monsterinfo.walk = gladiator_walk; + self->monsterinfo.run = gladiator_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = gladiator_attack; + self->monsterinfo.melee = gladiator_melee; + self->monsterinfo.sight = gladiator_sight; + self->monsterinfo.idle = gladiator_idle; + self->monsterinfo.search = gladiator_search; + self->monsterinfo.blocked = gladiator_blocked; + + gi.linkentity(self); + self->monsterinfo.currentmove = &gladiator_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/src/game/monster/gladiator/gladiator.h b/src/game/monster/gladiator/gladiator.h new file mode 100644 index 000000000..f74445b14 --- /dev/null +++ b/src/game/monster/gladiator/gladiator.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Gladiator animations. + * + * ======================================================================= + */ + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_walk1 7 +#define FRAME_walk2 8 +#define FRAME_walk3 9 +#define FRAME_walk4 10 +#define FRAME_walk5 11 +#define FRAME_walk6 12 +#define FRAME_walk7 13 +#define FRAME_walk8 14 +#define FRAME_walk9 15 +#define FRAME_walk10 16 +#define FRAME_walk11 17 +#define FRAME_walk12 18 +#define FRAME_walk13 19 +#define FRAME_walk14 20 +#define FRAME_walk15 21 +#define FRAME_walk16 22 +#define FRAME_run1 23 +#define FRAME_run2 24 +#define FRAME_run3 25 +#define FRAME_run4 26 +#define FRAME_run5 27 +#define FRAME_run6 28 +#define FRAME_melee1 29 +#define FRAME_melee2 30 +#define FRAME_melee3 31 +#define FRAME_melee4 32 +#define FRAME_melee5 33 +#define FRAME_melee6 34 +#define FRAME_melee7 35 +#define FRAME_melee8 36 +#define FRAME_melee9 37 +#define FRAME_melee10 38 +#define FRAME_melee11 39 +#define FRAME_melee12 40 +#define FRAME_melee13 41 +#define FRAME_melee14 42 +#define FRAME_melee15 43 +#define FRAME_melee16 44 +#define FRAME_melee17 45 +#define FRAME_attack1 46 +#define FRAME_attack2 47 +#define FRAME_attack3 48 +#define FRAME_attack4 49 +#define FRAME_attack5 50 +#define FRAME_attack6 51 +#define FRAME_attack7 52 +#define FRAME_attack8 53 +#define FRAME_attack9 54 +#define FRAME_pain1 55 +#define FRAME_pain2 56 +#define FRAME_pain3 57 +#define FRAME_pain4 58 +#define FRAME_pain5 59 +#define FRAME_pain6 60 +#define FRAME_death1 61 +#define FRAME_death2 62 +#define FRAME_death3 63 +#define FRAME_death4 64 +#define FRAME_death5 65 +#define FRAME_death6 66 +#define FRAME_death7 67 +#define FRAME_death8 68 +#define FRAME_death9 69 +#define FRAME_death10 70 +#define FRAME_death11 71 +#define FRAME_death12 72 +#define FRAME_death13 73 +#define FRAME_death14 74 +#define FRAME_death15 75 +#define FRAME_death16 76 +#define FRAME_death17 77 +#define FRAME_death18 78 +#define FRAME_death19 79 +#define FRAME_death20 80 +#define FRAME_death21 81 +#define FRAME_death22 82 +#define FRAME_painup1 83 +#define FRAME_painup2 84 +#define FRAME_painup3 85 +#define FRAME_painup4 86 +#define FRAME_painup5 87 +#define FRAME_painup6 88 +#define FRAME_painup7 89 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/guardian/guardian.c b/src/game/monster/guardian/guardian.c new file mode 100644 index 000000000..9bcede86a --- /dev/null +++ b/src/game/monster/guardian/guardian.c @@ -0,0 +1,786 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * The Guardian. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "guardian.h" + +void BossExplode(edict_t *self); + +static int sound_charge; +static int sound_spin_loop; + +// +// stand +// + +static mframe_t guardian_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}, + {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 guardian_move_stand = +{ + FRAME_idle1, + FRAME_idle52, + guardian_frames_stand, + NULL +}; + +void +guardian_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &guardian_move_stand; +} + +// +// walk +// + +static int sound_step; + +void +guardian_footstep(edict_t *self) +{ + gi.sound(self, CHAN_BODY, sound_step, 1.f, ATTN_NORM, 0.0f); +} + +static mframe_t guardian_frames_walk[] = { + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, guardian_footstep}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 8, guardian_footstep}, + {ai_walk, 8, NULL} +}; + +mmove_t guardian_move_walk = +{ + FRAME_walk1, + FRAME_walk19, + guardian_frames_walk, + NULL +}; + +void +guardian_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &guardian_move_walk; +} + +// +// run +// + +static mframe_t guardian_frames_run[] = { + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, guardian_footstep}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, NULL}, + {ai_run, 8, guardian_footstep}, + {ai_run, 8, NULL} +}; + +mmove_t guardian_move_run = +{ + FRAME_walk1, + FRAME_walk19, + guardian_frames_run, + NULL +}; + +void +guardian_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &guardian_move_stand; + return; + } + + self->monsterinfo.currentmove = &guardian_move_run; +} + +// +// pain +// + +static mframe_t guardian_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t guardian_move_pain1 = +{ + FRAME_pain1_1, + FRAME_pain1_8, + guardian_frames_pain1, + guardian_run +}; + +void +guardian_pain(edict_t *self, edict_t *other /* other */, + float kick /* other */, int damage) +{ + if (!self) + { + return; + } + + if (damage <= 10) + { + return; + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + if (damage <= 75) + { + if (random() > 0.2) + { + return; + } + } + + /* don't go into pain while attacking */ + if ((self->s.frame >= FRAME_atk1_spin1) && (self->s.frame <= FRAME_atk1_spin15)) + { + return; + } + + if ((self->s.frame >= FRAME_atk2_fire1) && (self->s.frame <= FRAME_atk2_fire4)) + { + return; + } + + if ((self->s.frame >= FRAME_kick_in1) && (self->s.frame <= FRAME_kick_in13)) + { + return; + } + + self->pain_debounce_time = level.time + 3; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + self->monsterinfo.currentmove = &guardian_move_pain1; +} + +static mframe_t guardian_frames_atk1_out[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t guardian_atk1_out = +{ + FRAME_atk1_out1, + FRAME_atk1_out3, + guardian_frames_atk1_out, + guardian_run +}; + +void +guardian_atk1_finish(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &guardian_atk1_out; +} + +void +guardian_atk1_charge(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_charge, 1.f, ATTN_NORM, 0.f); +} + +void +guardian_fire_blaster(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start; + int id = MZ2_TANK_BLASTER_1; + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[id], forward, right, start); + VectorCopy(self->enemy->s.origin, target); + target[2] += self->enemy->viewheight; + for (int i = 0; i < 3; i++) + { + target[i] += (randk() % 10) - 5.f; + } + + /* calc direction to where we targeted */ + VectorSubtract(target, start, forward); + VectorNormalize(forward); + + monster_fire_blaster(self, start, forward, 2, 1000, id, (self->s.frame % 4) ? 0 : EF_HYPERBLASTER); + + if (self->enemy && self->enemy->health > 0 && + self->s.frame == FRAME_atk1_spin12 && self->timestamp > level.time && visible(self, self->enemy)) + self->monsterinfo.nextframe = FRAME_atk1_spin5; +} + +void +guardian_hyper_sound(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_spin_loop, 1.f, ATTN_NORM, 0.f); +} + +static mframe_t guardian_frames_atk1_spin[] = { + {ai_charge, 0, guardian_atk1_charge}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, guardian_hyper_sound}, + {ai_charge, 0, guardian_fire_blaster}, + {ai_charge, 0, guardian_fire_blaster}, + {ai_charge, 0, guardian_fire_blaster}, + {ai_charge, 0, guardian_fire_blaster}, + {ai_charge, 0, guardian_fire_blaster}, + {ai_charge, 0, guardian_fire_blaster}, + {ai_charge, 0, guardian_fire_blaster}, + {ai_charge, 0, guardian_fire_blaster}, + {ai_charge, 0}, + {ai_charge, 0}, + {ai_charge, 0} +}; + +mmove_t guardian_move_atk1_spin = +{ + FRAME_atk1_spin1, + FRAME_atk1_spin15, + guardian_frames_atk1_spin, + guardian_atk1_finish +}; + +void +guardian_atk1(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &guardian_move_atk1_spin; + self->timestamp = level.time + 0.650f + (randk() % 10 ) / 10.f * 1.5f; +} + +static mframe_t guardian_frames_atk1_in[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t guardian_move_atk1_in = +{ + FRAME_atk1_in1, + FRAME_atk1_in3, + guardian_frames_atk1_in, + guardian_atk1 +}; + +static mframe_t guardian_frames_atk2_out[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, guardian_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t guardian_move_atk2_out = +{ + FRAME_atk2_out1, + FRAME_atk2_out7, + guardian_frames_atk2_out, + guardian_run +}; + +void +guardian_atk2_out(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &guardian_move_atk2_out; +} + +static int sound_laser; + +static vec3_t laser_positions[] = { + {125.0f, -70.f, 60.f}, + {112.0f, -62.f, 60.f} +}; + +void +guardian_laser_fire(edict_t *self) +{ + vec3_t forward, right, up; + vec3_t tempang, start; + vec3_t dir, angles, end; + vec3_t tempvec; + edict_t *ent; + + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_laser, 1.f, ATTN_NORM, 0.f); + + if (random() > 0.8) + { + gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), 1, ATTN_STATIC, 0); + } + + VectorCopy(self->s.origin, start); + VectorCopy(self->enemy->s.origin, end); + VectorSubtract(end, start, dir); + vectoangles(dir, angles); + VectorCopy(laser_positions[1 - (self->s.frame & 1)], tempvec); + + ent = G_Spawn(); + VectorCopy(self->s.origin, ent->s.origin); + VectorCopy(angles, tempang); + AngleVectors(tempang, forward, right, up); + VectorCopy(tempang, ent->s.angles); + VectorCopy(ent->s.origin, start); + + VectorMA(start, tempvec[0] + 2, right, start); + VectorMA(start, tempvec[2] + 8, up, start); + VectorMA(start, tempvec[1], forward, start); + + VectorCopy(start, ent->s.origin); + ent->enemy = self->enemy; + ent->owner = self; + + ent->dmg = 25; + + monster_dabeam(ent); +} + +static mframe_t guardian_frames_atk2_fire[] = { + {ai_charge, 0, guardian_laser_fire}, + {ai_charge, 0, guardian_laser_fire}, + {ai_charge, 0, guardian_laser_fire}, + {ai_charge, 0, guardian_laser_fire } +}; + +mmove_t guardian_move_atk2_fire = +{ + FRAME_atk2_fire1, + FRAME_atk2_fire4, + guardian_frames_atk2_fire, + guardian_atk2_out +}; + +void +guardian_atk2(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &guardian_move_atk2_fire; +} + +static mframe_t guardian_frames_atk2_in[] = { + {ai_charge, 0, guardian_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, guardian_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, guardian_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t guardian_move_atk2_in = +{ + FRAME_atk2_in1, + FRAME_atk2_in12, + guardian_frames_atk2_in, + guardian_atk2 +}; + +void +guardian_kick(edict_t *self) +{ + vec3_t aim = {MELEE_DISTANCE, 0, -80}; + fire_hit(self, aim, 85, 700); +} + +static mframe_t guardian_frames_kick[] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, guardian_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, guardian_kick}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, guardian_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t guardian_move_kick = +{ + FRAME_kick_in1, + FRAME_kick_in13, + guardian_frames_kick, + guardian_run +}; + +void +guardian_attack(edict_t *self) +{ + float r; + + if (!self->enemy || !self->enemy->inuse) + { + return; + } + + r = realrange(self, self->enemy); + + if (r > RANGE_NEAR) + { + self->monsterinfo.currentmove = &guardian_move_atk2_in; + } + else if (r < 120.f) + { + self->monsterinfo.currentmove = &guardian_move_kick; + } + else + { + self->monsterinfo.currentmove = &guardian_move_atk1_in; + } +} + +// +// death +// + +void +guardian_explode(edict_t *self) +{ + vec3_t start, pos; + int i; + + VectorAdd(self->s.origin, self->mins, start); + + VectorCopy(self->size, pos); + for (i = 0; i < 3; i++) + { + pos[i] *= random(); + } + + VectorAdd(start, pos, pos); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(pos); + gi.multicast(self->s.origin, MULTICAST_ALL); +} + +static const char *gibs[] = { + "models/monsters/guardian/gib1.md2", + "models/monsters/guardian/gib2.md2", + "models/monsters/guardian/gib3.md2", + "models/monsters/guardian/gib4.md2", + "models/monsters/guardian/gib5.md2", + "models/monsters/guardian/gib6.md2", + "models/monsters/guardian/gib7.md2" +}; + +void +guardian_dead(edict_t *self) +{ + int i, n; + + for (i = 0; i < 3; i++) + { + guardian_explode(self); + } + + for (n = 0; n < 2; n++) + { + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", + 125, GIB_ORGANIC); + } + + for (n = 0; n < 4; n++) + { + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", + 125, GIB_METALLIC); + } + + for (n = 0; n < 6; n++) + { + for (i = 0; i < 2; i++) + { + ThrowGib(self, gibs[n], 125, GIB_METALLIC); + } + } + + ThrowHead(self, gibs[6], 125, GIB_METALLIC); +} + +static mframe_t guardian_frames_death1[FRAME_death26 - FRAME_death1 + 1] = { + {ai_move, 0, BossExplode}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t guardian_move_death = +{ + FRAME_death1, + FRAME_death26, + guardian_frames_death1, + guardian_dead +}; + +void +guardian_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage, vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + /* regular death */ + self->deadflag = true; + self->takedamage = true; + + self->monsterinfo.currentmove = &guardian_move_death; +} + +// +// monster_tank +// + +/* + * QUAKED monster_guardian (1 .5 0) (-96 -96 -66) (96 96 62) Ambush Trigger_Spawn Sight + */ +void +SP_monster_guardian(edict_t *self) +{ + int i; + + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_step = gi.soundindex("zortemp/step.wav"); + sound_charge = gi.soundindex("weapons/hyprbu1a.wav"); + sound_spin_loop = gi.soundindex("weapons/hyprbl1a.wav"); + sound_laser = gi.soundindex("weapons/laser2.wav"); + + for(i = 0; i < sizeof(gibs) / sizeof(*gibs); i++) + { + gi.modelindex(gibs[i]); + } + + self->s.modelindex = gi.modelindex("models/monsters/guardian/tris.md2"); + VectorSet(self->mins, -96, -96, -66); + VectorSet(self->maxs, 96, 96, 62); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 2500; + self->gib_health = -200; + + self->monsterinfo.scale = MODEL_SCALE; + + self->mass = 850; + + self->pain = guardian_pain; + self->die = guardian_die; + self->monsterinfo.stand = guardian_stand; + self->monsterinfo.walk = guardian_walk; + self->monsterinfo.run = guardian_run; + self->monsterinfo.attack = guardian_attack; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &guardian_move_stand; + + walkmonster_start(self); +} diff --git a/src/game/monster/guardian/guardian.h b/src/game/monster/guardian/guardian.h new file mode 100644 index 000000000..4807349d8 --- /dev/null +++ b/src/game/monster/guardian/guardian.h @@ -0,0 +1,244 @@ +/* + * Copyright (C) 1997-2001 Id Software Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Guardian animations. + * + * ======================================================================= + */ + +#define FRAME_sleep1 0 +#define FRAME_sleep2 1 +#define FRAME_sleep3 2 +#define FRAME_sleep4 3 +#define FRAME_sleep5 4 +#define FRAME_sleep6 5 +#define FRAME_sleep7 6 +#define FRAME_sleep8 7 +#define FRAME_sleep9 8 +#define FRAME_sleep10 9 +#define FRAME_sleep11 10 +#define FRAME_sleep12 11 +#define FRAME_sleep13 12 +#define FRAME_sleep14 13 +#define FRAME_death1 14 +#define FRAME_death2 15 +#define FRAME_death3 16 +#define FRAME_death4 17 +#define FRAME_death5 18 +#define FRAME_death6 19 +#define FRAME_death7 20 +#define FRAME_death8 21 +#define FRAME_death9 22 +#define FRAME_death10 23 +#define FRAME_death11 24 +#define FRAME_death12 25 +#define FRAME_death13 26 +#define FRAME_death14 27 +#define FRAME_death15 28 +#define FRAME_death16 29 +#define FRAME_death17 30 +#define FRAME_death18 31 +#define FRAME_death19 32 +#define FRAME_death20 33 +#define FRAME_death21 34 +#define FRAME_death22 35 +#define FRAME_death23 36 +#define FRAME_death24 37 +#define FRAME_death25 38 +#define FRAME_death26 39 +#define FRAME_atk1_out1 40 +#define FRAME_atk1_out2 41 +#define FRAME_atk1_out3 42 +#define FRAME_atk2_out1 43 +#define FRAME_atk2_out2 44 +#define FRAME_atk2_out3 45 +#define FRAME_atk2_out4 46 +#define FRAME_atk2_out5 47 +#define FRAME_atk2_out6 48 +#define FRAME_atk2_out7 49 +#define FRAME_kick_out1 50 +#define FRAME_kick_out2 51 +#define FRAME_kick_out3 52 +#define FRAME_kick_out4 53 +#define FRAME_kick_out5 54 +#define FRAME_kick_out6 55 +#define FRAME_kick_out7 56 +#define FRAME_kick_out8 57 +#define FRAME_kick_out9 58 +#define FRAME_kick_out10 59 +#define FRAME_kick_out11 60 +#define FRAME_kick_out12 61 +#define FRAME_pain1_1 62 +#define FRAME_pain1_2 63 +#define FRAME_pain1_3 64 +#define FRAME_pain1_4 65 +#define FRAME_pain1_5 66 +#define FRAME_pain1_6 67 +#define FRAME_pain1_7 68 +#define FRAME_pain1_8 69 +#define FRAME_idle1 70 +#define FRAME_idle2 71 +#define FRAME_idle3 72 +#define FRAME_idle4 73 +#define FRAME_idle5 74 +#define FRAME_idle6 75 +#define FRAME_idle7 76 +#define FRAME_idle8 77 +#define FRAME_idle9 78 +#define FRAME_idle10 79 +#define FRAME_idle11 80 +#define FRAME_idle12 81 +#define FRAME_idle13 82 +#define FRAME_idle14 83 +#define FRAME_idle15 84 +#define FRAME_idle16 85 +#define FRAME_idle17 86 +#define FRAME_idle18 87 +#define FRAME_idle19 88 +#define FRAME_idle20 89 +#define FRAME_idle21 90 +#define FRAME_idle22 91 +#define FRAME_idle23 92 +#define FRAME_idle24 93 +#define FRAME_idle25 94 +#define FRAME_idle26 95 +#define FRAME_idle27 96 +#define FRAME_idle28 97 +#define FRAME_idle29 98 +#define FRAME_idle30 99 +#define FRAME_idle31 100 +#define FRAME_idle32 101 +#define FRAME_idle33 102 +#define FRAME_idle34 103 +#define FRAME_idle35 104 +#define FRAME_idle36 105 +#define FRAME_idle37 106 +#define FRAME_idle38 107 +#define FRAME_idle39 108 +#define FRAME_idle40 109 +#define FRAME_idle41 110 +#define FRAME_idle42 111 +#define FRAME_idle43 112 +#define FRAME_idle44 113 +#define FRAME_idle45 114 +#define FRAME_idle46 115 +#define FRAME_idle47 116 +#define FRAME_idle48 117 +#define FRAME_idle49 118 +#define FRAME_idle50 119 +#define FRAME_idle51 120 +#define FRAME_idle52 121 +#define FRAME_atk1_in1 122 +#define FRAME_atk1_in2 123 +#define FRAME_atk1_in3 124 +#define FRAME_kick_in1 125 +#define FRAME_kick_in2 126 +#define FRAME_kick_in3 127 +#define FRAME_kick_in4 128 +#define FRAME_kick_in5 129 +#define FRAME_kick_in6 130 +#define FRAME_kick_in7 131 +#define FRAME_kick_in8 132 +#define FRAME_kick_in9 133 +#define FRAME_kick_in10 134 +#define FRAME_kick_in11 135 +#define FRAME_kick_in12 136 +#define FRAME_kick_in13 137 +#define FRAME_walk1 138 +#define FRAME_walk2 139 +#define FRAME_walk3 140 +#define FRAME_walk4 141 +#define FRAME_walk5 142 +#define FRAME_walk6 143 +#define FRAME_walk7 144 +#define FRAME_walk8 145 +#define FRAME_walk9 146 +#define FRAME_walk10 147 +#define FRAME_walk11 148 +#define FRAME_walk12 149 +#define FRAME_walk13 150 +#define FRAME_walk14 151 +#define FRAME_walk15 152 +#define FRAME_walk16 153 +#define FRAME_walk17 154 +#define FRAME_walk18 155 +#define FRAME_walk19 156 +#define FRAME_wake1 157 +#define FRAME_wake2 158 +#define FRAME_wake3 159 +#define FRAME_wake4 160 +#define FRAME_wake5 161 +#define FRAME_atk1_spin1 162 +#define FRAME_atk1_spin2 163 +#define FRAME_atk1_spin3 164 +#define FRAME_atk1_spin4 165 +#define FRAME_atk1_spin5 166 +#define FRAME_atk1_spin6 167 +#define FRAME_atk1_spin7 168 +#define FRAME_atk1_spin8 169 +#define FRAME_atk1_spin9 170 +#define FRAME_atk1_spin10 171 +#define FRAME_atk1_spin11 172 +#define FRAME_atk1_spin12 173 +#define FRAME_atk1_spin13 174 +#define FRAME_atk1_spin14 175 +#define FRAME_atk1_spin15 176 +#define FRAME_atk2_fire1 177 +#define FRAME_atk2_fire2 178 +#define FRAME_atk2_fire3 179 +#define FRAME_atk2_fire4 180 +#define FRAME_turnl_1 181 +#define FRAME_turnl_2 182 +#define FRAME_turnl_3 183 +#define FRAME_turnl_4 184 +#define FRAME_turnl_5 185 +#define FRAME_turnl_6 186 +#define FRAME_turnl_7 187 +#define FRAME_turnl_8 188 +#define FRAME_turnl_9 189 +#define FRAME_turnl_10 190 +#define FRAME_turnl_11 191 +#define FRAME_turnr_1 192 +#define FRAME_turnr_2 193 +#define FRAME_turnr_3 194 +#define FRAME_turnr_4 195 +#define FRAME_turnr_5 196 +#define FRAME_turnr_6 197 +#define FRAME_turnr_7 198 +#define FRAME_turnr_8 199 +#define FRAME_turnr_9 200 +#define FRAME_turnr_10 201 +#define FRAME_turnr_11 202 +#define FRAME_atk2_in1 203 +#define FRAME_atk2_in2 204 +#define FRAME_atk2_in3 205 +#define FRAME_atk2_in4 206 +#define FRAME_atk2_in5 207 +#define FRAME_atk2_in6 208 +#define FRAME_atk2_in7 209 +#define FRAME_atk2_in8 210 +#define FRAME_atk2_in9 211 +#define FRAME_atk2_in10 212 +#define FRAME_atk2_in11 213 +#define FRAME_atk2_in12 214 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/gunner/gunner.c b/src/game/monster/gunner/gunner.c new file mode 100644 index 000000000..e09a301e0 --- /dev/null +++ b/src/game/monster/gunner/gunner.c @@ -0,0 +1,1480 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Gunner. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "gunner.h" + +static int sound_pain; +static int sound_pain2; +static int sound_death; +static int sound_idle; +static int sound_open; +static int sound_search; +static int sound_sight; + +static int sound_step; +static int sound_step2; + +qboolean visible(edict_t *self, edict_t *other); +void GunnerGrenade(edict_t *self); +void GunnerFire(edict_t *self); +void gunner_fire_chain(edict_t *self); +void gunner_refire_chain(edict_t *self); +void gunner_stand(edict_t *self); + +void +gunner_footstep(edict_t *self) +{ + if (!g_monsterfootsteps->value) + return; + + // Lazy loading for savegame compatibility. + if (sound_step == 0 || sound_step2 == 0) + { + sound_step = gi.soundindex("gunner/step1.wav"); + sound_step2 = gi.soundindex("gunner/step2.wav"); + } + + if (randk() % 2 == 0) + { + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + } +} + + +void +gunner_idlesound(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void +gunner_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +gunner_search(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +static mframe_t gunner_frames_fidget[] = { + {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, gunner_idlesound}, + {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}, + {ai_stand, 0, NULL} +}; + +mmove_t gunner_move_fidget = +{ + FRAME_stand31, + FRAME_stand70, + gunner_frames_fidget, + gunner_stand +}; + +void +gunner_fidget(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->enemy) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + return; + } + + if (random() <= 0.05) + { + self->monsterinfo.currentmove = &gunner_move_fidget; + } +} + +static mframe_t gunner_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, gunner_fidget}, + + {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, gunner_fidget}, + + {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, gunner_fidget} +}; + +mmove_t gunner_move_stand = +{ + FRAME_stand01, + FRAME_stand30, + gunner_frames_stand, + NULL +}; + +void +gunner_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gunner_move_stand; +} + +static mframe_t gunner_frames_walk[] = { + {ai_walk, 0, gunner_footstep}, + {ai_walk, 3, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 2, gunner_footstep}, + {ai_walk, 6, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 4, gunner_footstep} +}; + +mmove_t gunner_move_walk = +{ + FRAME_walk07, + FRAME_walk19, + gunner_frames_walk, + NULL +}; + +void +gunner_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gunner_move_walk; +} + +static mframe_t gunner_frames_run[] = { + {ai_run, 26, NULL}, + {ai_run, 9, gunner_footstep}, + {ai_run, 9, NULL}, + {ai_run, 9, monster_done_dodge}, + {ai_run, 15, NULL}, + {ai_run, 10, gunner_footstep}, + {ai_run, 13, NULL}, + {ai_run, 6, NULL} +}; + +mmove_t gunner_move_run = +{ + FRAME_run01, + FRAME_run08, + gunner_frames_run, + NULL +}; + +void +gunner_run(edict_t *self) +{ + if (!self) + { + return; + } + + monster_done_dodge(self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &gunner_move_stand; + } + else + { + self->monsterinfo.currentmove = &gunner_move_run; + } +} + +static mframe_t gunner_frames_runandshoot[] = { + {ai_run, 32, NULL}, + {ai_run, 15, gunner_footstep}, + {ai_run, 10, NULL}, + {ai_run, 18, NULL}, + {ai_run, 8, gunner_footstep}, + {ai_run, 20, NULL} +}; + +mmove_t gunner_move_runandshoot = +{ + FRAME_runs01, + FRAME_runs06, + gunner_frames_runandshoot, + NULL +}; + +void +gunner_runandshoot(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gunner_move_runandshoot; +} + +static mframe_t gunner_frames_pain3[] = { + {ai_move, -3, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL} +}; + +mmove_t gunner_move_pain3 = +{ + FRAME_pain301, + FRAME_pain305, + gunner_frames_pain3, + gunner_run +}; + +static mframe_t gunner_frames_pain2[] = { + {ai_move, -2, NULL}, + {ai_move, 11, NULL}, + {ai_move, 6, gunner_footstep}, + {ai_move, 2, NULL}, + {ai_move, -1, NULL}, + {ai_move, -7, NULL}, + {ai_move, -2, NULL}, + {ai_move, -7, gunner_footstep} +}; + +mmove_t gunner_move_pain2 = +{ + FRAME_pain201, + FRAME_pain208, + gunner_frames_pain2, + gunner_run +}; + +static mframe_t gunner_frames_pain1[] = { + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, -5, gunner_footstep}, + {ai_move, 3, NULL}, + {ai_move, -1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 1, gunner_footstep}, + {ai_move, 0, NULL}, + {ai_move, -2, NULL}, + {ai_move, -2, NULL}, + {ai_move, 0, gunner_footstep}, + {ai_move, 0, NULL} +}; + +mmove_t gunner_move_pain1 = +{ + FRAME_pain101, + FRAME_pain118, + gunner_frames_pain1, + gunner_run +}; + +void +gunner_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + monster_done_dodge(self); + + if (!self->groundentity) + { + return; + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 3; + + if (randk() & 1) + { + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (damage <= 10) + { + self->monsterinfo.currentmove = &gunner_move_pain3; + } + else if (damage <= 25) + { + self->monsterinfo.currentmove = &gunner_move_pain2; + } + else + { + self->monsterinfo.currentmove = &gunner_move_pain1; + } + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } +} + +void +gunner_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +static mframe_t gunner_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -7, NULL}, + {ai_move, -3, NULL}, + {ai_move, -5, NULL}, + {ai_move, 8, NULL}, + {ai_move, 6, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t gunner_move_death = +{ + FRAME_death01, + FRAME_death11, + gunner_frames_death, + gunner_dead +}; + +void +gunner_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage /* unused */, + vec3_t point) +{ + int n; + + if (!self) + { + return; + } + + self->s.skinnum = 1; + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 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, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &gunner_move_death; +} + +void +gunner_duck_down(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + return; + } + + self->monsterinfo.aiflags |= AI_DUCKED; + + if (skill->value >= SKILL_HARD) + { + if (random() > 0.5) + { + GunnerGrenade(self); + } + } + + self->maxs[2] = self->monsterinfo.base_height - 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + + if (self->monsterinfo.duck_wait_time < level.time) + { + self->monsterinfo.duck_wait_time = level.time + 1; + } + + gi.linkentity(self); +} + +void +gunner_duck_hold(edict_t *self) +{ + if (!self) + { + return; + } + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +void +gunner_duck_up(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + +static void +gunner_duck_down_think(edict_t *self) +{ + gunner_duck_down(self); + + /* rogue code calls duck_down twice, so move this here */ + if (skill->value >= SKILL_HARD) + { + if (random() > 0.5) + { + GunnerGrenade(self); + } + } +} + +static mframe_t gunner_frames_duck[] = { + {ai_move, 1, gunner_duck_down_think}, + {ai_move, 1, NULL}, + {ai_move, 1, gunner_duck_hold}, + {ai_move, 0, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, 0, gunner_duck_up}, + {ai_move, -1, NULL} +}; + +mmove_t gunner_move_duck = +{ + FRAME_duck01, + FRAME_duck08, + gunner_frames_duck, + gunner_run +}; + +void +gunner_dodge(edict_t *self, edict_t *attacker, float eta /* unused */, + trace_t *tr /* unused */) +{ + if (!self || !attacker) + { + return; + } + + if (random() > 0.25) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + } + + self->monsterinfo.currentmove = &gunner_move_duck; +} + +/* gunner dodge moved below so I know about attack sequences */ +void +gunner_opengun(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0); +} + +void +GunnerFire(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t target; + vec3_t aim; + int flash_number; + + if (!self) + { + return; + } + + if (!self->enemy || !self->enemy->inuse) + { + return; + } + + flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216); + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + /* project enemy back a bit and target there */ + VectorCopy(self->enemy->s.origin, target); + VectorMA(target, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + + VectorSubtract(target, start, aim); + VectorNormalize(aim); + monster_fire_bullet(self, start, aim, 3, 4, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + flash_number); +} + +qboolean +gunner_grenade_check(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + trace_t tr; + vec3_t target, dir; + + if (!self) + { + return false; + } + + if (!self->enemy) + { + return false; + } + + /* if the player is above my head, use machinegun. */ + + /* check for flag telling us that we're blindfiring */ + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + if (self->s.origin[2] + self->viewheight < + self->monsterinfo.blind_fire_target[2]) + { + return false; + } + } + else if (self->absmax[2] <= self->enemy->absmin[2]) + { + return false; + } + + /* check to see that we can trace to the player + before we start tossing grenades around. */ + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GUNNER_GRENADE_1], + forward, right, start); + + /* check for blindfire flag */ + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + VectorCopy(self->monsterinfo.blind_fire_target, target); + } + else + { + VectorCopy(self->enemy->s.origin, target); + } + + /* see if we're too close */ + VectorSubtract(self->s.origin, target, dir); + + if (VectorLength(dir) < 100) + { + return false; + } + + tr = gi.trace(start, vec3_origin, vec3_origin, target, self, MASK_SHOT); + + if ((tr.ent == self->enemy) || (tr.fraction == 1)) + { + return true; + } + + return false; +} + +void +GunnerGrenade(edict_t *self) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + int flash_number; + float spread; + float pitch = 0; + vec3_t target; + qboolean blindfire = false; + + if (!self) + { + return; + } + + if (!self->enemy || !self->enemy->inuse) + { + return; + } + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + blindfire = true; + } + + if (self->s.frame == FRAME_attak105) + { + spread = .02; + flash_number = MZ2_GUNNER_GRENADE_1; + } + else if (self->s.frame == FRAME_attak108) + { + spread = .05; + flash_number = MZ2_GUNNER_GRENADE_2; + } + else if (self->s.frame == FRAME_attak111) + { + spread = .08; + flash_number = MZ2_GUNNER_GRENADE_3; + } + else + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + spread = .11; + flash_number = MZ2_GUNNER_GRENADE_4; + } + + /* if we're shooting blind and we still can't see our enemy */ + if ((blindfire) && (!visible(self, self->enemy))) + { + /* and we have a valid blind_fire_target */ + if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin)) + { + return; + } + + VectorCopy(self->monsterinfo.blind_fire_target, target); + } + else + { + VectorCopy(self->s.origin, target); + } + + AngleVectors(self->s.angles, forward, right, up); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + if (self->enemy) + { + float dist; + + VectorSubtract(target, self->s.origin, aim); + dist = VectorLength(aim); + + /* aim up if they're on the same level as me and far away. */ + if ((dist > 512) && (aim[2] < 64) && (aim[2] > -64)) + { + aim[2] += (dist - 512); + } + + VectorNormalize(aim); + pitch = aim[2]; + + if (pitch > 0.4) + { + pitch = 0.4; + } + else if (pitch < -0.5) + { + pitch = -0.5; + } + } + + VectorMA(forward, spread, right, aim); + VectorMA(aim, pitch, up, aim); + + monster_fire_grenade(self, start, aim, 50, 600, flash_number); +} + +static mframe_t gunner_frames_attack_chain[] = { + {ai_charge, 0, gunner_opengun}, + {ai_charge, 0, gunner_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t gunner_move_attack_chain = +{ + FRAME_attak209, + FRAME_attak215, + gunner_frames_attack_chain, + gunner_fire_chain +}; + +static mframe_t gunner_frames_fire_chain[] = { + {ai_charge, 0, GunnerFire}, + {ai_charge, 0, GunnerFire}, + {ai_charge, 0, GunnerFire}, + {ai_charge, 0, GunnerFire}, + {ai_charge, 0, GunnerFire}, + {ai_charge, 0, GunnerFire}, + {ai_charge, 0, GunnerFire}, + {ai_charge, 0, GunnerFire} +}; + +mmove_t gunner_move_fire_chain = +{ + FRAME_attak216, + FRAME_attak223, + gunner_frames_fire_chain, + gunner_refire_chain +}; + +static mframe_t gunner_frames_endfire_chain[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, gunner_footstep} +}; + +mmove_t gunner_move_endfire_chain = +{ + FRAME_attak224, + FRAME_attak230, + gunner_frames_endfire_chain, + gunner_run +}; + +void +gunner_blind_check(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, + aim); + self->ideal_yaw = vectoyaw(aim); + } +} + +static mframe_t gunner_frames_attack_grenade[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, GunnerGrenade}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, GunnerGrenade}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, GunnerGrenade}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, GunnerGrenade}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t gunner_move_attack_grenade = +{ + FRAME_attak101, + FRAME_attak121, + gunner_frames_attack_grenade, + gunner_run +}; + +void +gunner_attack(edict_t *self) +{ + float chance, r; + + if (!self) + { + return; + } + + monster_done_dodge(self); + + if (self->monsterinfo.attack_state == AS_BLIND) + { + /* setup shot probabilities */ + if (self->monsterinfo.blind_fire_delay < 1.0) + { + chance = 1.0; + } + else if (self->monsterinfo.blind_fire_delay < 7.5) + { + chance = 0.4; + } + else + { + chance = 0.1; + } + + r = random(); + + /* minimum of 2 seconds, plus 0-3, after the shots are done */ + self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + random() * 3.0; + + /* don't shoot at the origin */ + if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin)) + { + return; + } + + /* don't shoot if the dice say not to */ + if (r > chance) + { + return; + } + + /* turn on manual steering to signal both manual steering and blindfire */ + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + + if (gunner_grenade_check(self)) + { + /* if the check passes, go for the attack */ + self->monsterinfo.currentmove = &gunner_move_attack_grenade; + self->monsterinfo.attack_finished = level.time + 2 * random(); + } + + /* turn off blindfire flag */ + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return; + } + + /* gunner needs to use his chaingun if he's being attacked by a tesla. */ + if ((range(self, self->enemy) == RANGE_MELEE) || self->bad_area) + { + self->monsterinfo.currentmove = &gunner_move_attack_chain; + } + else + { + if ((random() <= 0.5) && gunner_grenade_check(self)) + { + self->monsterinfo.currentmove = &gunner_move_attack_grenade; + } + else + { + self->monsterinfo.currentmove = &gunner_move_attack_chain; + } + } +} + +void +gunner_fire_chain(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &gunner_move_fire_chain; +} + +void +gunner_refire_chain(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->enemy->health > 0) + { + if (visible(self, self->enemy)) + { + if (random() <= 0.5) + { + self->monsterinfo.currentmove = &gunner_move_fire_chain; + return; + } + } + } + + self->monsterinfo.currentmove = &gunner_move_endfire_chain; +} + +void +gunner_jump_now(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + monster_jump_start(self); + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +void +gunner_jump2_now(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + monster_jump_start(self); + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 150, forward, self->velocity); + VectorMA(self->velocity, 400, up, self->velocity); +} + +void +gunner_jump_wait_land(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->groundentity == NULL) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + { + self->monsterinfo.nextframe = self->s.frame + 1; + } + } + else + { + self->monsterinfo.nextframe = self->s.frame + 1; + } +} + +static mframe_t gunner_frames_jump[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, gunner_jump_now}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, gunner_jump_wait_land}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t gunner_move_jump = { + FRAME_jump01, + FRAME_jump10, + gunner_frames_jump, + gunner_run +}; + +static mframe_t gunner_frames_jump2[] = { + {ai_move, -8, NULL}, + {ai_move, -4, NULL}, + {ai_move, -4, NULL}, + {ai_move, 0, gunner_jump2_now}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, gunner_jump_wait_land}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t gunner_move_jump2 = { + FRAME_jump01, + FRAME_jump10, + gunner_frames_jump2, + gunner_run +}; + +void +gunner_jump(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->enemy) + { + return; + } + + monster_done_dodge(self); + + if (self->enemy->absmin[2] > self->absmin[2]) + { + self->monsterinfo.currentmove = &gunner_move_jump2; + } + else + { + self->monsterinfo.currentmove = &gunner_move_jump; + } +} + +qboolean +gunner_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + if (blocked_checkjump(self, dist, 192, 40)) + { + gunner_jump(self); + return true; + } + + return false; +} + +/* new duck code */ +void +gunner_duck(edict_t *self, float eta) +{ + if (!self) + { + return; + } + + if ((self->monsterinfo.currentmove == &gunner_move_jump2) || + (self->monsterinfo.currentmove == &gunner_move_jump)) + { + return; + } + + if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) || + (self->monsterinfo.currentmove == &gunner_move_fire_chain) || + (self->monsterinfo.currentmove == &gunner_move_attack_grenade) + ) + { + /* if we're shooting, and not on easy, don't dodge */ + if (skill->value > SKILL_EASY) + { + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + } + + if (skill->value == SKILL_EASY) + { + /* stupid dodge */ + self->monsterinfo.duck_wait_time = level.time + eta + 1; + } + else + { + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + } + + /* has to be done immediately otherwise he can get stuck */ + gunner_duck_down(self); + + self->monsterinfo.nextframe = FRAME_duck01; + self->monsterinfo.currentmove = &gunner_move_duck; + return; +} + +void +gunner_sidestep(edict_t *self) +{ + if (!self) + { + return; + } + + if ((self->monsterinfo.currentmove == &gunner_move_jump2) || + (self->monsterinfo.currentmove == &gunner_move_jump)) + { + return; + } + + if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) || + (self->monsterinfo.currentmove == &gunner_move_fire_chain) || + (self->monsterinfo.currentmove == &gunner_move_attack_grenade) + ) + { + /* if we're shooting, and not on easy, don't dodge */ + if (skill->value > SKILL_EASY) + { + self->monsterinfo.aiflags &= ~AI_DODGING; + return; + } + } + + if (self->monsterinfo.currentmove != &gunner_move_run) + { + self->monsterinfo.currentmove = &gunner_move_run; + } +} + +/* + * QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_gunner(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + + sound_death = gi.soundindex("gunner/death1.wav"); + sound_pain = gi.soundindex("gunner/gunpain2.wav"); + sound_pain2 = gi.soundindex("gunner/gunpain1.wav"); + sound_idle = gi.soundindex("gunner/gunidle1.wav"); + sound_open = gi.soundindex("gunner/gunatck1.wav"); + sound_search = gi.soundindex("gunner/gunsrch1.wav"); + sound_sight = gi.soundindex("gunner/sight1.wav"); + + gi.soundindex("gunner/gunatck2.wav"); + gi.soundindex("gunner/gunatck3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 32); + + self->health = 175; + self->gib_health = -70; + self->mass = 200; + + self->pain = gunner_pain; + self->die = gunner_die; + + self->monsterinfo.stand = gunner_stand; + self->monsterinfo.walk = gunner_walk; + self->monsterinfo.run = gunner_run; + self->monsterinfo.dodge = gunner_dodge; + self->monsterinfo.duck = gunner_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = gunner_sidestep; + self->monsterinfo.attack = gunner_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = gunner_sight; + self->monsterinfo.search = gunner_search; + self->monsterinfo.blocked = gunner_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &gunner_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.blindfire = true; + walkmonster_start(self); +} + +/* + * QUAKED monster_guncmdr (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping + */ +void +SP_monster_guncmdr(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + + sound_death = gi.soundindex("guncmdr/gcdrdeath1.wav"); + sound_pain = gi.soundindex("guncmdr/gcdrpain2.wav"); + sound_pain2 = gi.soundindex("guncmdr/gcdrpain1.wav"); + sound_idle = gi.soundindex("guncmdr/gcdridle1.wav"); + sound_open = gi.soundindex("guncmdr/gcdratck1.wav"); + sound_search = gi.soundindex("guncmdr/gcdrsrch1.wav"); + sound_sight = gi.soundindex("guncmdr/sight1.wav"); + + gi.soundindex("guncmdr/gcdratck2.wav"); + gi.soundindex("guncmdr/gcdratck3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 32); + + self->health = 175; + self->gib_health = -70; + self->mass = 200; + + self->pain = gunner_pain; + self->die = gunner_die; + + self->monsterinfo.stand = gunner_stand; + self->monsterinfo.walk = gunner_walk; + self->monsterinfo.run = gunner_run; + self->monsterinfo.dodge = gunner_dodge; + self->monsterinfo.duck = gunner_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = gunner_sidestep; + self->monsterinfo.attack = gunner_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = gunner_sight; + self->monsterinfo.search = gunner_search; + self->monsterinfo.blocked = gunner_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &gunner_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.blindfire = true; + walkmonster_start(self); +} diff --git a/src/game/monster/gunner/gunner.h b/src/game/monster/gunner/gunner.h new file mode 100644 index 000000000..1361d4f9a --- /dev/null +++ b/src/game/monster/gunner/gunner.h @@ -0,0 +1,248 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Gunner animations. + * + * ======================================================================= + */ + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_stand41 40 +#define FRAME_stand42 41 +#define FRAME_stand43 42 +#define FRAME_stand44 43 +#define FRAME_stand45 44 +#define FRAME_stand46 45 +#define FRAME_stand47 46 +#define FRAME_stand48 47 +#define FRAME_stand49 48 +#define FRAME_stand50 49 +#define FRAME_stand51 50 +#define FRAME_stand52 51 +#define FRAME_stand53 52 +#define FRAME_stand54 53 +#define FRAME_stand55 54 +#define FRAME_stand56 55 +#define FRAME_stand57 56 +#define FRAME_stand58 57 +#define FRAME_stand59 58 +#define FRAME_stand60 59 +#define FRAME_stand61 60 +#define FRAME_stand62 61 +#define FRAME_stand63 62 +#define FRAME_stand64 63 +#define FRAME_stand65 64 +#define FRAME_stand66 65 +#define FRAME_stand67 66 +#define FRAME_stand68 67 +#define FRAME_stand69 68 +#define FRAME_stand70 69 +#define FRAME_walk01 70 +#define FRAME_walk02 71 +#define FRAME_walk03 72 +#define FRAME_walk04 73 +#define FRAME_walk05 74 +#define FRAME_walk06 75 +#define FRAME_walk07 76 +#define FRAME_walk08 77 +#define FRAME_walk09 78 +#define FRAME_walk10 79 +#define FRAME_walk11 80 +#define FRAME_walk12 81 +#define FRAME_walk13 82 +#define FRAME_walk14 83 +#define FRAME_walk15 84 +#define FRAME_walk16 85 +#define FRAME_walk17 86 +#define FRAME_walk18 87 +#define FRAME_walk19 88 +#define FRAME_walk20 89 +#define FRAME_walk21 90 +#define FRAME_walk22 91 +#define FRAME_walk23 92 +#define FRAME_walk24 93 +#define FRAME_run01 94 +#define FRAME_run02 95 +#define FRAME_run03 96 +#define FRAME_run04 97 +#define FRAME_run05 98 +#define FRAME_run06 99 +#define FRAME_run07 100 +#define FRAME_run08 101 +#define FRAME_runs01 102 +#define FRAME_runs02 103 +#define FRAME_runs03 104 +#define FRAME_runs04 105 +#define FRAME_runs05 106 +#define FRAME_runs06 107 +#define FRAME_attak101 108 +#define FRAME_attak102 109 +#define FRAME_attak103 110 +#define FRAME_attak104 111 +#define FRAME_attak105 112 +#define FRAME_attak106 113 +#define FRAME_attak107 114 +#define FRAME_attak108 115 +#define FRAME_attak109 116 +#define FRAME_attak110 117 +#define FRAME_attak111 118 +#define FRAME_attak112 119 +#define FRAME_attak113 120 +#define FRAME_attak114 121 +#define FRAME_attak115 122 +#define FRAME_attak116 123 +#define FRAME_attak117 124 +#define FRAME_attak118 125 +#define FRAME_attak119 126 +#define FRAME_attak120 127 +#define FRAME_attak121 128 +#define FRAME_attak201 129 +#define FRAME_attak202 130 +#define FRAME_attak203 131 +#define FRAME_attak204 132 +#define FRAME_attak205 133 +#define FRAME_attak206 134 +#define FRAME_attak207 135 +#define FRAME_attak208 136 +#define FRAME_attak209 137 +#define FRAME_attak210 138 +#define FRAME_attak211 139 +#define FRAME_attak212 140 +#define FRAME_attak213 141 +#define FRAME_attak214 142 +#define FRAME_attak215 143 +#define FRAME_attak216 144 +#define FRAME_attak217 145 +#define FRAME_attak218 146 +#define FRAME_attak219 147 +#define FRAME_attak220 148 +#define FRAME_attak221 149 +#define FRAME_attak222 150 +#define FRAME_attak223 151 +#define FRAME_attak224 152 +#define FRAME_attak225 153 +#define FRAME_attak226 154 +#define FRAME_attak227 155 +#define FRAME_attak228 156 +#define FRAME_attak229 157 +#define FRAME_attak230 158 +#define FRAME_pain101 159 +#define FRAME_pain102 160 +#define FRAME_pain103 161 +#define FRAME_pain104 162 +#define FRAME_pain105 163 +#define FRAME_pain106 164 +#define FRAME_pain107 165 +#define FRAME_pain108 166 +#define FRAME_pain109 167 +#define FRAME_pain110 168 +#define FRAME_pain111 169 +#define FRAME_pain112 170 +#define FRAME_pain113 171 +#define FRAME_pain114 172 +#define FRAME_pain115 173 +#define FRAME_pain116 174 +#define FRAME_pain117 175 +#define FRAME_pain118 176 +#define FRAME_pain201 177 +#define FRAME_pain202 178 +#define FRAME_pain203 179 +#define FRAME_pain204 180 +#define FRAME_pain205 181 +#define FRAME_pain206 182 +#define FRAME_pain207 183 +#define FRAME_pain208 184 +#define FRAME_pain301 185 +#define FRAME_pain302 186 +#define FRAME_pain303 187 +#define FRAME_pain304 188 +#define FRAME_pain305 189 +#define FRAME_death01 190 +#define FRAME_death02 191 +#define FRAME_death03 192 +#define FRAME_death04 193 +#define FRAME_death05 194 +#define FRAME_death06 195 +#define FRAME_death07 196 +#define FRAME_death08 197 +#define FRAME_death09 198 +#define FRAME_death10 199 +#define FRAME_death11 200 +#define FRAME_duck01 201 +#define FRAME_duck02 202 +#define FRAME_duck03 203 +#define FRAME_duck04 204 +#define FRAME_duck05 205 +#define FRAME_duck06 206 +#define FRAME_duck07 207 +#define FRAME_duck08 208 +#define FRAME_jump01 209 +#define FRAME_jump02 210 +#define FRAME_jump03 211 +#define FRAME_jump04 212 +#define FRAME_jump05 213 +#define FRAME_jump06 214 +#define FRAME_jump07 215 +#define FRAME_jump08 216 +#define FRAME_jump09 217 +#define FRAME_jump10 218 + +#define MODEL_SCALE 1.150000 diff --git a/src/game/monster/hknight/hknight.c b/src/game/monster/hknight/hknight.c new file mode 100644 index 000000000..e7e18fc17 --- /dev/null +++ b/src/game/monster/hknight/hknight.c @@ -0,0 +1,562 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "hknight.h" + +static int sound_attack; +static int sound_melee; +static int sound_death; +static int sound_proj_hit; +static int sound_sight; +static int sound_search; +static int sound_pain; + +void hknight_run(edict_t *self); +void swing_sword_step(edict_t *self); + +// Stand +static mframe_t hknight_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}, +}; +mmove_t hknight_move_stand = +{ + FRAME_stand1, + FRAME_stand9, + hknight_frames_stand, + NULL +}; + +void +hknight_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &hknight_move_stand; +} + +// Charge +static mframe_t hknight_frames_charge [] = +{ + {ai_charge, 20, NULL}, + {ai_charge, 25, NULL}, + {ai_charge, 18, NULL}, + {ai_charge, 16, NULL}, + + {ai_charge, 14, NULL}, + {ai_charge, 20, swing_sword_step}, + {ai_charge, 21, swing_sword_step}, + {ai_charge, 13, swing_sword_step}, + + {ai_charge, 20, swing_sword_step}, + {ai_charge, 20, swing_sword_step}, + {ai_charge, 18, swing_sword_step}, + {ai_charge, 16, NULL}, + + {ai_charge, 20, NULL}, + {ai_charge, 14, NULL}, + {ai_charge, 25, NULL}, + {ai_charge, 21, NULL}, +}; +mmove_t hknight_move_charge = +{ + FRAME_char_a1, + FRAME_char_a16, + hknight_frames_charge, + hknight_run +}; + +static qboolean +check_for_charge(edict_t *self) +{ + if (!self->enemy) + return false; + if (!visible(self, self->enemy)) + return false; + if (fabs(self->s.origin[2] - self->enemy->s.origin[2]) > 20) + return false; + if (realrange(self, self->enemy) > 320) + return false; + return true; +} + +// Run +static mframe_t hknight_frames_run [] = +{ + {ai_run, 20, NULL}, + {ai_run, 18, NULL}, + {ai_run, 25, NULL}, + {ai_run, 16, NULL}, + + {ai_run, 14, NULL}, + {ai_run, 25, NULL}, + {ai_run, 21, NULL}, + {ai_run, 13, NULL} +}; +mmove_t hknight_move_run = +{ + FRAME_run1, + FRAME_run8, + hknight_frames_run, + NULL +}; + +void +hknight_run(edict_t *self) +{ + self->monsterinfo.currentmove = &hknight_move_run; +} + +static void +hknight_reset_magic(edict_t *self) +{ + self->radius_dmg = -2; + + if (self->enemy && realrange(self, self->enemy) < 320 && (random() < 0.75)) + { + self->monsterinfo.attack_finished = level.time + 1.0; + } +} + +void +magic_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + { + return; + } + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, 0); + else + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(15); + gi.WritePosition(self->s.origin); + gi.WriteDir((!plane) ? vec3_origin : plane->normal); + gi.WriteByte(66); + gi.multicast(self->s.origin, MULTICAST_PVS); + + gi.sound (self, CHAN_WEAPON, sound_proj_hit, 1, ATTN_NORM, 0); + } + G_FreeEdict(self); +} + +static void +fire_magic(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed) +{ + edict_t *magic; + trace_t tr; + + if (!self) + { + return; + } + + magic = G_Spawn(); + magic->svflags = SVF_DEADMONSTER; + VectorCopy(start, magic->s.origin); + VectorCopy(start, magic->s.old_origin); + vectoangles(dir, magic->s.angles); + VectorScale(dir, speed, magic->velocity); + + magic->movetype = MOVETYPE_FLYMISSILE; + magic->clipmask = MASK_SHOT; + magic->solid = SOLID_BBOX; + magic->s.effects |= EF_IONRIPPER; + VectorClear(magic->mins); + VectorClear(magic->maxs); + + magic->s.modelindex = gi.modelindex("models/proj/fireball/tris.md2"); + magic->owner = self; + magic->touch = magic_touch; + magic->nextthink = level.time + 10; + magic->think = G_FreeEdict; + magic->dmg = damage; + magic->classname = "fireball"; + gi.linkentity(magic); + + tr = gi.trace (magic->s.origin, NULL, NULL, magic->s.origin, magic, MASK_SHOT); + + if (tr.fraction < 1.0) + { + VectorMA(magic->s.origin, -10, dir, magic->s.origin); + magic->touch(magic, tr.ent, NULL, NULL); + } + gi.sound(self, CHAN_WEAPON, sound_attack, 1, ATTN_NORM, 0); +} + +static void +fire_magic_step(edict_t *self) +{ + vec3_t dir; + + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + dir[1] += self->radius_dmg * 60; + VectorNormalize(dir); + + fire_magic(self, self->s.origin, dir, 9, 300); + self->radius_dmg++; +} + +// Attack +static mframe_t hknight_frames_attack [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, hknight_reset_magic}, + {ai_charge, 0, fire_magic_step}, + {ai_charge, 0, fire_magic_step}, + {ai_charge, 0, fire_magic_step}, + + {ai_charge, 0, fire_magic_step}, + {ai_charge, 0, fire_magic_step}, + {ai_charge, 0, fire_magic_step} +}; +mmove_t hknight_move_attack = +{ + FRAME_magicc1, + FRAME_magicc11, + hknight_frames_attack, + hknight_run +}; + +void +hknight_attack(edict_t *self) +{ + if (check_for_charge(self)) + self->monsterinfo.currentmove = &hknight_move_charge; + else if (self->monsterinfo.attack_finished < level.time) + self->monsterinfo.currentmove = &hknight_move_attack; + else + self->monsterinfo.currentmove = &hknight_move_charge; +} + +// Slice +static mframe_t hknight_frames_slice [] = +{ + {ai_charge, 9, NULL}, + {ai_charge, 6, NULL}, + {ai_charge, 13, NULL}, + {ai_charge, 4, NULL}, + + {ai_charge, 7, swing_sword_step}, + {ai_charge, 15, swing_sword_step}, + {ai_charge, 8, swing_sword_step}, + {ai_charge, 2, swing_sword_step}, + + {ai_charge, 0, swing_sword_step}, + {ai_charge, 3, NULL} +}; +mmove_t hknight_move_slice = +{ + FRAME_slice1, + FRAME_slice10, + hknight_frames_slice, + hknight_run +}; + +// Smash +static mframe_t hknight_frames_smash [] = +{ + {ai_charge, 1, NULL}, + {ai_charge, 13, NULL}, + {ai_charge, 9, NULL}, + {ai_charge, 11, NULL}, + + {ai_charge, 10, swing_sword_step}, + {ai_charge, 7, swing_sword_step}, + {ai_charge, 12, swing_sword_step}, + {ai_charge, 2, swing_sword_step}, + + {ai_charge, 3, swing_sword_step}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; +mmove_t hknight_move_smash = +{ + FRAME_smash1, + FRAME_smash11, + hknight_frames_smash, + hknight_run +}; + +// Watk +static mframe_t hknight_frames_watk [] = +{ + {ai_charge, 2, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, swing_sword_step}, + + {ai_charge, 0, swing_sword_step}, + {ai_charge, 0, swing_sword_step}, + {ai_charge, 1, NULL}, + {ai_charge, 4, NULL}, + + {ai_charge, 5, NULL}, + {ai_charge, 3, swing_sword_step}, + {ai_charge, 2, swing_sword_step}, + {ai_charge, 2, swing_sword_step}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 1, NULL}, + + {ai_charge, 1, swing_sword_step}, + {ai_charge, 3, swing_sword_step}, + {ai_charge, 4, swing_sword_step}, + {ai_charge, 6, NULL}, + + {ai_charge, 7, NULL}, + {ai_charge, 3, NULL}, + +}; +mmove_t hknight_move_watk = +{ + FRAME_w_attack1, + FRAME_w_attack22, + hknight_frames_watk, + hknight_run +}; + +// Melee +void +hknight_melee(edict_t *self) +{ + self->dmg_radius++; + + if (self->dmg_radius == 1) + self->monsterinfo.currentmove = &hknight_move_slice; + else if (self->dmg_radius == 2) + self->monsterinfo.currentmove = &hknight_move_smash; + else if (self->dmg_radius == 3) + { + self->monsterinfo.currentmove = &hknight_move_watk; + self->dmg_radius = 0; + } + gi.sound(self, CHAN_WEAPON, sound_melee, 1, ATTN_NORM, 0); +} + +// Pain +static mframe_t hknight_frames_pain [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t hknight_move_pain = +{ + FRAME_pain1, + FRAME_pain5, + hknight_frames_pain, + hknight_run +}; + +void +hknight_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + // decino: No pain animations in Nightmare mode + if (skill->value == SKILL_HARDPLUS) + return; + if (level.time < self->pain_debounce_time) + return; + + if (level.time - self->pain_debounce_time > 5) + { + self->monsterinfo.currentmove = &hknight_move_pain; + self->pain_debounce_time = level.time + 1; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + return; + } + if ((random() * 30 > damage) ) + return; + self->monsterinfo.currentmove = &hknight_move_pain; + self->pain_debounce_time = level.time + 1; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); +} + +void +hknight_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); +} + +// Death (1) +static mframe_t hknight_frames_die1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 10, NULL}, + {ai_move, 8, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 10, NULL}, + + {ai_move, 11, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t hknight_move_die1 = +{ + FRAME_death1, + FRAME_death12, + hknight_frames_die1, + hknight_dead +}; + +// Death (2) +static mframe_t hknight_frames_die2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t hknight_move_die2 = +{ + FRAME_deathb1, + FRAME_deathb9, + hknight_frames_die2, + hknight_dead +}; + +void +hknight_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 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; + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (random() < 0.5) + self->monsterinfo.currentmove = &hknight_move_die1; + else + self->monsterinfo.currentmove = &hknight_move_die2; +} + +// Sight +void +hknight_sight(edict_t *self, edict_t *other /* unused */) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +// Search +void +hknight_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +/* + * QUAKED monster_hknight (1 .5 0) (-16, -16, -24) (16, 16, 40) Ambush Trigger_Spawn Sight + */ +void +SP_monster_hknight(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/hknight/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 40); + self->health = 250; + + sound_attack = gi.soundindex("hknight/attack1.wav"); + sound_melee = gi.soundindex("hknight/slash1.wav"); + sound_death = gi.soundindex("hknight/death1.wav"); + sound_proj_hit = gi.soundindex("hknight/hit.wav"); + sound_sight = gi.soundindex("hknight/sight1.wav"); + sound_search = gi.soundindex("hknight/idle.wav"); + sound_pain = gi.soundindex("hknight/pain1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -40; + self->mass = 250; + + self->monsterinfo.stand = hknight_stand; + self->monsterinfo.walk = hknight_run; + self->monsterinfo.run = hknight_run; + self->monsterinfo.attack = hknight_attack; + self->monsterinfo.melee = hknight_melee; + self->monsterinfo.sight = hknight_sight; + self->monsterinfo.search = hknight_search; + + self->pain = hknight_pain; + self->die = hknight_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/hknight/hknight.h b/src/game/monster/hknight/hknight.h new file mode 100644 index 000000000..0ae1c9839 --- /dev/null +++ b/src/game/monster/hknight/hknight.h @@ -0,0 +1,195 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Hknight animations. + * + * ======================================================================= + */ + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_stand8 7 +#define FRAME_stand9 8 +#define FRAME_walk1 9 +#define FRAME_walk2 10 +#define FRAME_walk3 11 +#define FRAME_walk4 12 +#define FRAME_walk5 13 +#define FRAME_walk6 14 +#define FRAME_walk7 15 +#define FRAME_walk8 16 +#define FRAME_walk9 17 +#define FRAME_walk10 18 +#define FRAME_walk11 19 +#define FRAME_walk12 20 +#define FRAME_walk13 21 +#define FRAME_walk14 22 +#define FRAME_walk15 23 +#define FRAME_walk16 24 +#define FRAME_walk17 25 +#define FRAME_walk18 26 +#define FRAME_walk19 27 +#define FRAME_walk20 28 +#define FRAME_run1 29 +#define FRAME_run2 30 +#define FRAME_run3 31 +#define FRAME_run4 32 +#define FRAME_run5 33 +#define FRAME_run6 34 +#define FRAME_run7 35 +#define FRAME_run8 36 +#define FRAME_pain1 37 +#define FRAME_pain2 38 +#define FRAME_pain3 39 +#define FRAME_pain4 40 +#define FRAME_pain5 41 +#define FRAME_death1 42 +#define FRAME_death2 43 +#define FRAME_death3 44 +#define FRAME_death4 45 +#define FRAME_death5 46 +#define FRAME_death6 47 +#define FRAME_death7 48 +#define FRAME_death8 49 +#define FRAME_death9 50 +#define FRAME_death10 51 +#define FRAME_death11 52 +#define FRAME_death12 53 +#define FRAME_deathb1 54 +#define FRAME_deathb2 55 +#define FRAME_deathb3 56 +#define FRAME_deathb4 57 +#define FRAME_deathb5 58 +#define FRAME_deathb6 59 +#define FRAME_deathb7 60 +#define FRAME_deathb8 61 +#define FRAME_deathb9 62 +#define FRAME_char_a1 63 +#define FRAME_char_a2 64 +#define FRAME_char_a3 65 +#define FRAME_char_a4 66 +#define FRAME_char_a5 67 +#define FRAME_char_a6 68 +#define FRAME_char_a7 69 +#define FRAME_char_a8 70 +#define FRAME_char_a9 71 +#define FRAME_char_a10 72 +#define FRAME_char_a11 73 +#define FRAME_char_a12 74 +#define FRAME_char_a13 75 +#define FRAME_char_a14 76 +#define FRAME_char_a15 77 +#define FRAME_char_a16 78 +#define FRAME_magica1 79 +#define FRAME_magica2 80 +#define FRAME_magica3 81 +#define FRAME_magica4 82 +#define FRAME_magica5 83 +#define FRAME_magica6 84 +#define FRAME_magica7 85 +#define FRAME_magica8 86 +#define FRAME_magica9 87 +#define FRAME_magica10 88 +#define FRAME_magica11 89 +#define FRAME_magica12 90 +#define FRAME_magica13 91 +#define FRAME_magica14 92 +#define FRAME_magicb1 93 +#define FRAME_magicb2 94 +#define FRAME_magicb3 95 +#define FRAME_magicb4 96 +#define FRAME_magicb5 97 +#define FRAME_magicb6 98 +#define FRAME_magicb7 99 +#define FRAME_magicb8 100 +#define FRAME_magicb9 101 +#define FRAME_magicb10 102 +#define FRAME_magicb11 103 +#define FRAME_magicb12 104 +#define FRAME_magicb13 105 +#define FRAME_char_b1 106 +#define FRAME_char_b2 107 +#define FRAME_char_b3 108 +#define FRAME_char_b4 109 +#define FRAME_char_b5 110 +#define FRAME_char_b6 111 +#define FRAME_slice1 112 +#define FRAME_slice2 113 +#define FRAME_slice3 114 +#define FRAME_slice4 115 +#define FRAME_slice5 116 +#define FRAME_slice6 117 +#define FRAME_slice7 118 +#define FRAME_slice8 119 +#define FRAME_slice9 120 +#define FRAME_slice10 121 +#define FRAME_smash1 122 +#define FRAME_smash2 123 +#define FRAME_smash3 124 +#define FRAME_smash4 125 +#define FRAME_smash5 126 +#define FRAME_smash6 127 +#define FRAME_smash7 128 +#define FRAME_smash8 129 +#define FRAME_smash9 130 +#define FRAME_smash10 131 +#define FRAME_smash11 132 +#define FRAME_w_attack1 133 +#define FRAME_w_attack2 134 +#define FRAME_w_attack3 135 +#define FRAME_w_attack4 136 +#define FRAME_w_attack5 137 +#define FRAME_w_attack6 138 +#define FRAME_w_attack7 139 +#define FRAME_w_attack8 140 +#define FRAME_w_attack9 141 +#define FRAME_w_attack10 142 +#define FRAME_w_attack11 143 +#define FRAME_w_attack12 144 +#define FRAME_w_attack13 145 +#define FRAME_w_attack14 146 +#define FRAME_w_attack15 147 +#define FRAME_w_attack16 148 +#define FRAME_w_attack17 149 +#define FRAME_w_attack18 150 +#define FRAME_w_attack19 151 +#define FRAME_w_attack20 152 +#define FRAME_w_attack21 153 +#define FRAME_w_attack22 154 +#define FRAME_magicc1 155 +#define FRAME_magicc2 156 +#define FRAME_magicc3 157 +#define FRAME_magicc4 158 +#define FRAME_magicc5 159 +#define FRAME_magicc6 160 +#define FRAME_magicc7 161 +#define FRAME_magicc8 162 +#define FRAME_magicc9 163 +#define FRAME_magicc10 164 +#define FRAME_magicc11 165 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/hover/hover.c b/src/game/monster/hover/hover.c new file mode 100644 index 000000000..ce5e12289 --- /dev/null +++ b/src/game/monster/hover/hover.c @@ -0,0 +1,1077 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Icarus and Daedalus. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "hover.h" + +qboolean visible(edict_t *self, edict_t *other); +void hover_run(edict_t *self); +void hover_stand(edict_t *self); +void hover_dead(edict_t *self); +void hover_attack(edict_t *self); +void hover_reattack(edict_t *self); +void hover_fire_blaster(edict_t *self); +void hover_die(edict_t *self, edict_t *inflictor, edict_t *attacker, + int damage, vec3_t point); + +static int sound_pain1; +static int sound_pain2; +static int sound_death1; +static int sound_death2; +static int sound_sight; +static int sound_search1; +static int sound_search2; + +/* daedalus sounds */ +static int daed_sound_pain1; +static int daed_sound_pain2; +static int daed_sound_death1; +static int daed_sound_death2; +static int daed_sound_sight; +static int daed_sound_search1; +static int daed_sound_search2; + +void +hover_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + if (self->mass < 225) + { + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, daed_sound_sight, 1, ATTN_NORM, 0); + } +} + +void +hover_search(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->mass < 225) + { + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + } + } + else + { + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, daed_sound_search1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, daed_sound_search2, 1, ATTN_NORM, 0); + } + } +} + +static mframe_t hover_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} +}; + +mmove_t hover_move_stand = +{ + FRAME_stand01, + FRAME_stand30, + hover_frames_stand, + NULL +}; + +static mframe_t hover_frames_stop1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t hover_move_stop1 = +{ + FRAME_stop101, + FRAME_stop109, + hover_frames_stop1, + NULL +}; + +static mframe_t hover_frames_stop2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t hover_move_stop2 = +{ + FRAME_stop201, + FRAME_stop208, + hover_frames_stop2, + NULL +}; + +static mframe_t hover_frames_takeoff[] = { + {ai_move, 0, NULL}, + {ai_move, -2, NULL}, + {ai_move, 5, NULL}, + {ai_move, -1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 2, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, -6, NULL}, + {ai_move, -9, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 2, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t hover_move_takeoff = +{ + FRAME_takeof01, + FRAME_takeof30, + hover_frames_takeoff, + NULL +}; + +static mframe_t hover_frames_pain3[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t hover_move_pain3 = +{ + FRAME_pain301, + FRAME_pain309, + hover_frames_pain3, + hover_run +}; + +static mframe_t hover_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t hover_move_pain2 = +{ + FRAME_pain201, + FRAME_pain212, + hover_frames_pain2, + hover_run +}; + +static mframe_t hover_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, -8, NULL}, + {ai_move, -4, NULL}, + {ai_move, -6, NULL}, + {ai_move, -4, NULL}, + {ai_move, -3, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 3, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL}, + {ai_move, 7, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 5, NULL}, + {ai_move, 3, NULL}, + {ai_move, 4, NULL} +}; + +mmove_t hover_move_pain1 = +{ + FRAME_pain101, + FRAME_pain128, + hover_frames_pain1, + hover_run +}; + +static mframe_t hover_frames_land[] = { + {ai_move, 0, NULL} +}; + +mmove_t hover_move_land = +{ + FRAME_land01, + FRAME_land01, + hover_frames_land, + NULL +}; + +static mframe_t hover_frames_forward[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t hover_move_forward = +{ + FRAME_forwrd01, + FRAME_forwrd35, + hover_frames_forward, + NULL +}; + +static mframe_t hover_frames_walk[] = { + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL} +}; + +mmove_t hover_move_walk = +{ + FRAME_forwrd01, + FRAME_forwrd35, + hover_frames_walk, + NULL +}; + +static mframe_t hover_frames_run[] = { + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL} +}; + +mmove_t hover_move_run = +{ + FRAME_forwrd01, + FRAME_forwrd35, + hover_frames_run, + NULL +}; + +static mframe_t hover_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -10, NULL}, + {ai_move, 3, NULL}, + {ai_move, 5, NULL}, + {ai_move, 4, NULL}, + {ai_move, 7, NULL} +}; + +mmove_t hover_move_death1 = +{ + FRAME_death101, + FRAME_death111, + hover_frames_death1, + hover_dead +}; + +static mframe_t hover_frames_backward[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t hover_move_backward = +{ + FRAME_backwd01, + FRAME_backwd24, + hover_frames_backward, + NULL +}; + +static mframe_t hover_frames_start_attack[] = { + {ai_charge, 1, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, 1, NULL} +}; + +mmove_t hover_move_start_attack = +{ + FRAME_attak101, + FRAME_attak103, + hover_frames_start_attack, + hover_attack +}; + +static mframe_t hover_frames_attack1[] = { + {ai_charge, -10, hover_fire_blaster}, + {ai_charge, -10, hover_fire_blaster}, + {ai_charge, 0, hover_reattack}, +}; + +mmove_t hover_move_attack1 = +{ + FRAME_attak104, + FRAME_attak106, + hover_frames_attack1, + NULL +}; + +static mframe_t hover_frames_end_attack[] = { + {ai_charge, 1, NULL}, + {ai_charge, 1, NULL} +}; + +mmove_t hover_move_end_attack = +{ + FRAME_attak107, + FRAME_attak108, + hover_frames_end_attack, + hover_run +}; + +static mframe_t hover_frames_start_attack2[] = { + {ai_charge, 15, NULL}, + {ai_charge, 15, NULL}, + {ai_charge, 15, NULL} +}; + +mmove_t hover_move_start_attack2 = { + FRAME_attak101, + FRAME_attak103, + hover_frames_start_attack2, + hover_attack +}; + +static mframe_t hover_frames_attack2[] = { + {ai_charge, 10, hover_fire_blaster}, + {ai_charge, 10, hover_fire_blaster}, + {ai_charge, 10, hover_reattack}, +}; + +mmove_t hover_move_attack2 = { + FRAME_attak104, + FRAME_attak106, + hover_frames_attack2, + NULL +}; + +static mframe_t hover_frames_end_attack2[] = { + {ai_charge, 15, NULL}, + {ai_charge, 15, NULL} +}; + +mmove_t hover_move_end_attack2 = { + FRAME_attak107, + FRAME_attak108, + hover_frames_end_attack2, + hover_run +}; + +void +hover_reattack(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->enemy->health > 0) + { + if (visible(self, self->enemy)) + { + if (random() <= 0.6) + { + if (self->monsterinfo.attack_state == AS_STRAIGHT) + { + self->monsterinfo.currentmove = &hover_move_attack1; + return; + } + else if (self->monsterinfo.attack_state == AS_SLIDING) + { + self->monsterinfo.currentmove = &hover_move_attack2; + return; + } + else + { + gi.dprintf("hover_reattack: unexpected state %d\n", self->monsterinfo.attack_state); + } + } + } + } + + self->monsterinfo.currentmove = &hover_move_end_attack; +} + +void +hover_fire_blaster(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + + if (!self) + { + return; + } + + if (!self->enemy || !self->enemy->inuse) + { + return; + } + + if (self->s.frame == FRAME_attak104) + { + effect = EF_HYPERBLASTER; + } + else + { + effect = 0; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_HOVER_BLASTER_1], + forward, right, start); + + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, dir); + + if (self->mass < 200) + { + monster_fire_blaster(self, start, dir, 1, + 1000, MZ2_HOVER_BLASTER_1, effect); + } + else + { + monster_fire_blaster2(self, start, dir, 1, 1000, + MZ2_DAEDALUS_BLASTER, EF_BLASTER); + } +} + +void +hover_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &hover_move_stand; +} + +void +hover_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &hover_move_stand; + } + else + { + self->monsterinfo.currentmove = &hover_move_run; + } +} + +void +hover_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &hover_move_walk; +} + +void +hover_start_attack(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &hover_move_start_attack; +} + +void +hover_attack(edict_t *self) +{ + float chance; + + if (!self) + { + return; + } + + if (skill->value == SKILL_EASY) + { + chance = 0; + } + else + { + chance = 1.0 - (0.5 / (float)(skill->value)); + } + + if (self->mass > 150) /* the daedalus strafes more */ + { + chance += 0.1; + } + + if (random() > chance) + { + self->monsterinfo.currentmove = &hover_move_attack1; + self->monsterinfo.attack_state = AS_STRAIGHT; + } + else /* circle strafe */ + { + if (random() <= 0.5) /* switch directions */ + { + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + } + + self->monsterinfo.currentmove = &hover_move_attack2; + self->monsterinfo.attack_state = AS_SLIDING; + } +} + +void +hover_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum |= 1; /* support for skins 2 & 3. */ + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 3; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (damage <= 25) + { + if (random() < 0.5) + { + /* daedalus sounds */ + if (self->mass < 225) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0); + } + + self->monsterinfo.currentmove = &hover_move_pain3; + } + else + { + /* daedalus sounds */ + if (self->mass < 225) + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0); + } + + self->monsterinfo.currentmove = &hover_move_pain2; + } + } + else + { + if (random() < (0.45 - (0.1 * skill->value))) + { + /* daedalus sounds */ + if (self->mass < 225) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0); + } + + self->monsterinfo.currentmove = &hover_move_pain1; + } + else + { + /* daedalus sounds */ + if (self->mass < 225) + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0); + } + + self->monsterinfo.currentmove = &hover_move_pain2; + } + } +} + +void +hover_deadthink(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->groundentity && (level.time < self->timestamp)) + { + self->nextthink = level.time + FRAMETIME; + return; + } + + BecomeExplosion1(self); +} + +void +hover_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->think = hover_deadthink; + self->nextthink = level.time + FRAMETIME; + self->timestamp = level.time + 15; + gi.linkentity(self); +} + +void +hover_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + self->s.effects = 0; + self->monsterinfo.power_armor_type = POWER_ARMOR_NONE; + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex( + "misc/udeath.wav"), 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 < 2; n++) + { + ThrowGib(self, + "models/objects/gibs/sm_meat/tris.md2", + damage, + GIB_ORGANIC); + } + + ThrowHead(self, + "models/objects/gibs/sm_meat/tris.md2", + damage, + GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + { + return; + } + + /* regular death */ + if (self->mass < 225) + { + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); + } + } + else + { + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, daed_sound_death1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, daed_sound_death2, 1, ATTN_NORM, 0); + } + } + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &hover_move_death1; +} + +qboolean +hover_blocked(edict_t *self, float dist) +{ + return false; +} + + +/* + * QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + * + * QUAKED monster_daedalus (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + * This is the improved icarus monster. + */ +void +SP_monster_hover(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("hover/hovpain1.wav"); + sound_pain2 = gi.soundindex("hover/hovpain2.wav"); + sound_death1 = gi.soundindex("hover/hovdeth1.wav"); + sound_death2 = gi.soundindex("hover/hovdeth2.wav"); + sound_sight = gi.soundindex("hover/hovsght1.wav"); + sound_search1 = gi.soundindex("hover/hovsrch1.wav"); + sound_search2 = gi.soundindex("hover/hovsrch2.wav"); + + gi.soundindex("hover/hovatck1.wav"); + + self->s.sound = gi.soundindex("hover/hovidle1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2"); + VectorSet(self->mins, -24, -24, -24); + VectorSet(self->maxs, 24, 24, 32); + + self->health = 240; + self->gib_health = -100; + self->mass = 150; + + self->pain = hover_pain; + self->die = hover_die; + + self->monsterinfo.stand = hover_stand; + self->monsterinfo.walk = hover_walk; + self->monsterinfo.run = hover_run; + self->monsterinfo.attack = hover_start_attack; + self->monsterinfo.sight = hover_sight; + self->monsterinfo.search = hover_search; + self->monsterinfo.blocked = hover_blocked; + + if (strcmp(self->classname, "monster_daedalus") == 0) + { + self->health = 450; + self->mass = 225; + self->yaw_speed = 25; + self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN; + self->monsterinfo.power_armor_power = 100; + self->s.sound = gi.soundindex("daedalus/daedidle1.wav"); + daed_sound_pain1 = gi.soundindex("daedalus/daedpain1.wav"); + daed_sound_pain2 = gi.soundindex("daedalus/daedpain2.wav"); + daed_sound_death1 = gi.soundindex("daedalus/daeddeth1.wav"); + daed_sound_death2 = gi.soundindex("daedalus/daeddeth2.wav"); + daed_sound_sight = gi.soundindex("daedalus/daedsght1.wav"); + daed_sound_search1 = gi.soundindex("daedalus/daedsrch1.wav"); + daed_sound_search2 = gi.soundindex("daedalus/daedsrch2.wav"); + gi.soundindex("tank/tnkatck3.wav"); + } + else + { + sound_pain1 = gi.soundindex("hover/hovpain1.wav"); + sound_pain2 = gi.soundindex("hover/hovpain2.wav"); + sound_death1 = gi.soundindex("hover/hovdeth1.wav"); + sound_death2 = gi.soundindex("hover/hovdeth2.wav"); + sound_sight = gi.soundindex("hover/hovsght1.wav"); + sound_search1 = gi.soundindex("hover/hovsrch1.wav"); + sound_search2 = gi.soundindex("hover/hovsrch2.wav"); + gi.soundindex("hover/hovatck1.wav"); + + self->s.sound = gi.soundindex("hover/hovidle1.wav"); + } + + gi.linkentity(self); + + self->monsterinfo.currentmove = &hover_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + flymonster_start(self); + + if (strcmp(self->classname, "monster_daedalus") == 0) + { + self->s.skinnum = 2; + } +} diff --git a/src/game/monster/hover/hover.h b/src/game/monster/hover/hover.h new file mode 100644 index 000000000..80dfe1849 --- /dev/null +++ b/src/game/monster/hover/hover.h @@ -0,0 +1,234 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Icarus and Daedalus animations. + * + * ======================================================================= + */ + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_forwrd01 30 +#define FRAME_forwrd02 31 +#define FRAME_forwrd03 32 +#define FRAME_forwrd04 33 +#define FRAME_forwrd05 34 +#define FRAME_forwrd06 35 +#define FRAME_forwrd07 36 +#define FRAME_forwrd08 37 +#define FRAME_forwrd09 38 +#define FRAME_forwrd10 39 +#define FRAME_forwrd11 40 +#define FRAME_forwrd12 41 +#define FRAME_forwrd13 42 +#define FRAME_forwrd14 43 +#define FRAME_forwrd15 44 +#define FRAME_forwrd16 45 +#define FRAME_forwrd17 46 +#define FRAME_forwrd18 47 +#define FRAME_forwrd19 48 +#define FRAME_forwrd20 49 +#define FRAME_forwrd21 50 +#define FRAME_forwrd22 51 +#define FRAME_forwrd23 52 +#define FRAME_forwrd24 53 +#define FRAME_forwrd25 54 +#define FRAME_forwrd26 55 +#define FRAME_forwrd27 56 +#define FRAME_forwrd28 57 +#define FRAME_forwrd29 58 +#define FRAME_forwrd30 59 +#define FRAME_forwrd31 60 +#define FRAME_forwrd32 61 +#define FRAME_forwrd33 62 +#define FRAME_forwrd34 63 +#define FRAME_forwrd35 64 +#define FRAME_stop101 65 +#define FRAME_stop102 66 +#define FRAME_stop103 67 +#define FRAME_stop104 68 +#define FRAME_stop105 69 +#define FRAME_stop106 70 +#define FRAME_stop107 71 +#define FRAME_stop108 72 +#define FRAME_stop109 73 +#define FRAME_stop201 74 +#define FRAME_stop202 75 +#define FRAME_stop203 76 +#define FRAME_stop204 77 +#define FRAME_stop205 78 +#define FRAME_stop206 79 +#define FRAME_stop207 80 +#define FRAME_stop208 81 +#define FRAME_takeof01 82 +#define FRAME_takeof02 83 +#define FRAME_takeof03 84 +#define FRAME_takeof04 85 +#define FRAME_takeof05 86 +#define FRAME_takeof06 87 +#define FRAME_takeof07 88 +#define FRAME_takeof08 89 +#define FRAME_takeof09 90 +#define FRAME_takeof10 91 +#define FRAME_takeof11 92 +#define FRAME_takeof12 93 +#define FRAME_takeof13 94 +#define FRAME_takeof14 95 +#define FRAME_takeof15 96 +#define FRAME_takeof16 97 +#define FRAME_takeof17 98 +#define FRAME_takeof18 99 +#define FRAME_takeof19 100 +#define FRAME_takeof20 101 +#define FRAME_takeof21 102 +#define FRAME_takeof22 103 +#define FRAME_takeof23 104 +#define FRAME_takeof24 105 +#define FRAME_takeof25 106 +#define FRAME_takeof26 107 +#define FRAME_takeof27 108 +#define FRAME_takeof28 109 +#define FRAME_takeof29 110 +#define FRAME_takeof30 111 +#define FRAME_land01 112 +#define FRAME_pain101 113 +#define FRAME_pain102 114 +#define FRAME_pain103 115 +#define FRAME_pain104 116 +#define FRAME_pain105 117 +#define FRAME_pain106 118 +#define FRAME_pain107 119 +#define FRAME_pain108 120 +#define FRAME_pain109 121 +#define FRAME_pain110 122 +#define FRAME_pain111 123 +#define FRAME_pain112 124 +#define FRAME_pain113 125 +#define FRAME_pain114 126 +#define FRAME_pain115 127 +#define FRAME_pain116 128 +#define FRAME_pain117 129 +#define FRAME_pain118 130 +#define FRAME_pain119 131 +#define FRAME_pain120 132 +#define FRAME_pain121 133 +#define FRAME_pain122 134 +#define FRAME_pain123 135 +#define FRAME_pain124 136 +#define FRAME_pain125 137 +#define FRAME_pain126 138 +#define FRAME_pain127 139 +#define FRAME_pain128 140 +#define FRAME_pain201 141 +#define FRAME_pain202 142 +#define FRAME_pain203 143 +#define FRAME_pain204 144 +#define FRAME_pain205 145 +#define FRAME_pain206 146 +#define FRAME_pain207 147 +#define FRAME_pain208 148 +#define FRAME_pain209 149 +#define FRAME_pain210 150 +#define FRAME_pain211 151 +#define FRAME_pain212 152 +#define FRAME_pain301 153 +#define FRAME_pain302 154 +#define FRAME_pain303 155 +#define FRAME_pain304 156 +#define FRAME_pain305 157 +#define FRAME_pain306 158 +#define FRAME_pain307 159 +#define FRAME_pain308 160 +#define FRAME_pain309 161 +#define FRAME_death101 162 +#define FRAME_death102 163 +#define FRAME_death103 164 +#define FRAME_death104 165 +#define FRAME_death105 166 +#define FRAME_death106 167 +#define FRAME_death107 168 +#define FRAME_death108 169 +#define FRAME_death109 170 +#define FRAME_death110 171 +#define FRAME_death111 172 +#define FRAME_backwd01 173 +#define FRAME_backwd02 174 +#define FRAME_backwd03 175 +#define FRAME_backwd04 176 +#define FRAME_backwd05 177 +#define FRAME_backwd06 178 +#define FRAME_backwd07 179 +#define FRAME_backwd08 180 +#define FRAME_backwd09 181 +#define FRAME_backwd10 182 +#define FRAME_backwd11 183 +#define FRAME_backwd12 184 +#define FRAME_backwd13 185 +#define FRAME_backwd14 186 +#define FRAME_backwd15 187 +#define FRAME_backwd16 188 +#define FRAME_backwd17 189 +#define FRAME_backwd18 190 +#define FRAME_backwd19 191 +#define FRAME_backwd20 192 +#define FRAME_backwd21 193 +#define FRAME_backwd22 194 +#define FRAME_backwd23 195 +#define FRAME_backwd24 196 +#define FRAME_attak101 197 +#define FRAME_attak102 198 +#define FRAME_attak103 199 +#define FRAME_attak104 200 +#define FRAME_attak105 201 +#define FRAME_attak106 202 +#define FRAME_attak107 203 +#define FRAME_attak108 204 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/infantry/infantry.c b/src/game/monster/infantry/infantry.c new file mode 100644 index 000000000..f1fbbb181 --- /dev/null +++ b/src/game/monster/infantry/infantry.c @@ -0,0 +1,1137 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Infantry. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "infantry.h" + +void InfantryMachineGun(edict_t *self); + +static int sound_pain1; +static int sound_pain2; +static int sound_die1; +static int sound_die2; + +static int sound_gunshot; +static int sound_weapon_cock; +static int sound_punch_swing; +static int sound_punch_hit; +static int sound_sight; +static int sound_search; +static int sound_idle; + +static int sound_step; +static int sound_step2; + +void +infantry_footstep(edict_t *self) +{ + if (!g_monsterfootsteps->value) + return; + + // Lazy loading for savegame compatibility. + if (sound_step == 0 || sound_step2 == 0) + { + sound_step = gi.soundindex("infantry/step1.wav"); + sound_step2 = gi.soundindex("infantry/step2.wav"); + } + + if (randk() % 2 == 0) + { + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + } +} + + +static mframe_t infantry_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} +}; + +mmove_t infantry_move_stand = +{ + FRAME_stand50, + FRAME_stand71, + infantry_frames_stand, + NULL +}; + +void +infantry_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &infantry_move_stand; +} + +static mframe_t infantry_frames_fidget[] = { + {ai_stand, 1, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 1, NULL}, + {ai_stand, 3, NULL}, + {ai_stand, 6, NULL}, + {ai_stand, 3, infantry_footstep}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 1, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 1, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, -1, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 1, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, -2, NULL}, + {ai_stand, 1, NULL}, + {ai_stand, 1, NULL}, + {ai_stand, 1, NULL}, + {ai_stand, -1, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, -1, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, -1, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 1, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, -1, NULL}, + {ai_stand, -1, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, -3, NULL}, + {ai_stand, -2, NULL}, + {ai_stand, -3, NULL}, + {ai_stand, -3, infantry_footstep}, + {ai_stand, -2, NULL} +}; + +mmove_t infantry_move_fidget = +{ + FRAME_stand01, + FRAME_stand49, + infantry_frames_fidget, + infantry_stand +}; + +void +infantry_fidget(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &infantry_move_fidget; + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +static mframe_t infantry_frames_walk[] = { + {ai_walk, 5, infantry_footstep}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 6, infantry_footstep}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 5, NULL} +}; + +mmove_t infantry_move_walk = +{ + FRAME_walk03, + FRAME_walk14, + infantry_frames_walk, + NULL +}; + +void +infantry_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &infantry_move_walk; +} + +static mframe_t infantry_frames_run[] = { + {ai_run, 10, NULL}, + {ai_run, 20, infantry_footstep}, + {ai_run, 5, NULL}, + {ai_run, 7, monster_done_dodge}, + {ai_run, 30, NULL}, + {ai_run, 35, infantry_footstep}, + {ai_run, 2, NULL}, + {ai_run, 6, NULL} +}; + +mmove_t infantry_move_run = +{ + FRAME_run01, + FRAME_run08, + infantry_frames_run, + NULL +}; + +void +infantry_run(edict_t *self) +{ + if (!self) + { + return; + } + + monster_done_dodge(self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &infantry_move_stand; + } + else + { + self->monsterinfo.currentmove = &infantry_move_run; + } +} + +static mframe_t infantry_frames_pain1[] = { + {ai_move, -3, NULL}, + {ai_move, -2, NULL}, + {ai_move, -1, NULL}, + {ai_move, -2, NULL}, + {ai_move, -1, infantry_footstep}, + {ai_move, 1, NULL}, + {ai_move, -1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 6, NULL}, + {ai_move, 2, infantry_footstep} +}; + +mmove_t infantry_move_pain1 = +{ + FRAME_pain101, + FRAME_pain110, + infantry_frames_pain1, + infantry_run +}; + +static mframe_t infantry_frames_pain2[] = { + {ai_move, -3, NULL}, + {ai_move, -3, NULL}, + {ai_move, 0, NULL}, + {ai_move, -1, NULL}, + {ai_move, -2, infantry_footstep}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 5, NULL}, + {ai_move, 2, infantry_footstep} +}; + +mmove_t infantry_move_pain2 = +{ + FRAME_pain201, + FRAME_pain210, + infantry_frames_pain2, + infantry_run +}; + +void +infantry_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (!self->groundentity) + { + return; + } + + monster_done_dodge(self); + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 3; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (randk() % 2 == 0) + { + self->monsterinfo.currentmove = &infantry_move_pain1; + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &infantry_move_pain2; + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + + /* clear duck flag */ + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } +} + +vec3_t aimangles[] = { + {0.0, 5.0, 0.0}, + {10.0, 15.0, 0.0}, + {20.0, 25.0, 0.0}, + {25.0, 35.0, 0.0}, + {30.0, 40.0, 0.0}, + {30.0, 45.0, 0.0}, + {25.0, 50.0, 0.0}, + {20.0, 40.0, 0.0}, + {15.0, 35.0, 0.0}, + {40.0, 35.0, 0.0}, + {70.0, 35.0, 0.0}, + {90.0, 35.0, 0.0} +}; + +void +InfantryMachineGun(edict_t *self) +{ + vec3_t start, target; + vec3_t forward, right; + vec3_t vec; + int flash_number; + + if (!self) + { + return; + } + + /* new attack start frame */ + if (self->s.frame == FRAME_attak104) + { + flash_number = MZ2_INFANTRY_MACHINEGUN_1; + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + if (self->enemy && self->enemy->inuse) + { + VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target); + target[2] += self->enemy->viewheight; + VectorSubtract(target, start, forward); + VectorNormalize(forward); + } + else + { + AngleVectors(self->s.angles, forward, right, NULL); + } + } + else + { + flash_number = MZ2_INFANTRY_MACHINEGUN_2 + + (self->s.frame - FRAME_death211); + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + VectorSubtract(self->s.angles, aimangles[flash_number - MZ2_INFANTRY_MACHINEGUN_2], vec); + AngleVectors(vec, forward, NULL, NULL); + } + + monster_fire_bullet(self, start, forward, 3, 4, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + flash_number); +} + +void +infantry_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_sight, 1, ATTN_NORM, 0); +} + +void +infantry_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); + + M_FlyCheck(self); +} + +static mframe_t infantry_frames_death1[] = { + {ai_move, -4, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -1, NULL}, + {ai_move, -4, infantry_footstep}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -1, infantry_footstep}, + {ai_move, 3, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, -2, NULL}, + {ai_move, 2, NULL}, + {ai_move, 2, NULL}, + {ai_move, 9, NULL}, + {ai_move, 9, NULL}, + {ai_move, 5, NULL}, + {ai_move, -3, NULL}, + {ai_move, -3, NULL} +}; + +mmove_t infantry_move_death1 = +{ + FRAME_death101, + FRAME_death120, + infantry_frames_death1, + infantry_dead +}; + +/* Off with his head */ +static mframe_t infantry_frames_death2[] = { + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 5, NULL}, + {ai_move, -1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, infantry_footstep}, + {ai_move, 1, infantry_footstep}, + {ai_move, 4, NULL}, + {ai_move, 3, NULL}, + {ai_move, 0, NULL}, + {ai_move, -2, InfantryMachineGun}, + {ai_move, -2, InfantryMachineGun}, + {ai_move, -3, InfantryMachineGun}, + {ai_move, -1, InfantryMachineGun}, + {ai_move, -2, InfantryMachineGun}, + {ai_move, 0, InfantryMachineGun}, + {ai_move, 2, InfantryMachineGun}, + {ai_move, 2, InfantryMachineGun}, + {ai_move, 3, InfantryMachineGun}, + {ai_move, -10, InfantryMachineGun}, + {ai_move, -7, InfantryMachineGun}, + {ai_move, -8, InfantryMachineGun}, + {ai_move, -6, NULL}, + {ai_move, 4, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t infantry_move_death2 = +{ + FRAME_death201, + FRAME_death225, + infantry_frames_death2, + infantry_dead +}; + +static mframe_t infantry_frames_death3[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -6, NULL}, + {ai_move, -11, NULL}, + {ai_move, -3, NULL}, + {ai_move, -11, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t infantry_move_death3 = +{ + FRAME_death301, + FRAME_death309, + infantry_frames_death3, + infantry_dead +}; + +void +infantry_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 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 */ + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum = 1; /* switch to bloody skin */ + + n = randk() % 3; + + if (n == 0) + { + self->monsterinfo.currentmove = &infantry_move_death1; + gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + } + else if (n == 1) + { + self->monsterinfo.currentmove = &infantry_move_death2; + gi.sound(self, CHAN_VOICE, sound_die1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &infantry_move_death3; + gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0); + } +} + +void +infantry_fire_prep(edict_t *self) +{ + int n; + + if (!self) + { + return; + } + + n = (rand() & 15) + 3 + 1; + self->monsterinfo.pausetime = level.time + n * FRAMETIME; +} + +void +infantry_jump_now(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + monster_jump_start(self); + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +void +infantry_jump2_now(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + monster_jump_start(self); + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 150, forward, self->velocity); + VectorMA(self->velocity, 400, up, self->velocity); +} + +void +infantry_jump_wait_land(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->groundentity == NULL) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + { + self->monsterinfo.nextframe = self->s.frame + 1; + } + } + else + { + self->monsterinfo.nextframe = self->s.frame + 1; + } +} + +static mframe_t infantry_frames_jump[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, infantry_jump_now}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, infantry_jump_wait_land}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t infantry_move_jump = { + FRAME_jump01, + FRAME_jump10, + infantry_frames_jump, + infantry_run +}; + +static mframe_t infantry_frames_jump2[] = { + {ai_move, -8, NULL}, + {ai_move, -4, NULL}, + {ai_move, -4, NULL}, + {ai_move, 0, infantry_jump2_now}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, infantry_jump_wait_land}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t infantry_move_jump2 = { + FRAME_jump01, + FRAME_jump10, + infantry_frames_jump2, + infantry_run +}; + +void +infantry_jump(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->enemy) + { + return; + } + + monster_done_dodge(self); + + if (self->enemy->absmin[2] > self->absmin[2]) + { + self->monsterinfo.currentmove = &infantry_move_jump2; + } + else + { + self->monsterinfo.currentmove = &infantry_move_jump; + } +} + +qboolean +infantry_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + if (blocked_checkjump(self, dist, 192, 40)) + { + infantry_jump(self); + return true; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + return false; +} + +void +infantry_duck_down(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + return; + } + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity(self); +} + +void +infantry_duck_hold(edict_t *self) +{ + if (!self) + { + return; + } + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +void +infantry_duck_up(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + +static mframe_t infantry_frames_duck[] = { + {ai_move, -2, infantry_duck_down}, + {ai_move, -5, infantry_duck_hold}, + {ai_move, 3, NULL}, + {ai_move, 4, infantry_duck_up}, + {ai_move, 0, infantry_footstep} +}; + +mmove_t infantry_move_duck = +{ + FRAME_duck01, + FRAME_duck05, + infantry_frames_duck, + infantry_run +}; + +void +infantry_dodge(edict_t *self, edict_t *attacker, float eta /* unused */, + trace_t *tr /* unused */) +{ + if (!self || !attacker) + { + return; + } + + if (random() > 0.25) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + } + + self->monsterinfo.currentmove = &infantry_move_duck; +} + +void +infantry_set_firetime(edict_t *self) +{ + int n; + + if (!self) + { + return; + } + + n = (randk() & 15) + 5; + self->monsterinfo.pausetime = level.time + n * FRAMETIME; +} + +void +infantry_cock_gun(edict_t *self) +{ + int n; + + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_weapon_cock, 1, ATTN_NORM, 0); + n = (randk() & 15) + 3 + 7; + self->monsterinfo.pausetime = level.time + n * FRAMETIME; +} + +void +infantry_fire(edict_t *self) +{ + if (!self) + { + return; + } + + InfantryMachineGun(self); + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +static mframe_t infantry_frames_attack1[] = { + {ai_charge, -3, infantry_set_firetime}, /* 101 */ + {ai_charge, -2, NULL}, /* 102 */ + {ai_charge, -1, infantry_fire_prep}, /* 103 */ + {ai_charge, 5, infantry_fire}, /* 104 */ + {ai_charge, 1, NULL}, /* 105 */ + {ai_charge, -3, NULL}, /* 106 */ + {ai_charge, -2, NULL}, /* 107 */ + {ai_charge, 2, infantry_cock_gun}, /* 108 */ + {ai_charge, 1, NULL}, /* 109 */ + {ai_charge, 1, NULL}, /* 110 */ + {ai_charge, -1, NULL}, /* 111 */ + {ai_charge, 0, NULL}, /* 112 */ + {ai_charge, -1, NULL}, /* 113 */ + {ai_charge, -1, NULL}, /* 114 */ + {ai_charge, 4, NULL} /* 115 */ +}; + +mmove_t infantry_move_attack1 = +{ + FRAME_attak101, + FRAME_attak115, + infantry_frames_attack1, + infantry_run +}; + +void +infantry_swing(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_punch_swing, 1, ATTN_NORM, 0); +} + +void +infantry_smack(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, 0, 0); + + if (fire_hit(self, aim, (5 + (rand() % 5)), 50)) + { + gi.sound(self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0); + } +} + +static mframe_t infantry_frames_attack2[] = { + {ai_charge, 3, NULL}, + {ai_charge, 6, NULL}, + {ai_charge, 0, infantry_swing}, + {ai_charge, 8, infantry_footstep}, + {ai_charge, 5, NULL}, + {ai_charge, 8, infantry_smack}, + {ai_charge, 6, NULL}, + {ai_charge, 3, NULL}, +}; + +mmove_t infantry_move_attack2 = +{ + FRAME_attak201, + FRAME_attak208, + infantry_frames_attack2, + infantry_run +}; + +void +infantry_duck(edict_t *self, float eta) +{ + if (!self) + { + return; + } + + /* if we're jumping, don't dodge */ + if ((self->monsterinfo.currentmove == &infantry_move_jump) || + (self->monsterinfo.currentmove == &infantry_move_jump2)) + { + return; + } + + if ((self->monsterinfo.currentmove == &infantry_move_attack1) || + (self->monsterinfo.currentmove == &infantry_move_attack2)) + { + /* if we're shooting, and not on easy, don't dodge */ + if (skill->value > SKILL_EASY) + { + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + } + + if (skill->value == SKILL_EASY) + { + /* stupid dodge */ + self->monsterinfo.duck_wait_time = level.time + eta + 1; + } + else + { + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + } + + /* has to be done immediately otherwise he can get stuck */ + monster_duck_down(self); + + self->monsterinfo.nextframe = FRAME_duck01; + self->monsterinfo.currentmove = &infantry_move_duck; + return; +} + +void +infantry_attack(edict_t *self) +{ + if (!self) + { + return; + } + + monster_done_dodge(self); + + if (range(self, self->enemy) == RANGE_MELEE) + { + self->monsterinfo.currentmove = &infantry_move_attack2; + } + else + { + self->monsterinfo.currentmove = &infantry_move_attack1; + } +} + +void +infantry_sidestep(edict_t *self) +{ + if (!self) + { + return; + } + + /* if we're jumping, don't dodge */ + if ((self->monsterinfo.currentmove == &infantry_move_jump) || + (self->monsterinfo.currentmove == &infantry_move_jump2)) + { + return; + } + + if ((self->monsterinfo.currentmove == &infantry_move_attack1) || + (self->monsterinfo.currentmove == &infantry_move_attack2)) + { + /* if we're shooting, and not on easy, don't dodge */ + if (skill->value > SKILL_EASY) + { + self->monsterinfo.aiflags &= ~AI_DODGING; + return; + } + } + + if (self->monsterinfo.currentmove != &infantry_move_run) + { + self->monsterinfo.currentmove = &infantry_move_run; + } +} + +/* + * QUAKED monster_infantry (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_infantry(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + + sound_pain1 = gi.soundindex("infantry/infpain1.wav"); + sound_pain2 = gi.soundindex("infantry/infpain2.wav"); + sound_die1 = gi.soundindex("infantry/infdeth1.wav"); + sound_die2 = gi.soundindex("infantry/infdeth2.wav"); + + sound_gunshot = gi.soundindex("infantry/infatck1.wav"); + sound_weapon_cock = gi.soundindex("infantry/infatck3.wav"); + sound_punch_swing = gi.soundindex("infantry/infatck2.wav"); + sound_punch_hit = gi.soundindex("infantry/melee2.wav"); + + sound_sight = gi.soundindex("infantry/infsght1.wav"); + sound_search = gi.soundindex("infantry/infsrch1.wav"); + sound_idle = gi.soundindex("infantry/infidle1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = -40; + self->mass = 200; + + self->pain = infantry_pain; + self->die = infantry_die; + + self->monsterinfo.stand = infantry_stand; + self->monsterinfo.walk = infantry_walk; + self->monsterinfo.run = infantry_run; + self->monsterinfo.dodge = infantry_dodge; + self->monsterinfo.duck = infantry_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = infantry_sidestep; + self->monsterinfo.attack = infantry_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = infantry_sight; + self->monsterinfo.idle = infantry_fidget; + self->monsterinfo.blocked = infantry_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &infantry_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/src/game/monster/infantry/infantry.h b/src/game/monster/infantry/infantry.h new file mode 100644 index 000000000..c4e510dbf --- /dev/null +++ b/src/game/monster/infantry/infantry.h @@ -0,0 +1,246 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Infantry animations. + * + * ======================================================================= + */ + +#define FRAME_gun02 0 +#define FRAME_stand01 1 +#define FRAME_stand02 2 +#define FRAME_stand03 3 +#define FRAME_stand04 4 +#define FRAME_stand05 5 +#define FRAME_stand06 6 +#define FRAME_stand07 7 +#define FRAME_stand08 8 +#define FRAME_stand09 9 +#define FRAME_stand10 10 +#define FRAME_stand11 11 +#define FRAME_stand12 12 +#define FRAME_stand13 13 +#define FRAME_stand14 14 +#define FRAME_stand15 15 +#define FRAME_stand16 16 +#define FRAME_stand17 17 +#define FRAME_stand18 18 +#define FRAME_stand19 19 +#define FRAME_stand20 20 +#define FRAME_stand21 21 +#define FRAME_stand22 22 +#define FRAME_stand23 23 +#define FRAME_stand24 24 +#define FRAME_stand25 25 +#define FRAME_stand26 26 +#define FRAME_stand27 27 +#define FRAME_stand28 28 +#define FRAME_stand29 29 +#define FRAME_stand30 30 +#define FRAME_stand31 31 +#define FRAME_stand32 32 +#define FRAME_stand33 33 +#define FRAME_stand34 34 +#define FRAME_stand35 35 +#define FRAME_stand36 36 +#define FRAME_stand37 37 +#define FRAME_stand38 38 +#define FRAME_stand39 39 +#define FRAME_stand40 40 +#define FRAME_stand41 41 +#define FRAME_stand42 42 +#define FRAME_stand43 43 +#define FRAME_stand44 44 +#define FRAME_stand45 45 +#define FRAME_stand46 46 +#define FRAME_stand47 47 +#define FRAME_stand48 48 +#define FRAME_stand49 49 +#define FRAME_stand50 50 +#define FRAME_stand51 51 +#define FRAME_stand52 52 +#define FRAME_stand53 53 +#define FRAME_stand54 54 +#define FRAME_stand55 55 +#define FRAME_stand56 56 +#define FRAME_stand57 57 +#define FRAME_stand58 58 +#define FRAME_stand59 59 +#define FRAME_stand60 60 +#define FRAME_stand61 61 +#define FRAME_stand62 62 +#define FRAME_stand63 63 +#define FRAME_stand64 64 +#define FRAME_stand65 65 +#define FRAME_stand66 66 +#define FRAME_stand67 67 +#define FRAME_stand68 68 +#define FRAME_stand69 69 +#define FRAME_stand70 70 +#define FRAME_stand71 71 +#define FRAME_walk01 72 +#define FRAME_walk02 73 +#define FRAME_walk03 74 +#define FRAME_walk04 75 +#define FRAME_walk05 76 +#define FRAME_walk06 77 +#define FRAME_walk07 78 +#define FRAME_walk08 79 +#define FRAME_walk09 80 +#define FRAME_walk10 81 +#define FRAME_walk11 82 +#define FRAME_walk12 83 +#define FRAME_walk13 84 +#define FRAME_walk14 85 +#define FRAME_walk15 86 +#define FRAME_walk16 87 +#define FRAME_walk17 88 +#define FRAME_walk18 89 +#define FRAME_walk19 90 +#define FRAME_walk20 91 +#define FRAME_run01 92 +#define FRAME_run02 93 +#define FRAME_run03 94 +#define FRAME_run04 95 +#define FRAME_run05 96 +#define FRAME_run06 97 +#define FRAME_run07 98 +#define FRAME_run08 99 +#define FRAME_pain101 100 +#define FRAME_pain102 101 +#define FRAME_pain103 102 +#define FRAME_pain104 103 +#define FRAME_pain105 104 +#define FRAME_pain106 105 +#define FRAME_pain107 106 +#define FRAME_pain108 107 +#define FRAME_pain109 108 +#define FRAME_pain110 109 +#define FRAME_pain201 110 +#define FRAME_pain202 111 +#define FRAME_pain203 112 +#define FRAME_pain204 113 +#define FRAME_pain205 114 +#define FRAME_pain206 115 +#define FRAME_pain207 116 +#define FRAME_pain208 117 +#define FRAME_pain209 118 +#define FRAME_pain210 119 +#define FRAME_duck01 120 +#define FRAME_duck02 121 +#define FRAME_duck03 122 +#define FRAME_duck04 123 +#define FRAME_duck05 124 +#define FRAME_death101 125 +#define FRAME_death102 126 +#define FRAME_death103 127 +#define FRAME_death104 128 +#define FRAME_death105 129 +#define FRAME_death106 130 +#define FRAME_death107 131 +#define FRAME_death108 132 +#define FRAME_death109 133 +#define FRAME_death110 134 +#define FRAME_death111 135 +#define FRAME_death112 136 +#define FRAME_death113 137 +#define FRAME_death114 138 +#define FRAME_death115 139 +#define FRAME_death116 140 +#define FRAME_death117 141 +#define FRAME_death118 142 +#define FRAME_death119 143 +#define FRAME_death120 144 +#define FRAME_death201 145 +#define FRAME_death202 146 +#define FRAME_death203 147 +#define FRAME_death204 148 +#define FRAME_death205 149 +#define FRAME_death206 150 +#define FRAME_death207 151 +#define FRAME_death208 152 +#define FRAME_death209 153 +#define FRAME_death210 154 +#define FRAME_death211 155 +#define FRAME_death212 156 +#define FRAME_death213 157 +#define FRAME_death214 158 +#define FRAME_death215 159 +#define FRAME_death216 160 +#define FRAME_death217 161 +#define FRAME_death218 162 +#define FRAME_death219 163 +#define FRAME_death220 164 +#define FRAME_death221 165 +#define FRAME_death222 166 +#define FRAME_death223 167 +#define FRAME_death224 168 +#define FRAME_death225 169 +#define FRAME_death301 170 +#define FRAME_death302 171 +#define FRAME_death303 172 +#define FRAME_death304 173 +#define FRAME_death305 174 +#define FRAME_death306 175 +#define FRAME_death307 176 +#define FRAME_death308 177 +#define FRAME_death309 178 +#define FRAME_block01 179 +#define FRAME_block02 180 +#define FRAME_block03 181 +#define FRAME_block04 182 +#define FRAME_block05 183 +#define FRAME_attak101 184 +#define FRAME_attak102 185 +#define FRAME_attak103 186 +#define FRAME_attak104 187 +#define FRAME_attak105 188 +#define FRAME_attak106 189 +#define FRAME_attak107 190 +#define FRAME_attak108 191 +#define FRAME_attak109 192 +#define FRAME_attak110 193 +#define FRAME_attak111 194 +#define FRAME_attak112 195 +#define FRAME_attak113 196 +#define FRAME_attak114 197 +#define FRAME_attak115 198 +#define FRAME_attak201 199 +#define FRAME_attak202 200 +#define FRAME_attak203 201 +#define FRAME_attak204 202 +#define FRAME_attak205 203 +#define FRAME_attak206 204 +#define FRAME_attak207 205 +#define FRAME_attak208 206 + +#define FRAME_jump01 207 +#define FRAME_jump02 208 +#define FRAME_jump03 209 +#define FRAME_jump04 210 +#define FRAME_jump05 211 +#define FRAME_jump06 212 +#define FRAME_jump07 213 +#define FRAME_jump08 214 +#define FRAME_jump09 215 +#define FRAME_jump10 216 +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/insane/insane.c b/src/game/monster/insane/insane.c new file mode 100644 index 000000000..b5e161bfb --- /dev/null +++ b/src/game/monster/insane/insane.c @@ -0,0 +1,1027 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * The insane earth soldiers. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "insane.h" + +#define SPAWNFLAG_CRUSIFIED 8 + +static int sound_fist; +static int sound_shake; +static int sound_moan; +static int sound_scream[8]; + +static int sound_step; +static int sound_step2; +static int sound_step3; +static int sound_step4; + +void insane_stand(edict_t *self); +void insane_dead(edict_t *self); +void insane_cross(edict_t *self); +void insane_walk(edict_t *self); +void insane_run(edict_t *self); +void insane_checkdown(edict_t *self); +void insane_checkup(edict_t *self); +void insane_onground(edict_t *self); + +void +insane_footstep(edict_t *self) +{ + if (!g_monsterfootsteps->value) + return; + + // Lazy loading for savegame compatibility. + if (sound_step == 0 || sound_step2 == 0 || sound_step3 == 0 || sound_step4 == 0) + { + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); + } + + int i; + i = randk() % 4; + + if (i == 0) + { + gi.sound(self, CHAN_BODY, sound_step, 0.7, ATTN_NORM, 0); + } + else if (i == 1) + { + gi.sound(self, CHAN_BODY, sound_step2, 0.7, ATTN_NORM, 0); + } + else if (i == 2) + { + gi.sound(self, CHAN_BODY, sound_step3, 0.7, ATTN_NORM, 0); + } + else if (i == 3) + { + gi.sound(self, CHAN_BODY, sound_step4, 0.7, ATTN_NORM, 0); + } +} + + +void +insane_fist(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_fist, 1, ATTN_IDLE, 0); +} + +void +insane_shake(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_shake, 1, ATTN_IDLE, 0); +} + +void +insane_moan(edict_t *self) +{ + if (!self) + { + return; + } + + /* suppress screaming so pain sound can play */ + if (self->fly_sound_debounce_time > level.time) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_moan, 1, ATTN_IDLE, 0); +} + +void +insane_scream(edict_t *self) +{ + if (!self) + { + return; + } + + /* suppress screaming so pain sound can play */ + if (self->fly_sound_debounce_time > level.time) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_scream[randk() % 8], 1, ATTN_IDLE, 0); +} + +static mframe_t insane_frames_stand_normal[] = { + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, insane_checkdown} +}; + +mmove_t insane_move_stand_normal = +{ + FRAME_stand60, + FRAME_stand65, + insane_frames_stand_normal, + insane_stand +}; + +static mframe_t insane_frames_stand_insane[] = { + {ai_stand, 0, insane_shake}, + {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, insane_checkdown} +}; + +mmove_t insane_move_stand_insane = +{ + FRAME_stand65, + FRAME_stand94, + insane_frames_stand_insane, + insane_stand +}; + +static mframe_t insane_frames_uptodown[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, insane_moan}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 2.7, NULL}, + {ai_move, 4.1, NULL}, + {ai_move, 6, NULL}, + {ai_move, 7.6, NULL}, + {ai_move, 3.6, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, insane_fist}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, insane_fist}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t insane_move_uptodown = +{ + FRAME_stand1, + FRAME_stand40, + insane_frames_uptodown, + insane_onground +}; + +static mframe_t insane_frames_downtoup[] = { + {ai_move, -0.7, NULL}, /* 41 */ + {ai_move, -1.2, NULL}, /* 42 */ + {ai_move, -1.5, NULL}, /* 43 */ + {ai_move, -4.5, NULL}, /* 44 */ + {ai_move, -3.5, NULL}, /* 45 */ + {ai_move, -0.2, NULL}, /* 46 */ + {ai_move, 0, NULL}, /* 47 */ + {ai_move, -1.3, NULL}, /* 48 */ + {ai_move, -3, NULL}, /* 49 */ + {ai_move, -2, NULL}, /* 50 */ + {ai_move, 0, NULL}, /* 51 */ + {ai_move, 0, NULL}, /* 52 */ + {ai_move, 0, NULL}, /* 53 */ + {ai_move, -3.3, NULL}, /* 54 */ + {ai_move, -1.6, NULL}, /* 55 */ + {ai_move, -0.3, NULL}, /* 56 */ + {ai_move, 0, NULL}, /* 57 */ + {ai_move, 0, NULL}, /* 58 */ + {ai_move, 0, NULL} /* 59 */ +}; + +mmove_t insane_move_downtoup = +{ + FRAME_stand41, + FRAME_stand59, + insane_frames_downtoup, + insane_stand +}; + +static mframe_t insane_frames_jumpdown[] = { + {ai_move, 0.2, NULL}, + {ai_move, 11.5, NULL}, + {ai_move, 5.1, NULL}, + {ai_move, 7.1, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t insane_move_jumpdown = +{ + FRAME_stand96, + FRAME_stand100, + insane_frames_jumpdown, + insane_onground +}; + +static mframe_t insane_frames_down[] = { + {ai_move, 0, NULL}, /* 100 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 110 */ + {ai_move, -1.7, NULL}, + {ai_move, -1.6, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, insane_fist}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 120 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 130 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, insane_moan}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 140 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 150 */ + {ai_move, 0.5, NULL}, + {ai_move, 0, NULL}, + {ai_move, -0.2, insane_scream}, + {ai_move, 0, NULL}, + {ai_move, 0.2, NULL}, + {ai_move, 0.4, NULL}, + {ai_move, 0.6, NULL}, + {ai_move, 0.8, NULL}, + {ai_move, 0.7, NULL}, + {ai_move, 0, insane_checkup} /* 160 */ +}; + +mmove_t insane_move_down = +{ + FRAME_stand100, + FRAME_stand160, + insane_frames_down, + insane_onground +}; + +static mframe_t insane_frames_walk_normal[] = { + {ai_walk, 0, insane_scream}, + {ai_walk, 2.5, NULL}, + {ai_walk, 3.5, NULL}, + {ai_walk, 1.7, NULL}, + {ai_walk, 2.3, NULL}, + {ai_walk, 2.4, NULL}, + {ai_walk, 2.2, insane_footstep}, + {ai_walk, 4.2, NULL}, + {ai_walk, 5.6, NULL}, + {ai_walk, 3.3, NULL}, + {ai_walk, 2.4, NULL}, + {ai_walk, 0.9, NULL}, + {ai_walk, 0, insane_footstep} +}; + +mmove_t insane_move_walk_normal = +{ + FRAME_walk27, + FRAME_walk39, + insane_frames_walk_normal, + insane_walk +}; + +mmove_t insane_move_run_normal = +{ + FRAME_walk27, + FRAME_walk39, + insane_frames_walk_normal, + insane_run +}; + +static mframe_t insane_frames_walk_insane[] = { + {ai_walk, 0, insane_scream}, /* walk 1 */ + {ai_walk, 3.4, NULL}, /* walk 2 */ + {ai_walk, 3.6, NULL}, /* 3 */ + {ai_walk, 2.9, NULL}, /* 4 */ + {ai_walk, 2.2, NULL}, /* 5 */ + {ai_walk, 2.6, NULL}, /* 6 */ + {ai_walk, 0, insane_footstep}, /* 7 */ + {ai_walk, 0.7, NULL}, /* 8 */ + {ai_walk, 4.8, NULL}, /* 9 */ + {ai_walk, 5.3, NULL}, /* 10 */ + {ai_walk, 1.1, NULL}, /* 11 */ + {ai_walk, 2, insane_footstep}, /* 12 */ + {ai_walk, 0.5, NULL}, /* 13 */ + {ai_walk, 0, NULL}, /* 14 */ + {ai_walk, 0, NULL}, /* 15 */ + {ai_walk, 4.9, NULL}, /* 16 */ + {ai_walk, 6.7, NULL}, /* 17 */ + {ai_walk, 3.8, NULL}, /* 18 */ + {ai_walk, 2, insane_footstep}, /* 19 */ + {ai_walk, 0.2, NULL}, /* 20 */ + {ai_walk, 0, NULL}, /* 21 */ + {ai_walk, 3.4, NULL}, /* 22 */ + {ai_walk, 6.4, NULL}, /* 23 */ + {ai_walk, 5, NULL}, /* 24 */ + {ai_walk, 1.8, insane_footstep}, /* 25 */ + {ai_walk, 0, NULL} /* 26 */ +}; + +mmove_t insane_move_walk_insane = +{ + FRAME_walk1, + FRAME_walk26, + insane_frames_walk_insane, + insane_walk +}; + +mmove_t insane_move_run_insane = +{ + FRAME_walk1, + FRAME_walk26, + insane_frames_walk_insane, + insane_run +}; + +static mframe_t insane_frames_stand_pain[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, insane_footstep}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t insane_move_stand_pain = +{ + FRAME_st_pain2, + FRAME_st_pain12, + insane_frames_stand_pain, + insane_run +}; + +static mframe_t insane_frames_stand_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t insane_move_stand_death = +{ + FRAME_st_death2, + FRAME_st_death18, + insane_frames_stand_death, + insane_dead +}; + +static mframe_t insane_frames_crawl[] = { + {ai_walk, 0, insane_scream}, + {ai_walk, 1.5, NULL}, + {ai_walk, 2.1, NULL}, + {ai_walk, 3.6, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 0.9, NULL}, + {ai_walk, 3, NULL}, + {ai_walk, 3.4, NULL}, + {ai_walk, 2.4, NULL} +}; + +mmove_t insane_move_crawl = +{ + FRAME_crawl1, + FRAME_crawl9, + insane_frames_crawl, + NULL +}; + +mmove_t insane_move_runcrawl = +{ + FRAME_crawl1, + FRAME_crawl9, + insane_frames_crawl, + NULL +}; + +static mframe_t insane_frames_crawl_pain[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t insane_move_crawl_pain = +{ + FRAME_cr_pain2, + FRAME_cr_pain10, + insane_frames_crawl_pain, + insane_run +}; + +static mframe_t insane_frames_crawl_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t insane_move_crawl_death = +{ + FRAME_cr_death10, + FRAME_cr_death16, + insane_frames_crawl_death, + insane_dead +}; + +static mframe_t insane_frames_cross[] = { + {ai_move, 0, insane_moan}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t insane_move_cross = +{ + FRAME_cross1, + FRAME_cross15, + insane_frames_cross, + insane_cross +}; + +static mframe_t insane_frames_struggle_cross[] = { + {ai_move, 0, insane_scream}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t insane_move_struggle_cross = +{ + FRAME_cross16, + FRAME_cross30, + insane_frames_struggle_cross, + insane_cross +}; + +void +insane_cross(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.8) + { + self->monsterinfo.currentmove = &insane_move_cross; + } + else + { + self->monsterinfo.currentmove = &insane_move_struggle_cross; + } +} + +void +insane_walk(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->spawnflags & 16) /* Hold Ground? */ + { + if (self->s.frame == FRAME_cr_pain10) + { + self->monsterinfo.currentmove = &insane_move_down; + return; + } + } + + if (self->spawnflags & 4) + { + self->monsterinfo.currentmove = &insane_move_crawl; + } + else + if (random() <= 0.5) + { + self->monsterinfo.currentmove = &insane_move_walk_normal; + } + else + { + self->monsterinfo.currentmove = &insane_move_walk_insane; + } +} + +void +insane_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->spawnflags & 16) /* Hold Ground? */ + { + if (self->s.frame == FRAME_cr_pain10) + { + self->monsterinfo.currentmove = &insane_move_down; + return; + } + } + + if (self->spawnflags & 4) /* Crawling? */ + { + self->monsterinfo.currentmove = &insane_move_runcrawl; + } + else if (frandk() <= 0.5) /* Else, mix it up */ + { + self->monsterinfo.currentmove = &insane_move_run_normal; + } + else + { + self->monsterinfo.currentmove = &insane_move_run_insane; + } +} + +void +insane_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + int l, r; + + if (!self) + { + return; + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 3; + + r = 1 + (randk() & 1); + + if (self->health < 25) + { + l = 25; + } + else if (self->health < 50) + { + l = 50; + } + else if (self->health < 75) + { + l = 75; + } + else + { + l = 100; + } + + gi.sound(self, CHAN_VOICE, gi.soundindex(va("player/male/pain%i_%i.wav", + l, r)), 1, ATTN_IDLE, 0); + + /* suppress screaming and moaning for 1 second so pain sound plays */ + self->fly_sound_debounce_time = level.time + 1; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + /* Don't go into pain frames if crucified. */ + if (self->spawnflags & SPAWNFLAG_CRUSIFIED) + { + self->monsterinfo.currentmove = &insane_move_struggle_cross; + return; + } + + if (((self->s.frame >= FRAME_crawl1) && + (self->s.frame <= FRAME_crawl9)) || + ((self->s.frame >= FRAME_stand99) && + (self->s.frame <= FRAME_stand160))) + { + self->monsterinfo.currentmove = &insane_move_crawl_pain; + } + else + { + self->monsterinfo.currentmove = &insane_move_stand_pain; + } +} + +void +insane_onground(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &insane_move_down; +} + +void +insane_checkdown(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->spawnflags & 32) /* Always stand */ + { + return; + } + + if (random() < 0.3) + { + if (random() < 0.5) + { + self->monsterinfo.currentmove = &insane_move_uptodown; + } + else + { + self->monsterinfo.currentmove = &insane_move_jumpdown; + } + } +} + +void +insane_checkup(edict_t *self) +{ + if (!self) + { + return; + } + + /* If Hold_Ground and Crawl are set */ + if ((self->spawnflags & 4) && (self->spawnflags & 16)) + { + return; + } + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &insane_move_downtoup; + } +} + +void +insane_stand(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->spawnflags & SPAWNFLAG_CRUSIFIED) /* If crucified */ + { + self->monsterinfo.currentmove = &insane_move_cross; + self->monsterinfo.aiflags |= AI_STAND_GROUND; + } + /* If Hold_Ground and Crawl are set */ + else if ((self->spawnflags & 4) && (self->spawnflags & 16)) + { + self->monsterinfo.currentmove = &insane_move_down; + } + else + if (random() < 0.5) + { + self->monsterinfo.currentmove = &insane_move_stand_normal; + } + else + { + self->monsterinfo.currentmove = &insane_move_stand_insane; + } +} + +void +insane_dead(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->spawnflags & SPAWNFLAG_CRUSIFIED) + { + self->flags |= FL_FLY; + } + else + { + 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); +} + +void +insane_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex( + "misc/udeath.wav"), 1, ATTN_IDLE, 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; + } + + gi.sound(self, CHAN_VOICE, gi.soundindex(va("player/male/death%i.wav", + (randk() % 4) + 1)), 1, ATTN_IDLE, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (self->spawnflags & SPAWNFLAG_CRUSIFIED) + { + insane_dead(self); + } + else + { + if (((self->s.frame >= FRAME_crawl1) && + (self->s.frame <= FRAME_crawl9)) || + ((self->s.frame >= FRAME_stand99) && + (self->s.frame <= FRAME_stand160))) + { + self->monsterinfo.currentmove = &insane_move_crawl_death; + } + else + { + self->monsterinfo.currentmove = &insane_move_stand_death; + } + } +} + +/* + * QUAKED misc_insane (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn CRAWL CRUCIFIED STAND_GROUND ALWAYS_STAND + */ +void +SP_misc_insane(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + sound_step3 = 0; + sound_step4 = 0; + + sound_fist = gi.soundindex("insane/insane11.wav"); + sound_shake = gi.soundindex("insane/insane5.wav"); + sound_moan = gi.soundindex("insane/insane7.wav"); + sound_scream[0] = gi.soundindex("insane/insane1.wav"); + sound_scream[1] = gi.soundindex("insane/insane2.wav"); + sound_scream[2] = gi.soundindex("insane/insane3.wav"); + sound_scream[3] = gi.soundindex("insane/insane4.wav"); + sound_scream[4] = gi.soundindex("insane/insane6.wav"); + sound_scream[5] = gi.soundindex("insane/insane8.wav"); + sound_scream[6] = gi.soundindex("insane/insane9.wav"); + sound_scream[7] = gi.soundindex("insane/insane10.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/insane/tris.md2"); + + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 32); + + self->health = 100; + self->gib_health = -50; + self->mass = 300; + + self->pain = insane_pain; + self->die = insane_die; + + self->monsterinfo.stand = insane_stand; + self->monsterinfo.walk = insane_walk; + self->monsterinfo.run = insane_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = NULL; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + self->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity(self); + + if (self->spawnflags & 16) /* Stand Ground */ + { + self->monsterinfo.aiflags |= AI_STAND_GROUND; + } + + self->monsterinfo.currentmove = &insane_move_stand_normal; + + self->monsterinfo.scale = MODEL_SCALE; + + if (self->spawnflags & SPAWNFLAG_CRUSIFIED) /* Crucified ? */ + { + VectorSet(self->mins, -16, 0, 0); + VectorSet(self->maxs, 16, 8, 32); + self->flags |= FL_NO_KNOCKBACK; + flymonster_start(self); + } + else + { + walkmonster_start(self); + self->s.skinnum = randk() % 3; + } +} diff --git a/src/game/monster/insane/insane.h b/src/game/monster/insane/insane.h new file mode 100644 index 000000000..1965bf147 --- /dev/null +++ b/src/game/monster/insane/insane.h @@ -0,0 +1,311 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Insane animations + * + * ======================================================================= + */ + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_stand8 7 +#define FRAME_stand9 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_stand41 40 +#define FRAME_stand42 41 +#define FRAME_stand43 42 +#define FRAME_stand44 43 +#define FRAME_stand45 44 +#define FRAME_stand46 45 +#define FRAME_stand47 46 +#define FRAME_stand48 47 +#define FRAME_stand49 48 +#define FRAME_stand50 49 +#define FRAME_stand51 50 +#define FRAME_stand52 51 +#define FRAME_stand53 52 +#define FRAME_stand54 53 +#define FRAME_stand55 54 +#define FRAME_stand56 55 +#define FRAME_stand57 56 +#define FRAME_stand58 57 +#define FRAME_stand59 58 +#define FRAME_stand60 59 +#define FRAME_stand61 60 +#define FRAME_stand62 61 +#define FRAME_stand63 62 +#define FRAME_stand64 63 +#define FRAME_stand65 64 +#define FRAME_stand66 65 +#define FRAME_stand67 66 +#define FRAME_stand68 67 +#define FRAME_stand69 68 +#define FRAME_stand70 69 +#define FRAME_stand71 70 +#define FRAME_stand72 71 +#define FRAME_stand73 72 +#define FRAME_stand74 73 +#define FRAME_stand75 74 +#define FRAME_stand76 75 +#define FRAME_stand77 76 +#define FRAME_stand78 77 +#define FRAME_stand79 78 +#define FRAME_stand80 79 +#define FRAME_stand81 80 +#define FRAME_stand82 81 +#define FRAME_stand83 82 +#define FRAME_stand84 83 +#define FRAME_stand85 84 +#define FRAME_stand86 85 +#define FRAME_stand87 86 +#define FRAME_stand88 87 +#define FRAME_stand89 88 +#define FRAME_stand90 89 +#define FRAME_stand91 90 +#define FRAME_stand92 91 +#define FRAME_stand93 92 +#define FRAME_stand94 93 +#define FRAME_stand95 94 +#define FRAME_stand96 95 +#define FRAME_stand97 96 +#define FRAME_stand98 97 +#define FRAME_stand99 98 +#define FRAME_stand100 99 +#define FRAME_stand101 100 +#define FRAME_stand102 101 +#define FRAME_stand103 102 +#define FRAME_stand104 103 +#define FRAME_stand105 104 +#define FRAME_stand106 105 +#define FRAME_stand107 106 +#define FRAME_stand108 107 +#define FRAME_stand109 108 +#define FRAME_stand110 109 +#define FRAME_stand111 110 +#define FRAME_stand112 111 +#define FRAME_stand113 112 +#define FRAME_stand114 113 +#define FRAME_stand115 114 +#define FRAME_stand116 115 +#define FRAME_stand117 116 +#define FRAME_stand118 117 +#define FRAME_stand119 118 +#define FRAME_stand120 119 +#define FRAME_stand121 120 +#define FRAME_stand122 121 +#define FRAME_stand123 122 +#define FRAME_stand124 123 +#define FRAME_stand125 124 +#define FRAME_stand126 125 +#define FRAME_stand127 126 +#define FRAME_stand128 127 +#define FRAME_stand129 128 +#define FRAME_stand130 129 +#define FRAME_stand131 130 +#define FRAME_stand132 131 +#define FRAME_stand133 132 +#define FRAME_stand134 133 +#define FRAME_stand135 134 +#define FRAME_stand136 135 +#define FRAME_stand137 136 +#define FRAME_stand138 137 +#define FRAME_stand139 138 +#define FRAME_stand140 139 +#define FRAME_stand141 140 +#define FRAME_stand142 141 +#define FRAME_stand143 142 +#define FRAME_stand144 143 +#define FRAME_stand145 144 +#define FRAME_stand146 145 +#define FRAME_stand147 146 +#define FRAME_stand148 147 +#define FRAME_stand149 148 +#define FRAME_stand150 149 +#define FRAME_stand151 150 +#define FRAME_stand152 151 +#define FRAME_stand153 152 +#define FRAME_stand154 153 +#define FRAME_stand155 154 +#define FRAME_stand156 155 +#define FRAME_stand157 156 +#define FRAME_stand158 157 +#define FRAME_stand159 158 +#define FRAME_stand160 159 +#define FRAME_walk27 160 +#define FRAME_walk28 161 +#define FRAME_walk29 162 +#define FRAME_walk30 163 +#define FRAME_walk31 164 +#define FRAME_walk32 165 +#define FRAME_walk33 166 +#define FRAME_walk34 167 +#define FRAME_walk35 168 +#define FRAME_walk36 169 +#define FRAME_walk37 170 +#define FRAME_walk38 171 +#define FRAME_walk39 172 +#define FRAME_walk1 173 +#define FRAME_walk2 174 +#define FRAME_walk3 175 +#define FRAME_walk4 176 +#define FRAME_walk5 177 +#define FRAME_walk6 178 +#define FRAME_walk7 179 +#define FRAME_walk8 180 +#define FRAME_walk9 181 +#define FRAME_walk10 182 +#define FRAME_walk11 183 +#define FRAME_walk12 184 +#define FRAME_walk13 185 +#define FRAME_walk14 186 +#define FRAME_walk15 187 +#define FRAME_walk16 188 +#define FRAME_walk17 189 +#define FRAME_walk18 190 +#define FRAME_walk19 191 +#define FRAME_walk20 192 +#define FRAME_walk21 193 +#define FRAME_walk22 194 +#define FRAME_walk23 195 +#define FRAME_walk24 196 +#define FRAME_walk25 197 +#define FRAME_walk26 198 +#define FRAME_st_pain2 199 +#define FRAME_st_pain3 200 +#define FRAME_st_pain4 201 +#define FRAME_st_pain5 202 +#define FRAME_st_pain6 203 +#define FRAME_st_pain7 204 +#define FRAME_st_pain8 205 +#define FRAME_st_pain9 206 +#define FRAME_st_pain10 207 +#define FRAME_st_pain11 208 +#define FRAME_st_pain12 209 +#define FRAME_st_death2 210 +#define FRAME_st_death3 211 +#define FRAME_st_death4 212 +#define FRAME_st_death5 213 +#define FRAME_st_death6 214 +#define FRAME_st_death7 215 +#define FRAME_st_death8 216 +#define FRAME_st_death9 217 +#define FRAME_st_death10 218 +#define FRAME_st_death11 219 +#define FRAME_st_death12 220 +#define FRAME_st_death13 221 +#define FRAME_st_death14 222 +#define FRAME_st_death15 223 +#define FRAME_st_death16 224 +#define FRAME_st_death17 225 +#define FRAME_st_death18 226 +#define FRAME_crawl1 227 +#define FRAME_crawl2 228 +#define FRAME_crawl3 229 +#define FRAME_crawl4 230 +#define FRAME_crawl5 231 +#define FRAME_crawl6 232 +#define FRAME_crawl7 233 +#define FRAME_crawl8 234 +#define FRAME_crawl9 235 +#define FRAME_cr_pain2 236 +#define FRAME_cr_pain3 237 +#define FRAME_cr_pain4 238 +#define FRAME_cr_pain5 239 +#define FRAME_cr_pain6 240 +#define FRAME_cr_pain7 241 +#define FRAME_cr_pain8 242 +#define FRAME_cr_pain9 243 +#define FRAME_cr_pain10 244 +#define FRAME_cr_death10 245 +#define FRAME_cr_death11 246 +#define FRAME_cr_death12 247 +#define FRAME_cr_death13 248 +#define FRAME_cr_death14 249 +#define FRAME_cr_death15 250 +#define FRAME_cr_death16 251 +#define FRAME_cross1 252 +#define FRAME_cross2 253 +#define FRAME_cross3 254 +#define FRAME_cross4 255 +#define FRAME_cross5 256 +#define FRAME_cross6 257 +#define FRAME_cross7 258 +#define FRAME_cross8 259 +#define FRAME_cross9 260 +#define FRAME_cross10 261 +#define FRAME_cross11 262 +#define FRAME_cross12 263 +#define FRAME_cross13 264 +#define FRAME_cross14 265 +#define FRAME_cross15 266 +#define FRAME_cross16 267 +#define FRAME_cross17 268 +#define FRAME_cross18 269 +#define FRAME_cross19 270 +#define FRAME_cross20 271 +#define FRAME_cross21 272 +#define FRAME_cross22 273 +#define FRAME_cross23 274 +#define FRAME_cross24 275 +#define FRAME_cross25 276 +#define FRAME_cross26 277 +#define FRAME_cross27 278 +#define FRAME_cross28 279 +#define FRAME_cross29 280 +#define FRAME_cross30 281 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/knight/knight.c b/src/game/monster/knight/knight.c new file mode 100644 index 000000000..408d5051c --- /dev/null +++ b/src/game/monster/knight/knight.c @@ -0,0 +1,401 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "knight.h" + +static int sound_death; +static int sound_pain; +static int sound_sight; +static int sound_search; +static int sound_melee1; +static int sound_melee2; + +void knight_attack(edict_t *self); + +// Stand +static mframe_t knight_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}, +}; +mmove_t knight_move_stand = +{ + FRAME_stand1, + FRAME_stand9, + knight_frames_stand, + NULL +}; + +void +knight_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &knight_move_stand; +} + +// Run +static mframe_t knight_frames_run [] = +{ + {ai_run, 16, NULL}, + {ai_run, 20, NULL}, + {ai_run, 13, NULL}, + {ai_run, 7, NULL}, + + {ai_run, 16, NULL}, + {ai_run, 20, NULL}, + {ai_run, 14, NULL}, + {ai_run, 6, knight_attack} +}; +mmove_t knight_move_run = +{ + FRAME_runb1, + FRAME_runb8, + knight_frames_run, + NULL +}; + +void +knight_run(edict_t *self) +{ + self->monsterinfo.currentmove = &knight_move_run; +} + +static void +knight_attack_swing(edict_t *self) +{ + if (random() > 0.5) + { + gi.sound(self, CHAN_WEAPON, sound_melee1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, sound_melee2, 1, ATTN_NORM, 0); + } +} + +void +swing_sword_step(edict_t *self) +{ + vec3_t dir; + static vec3_t aim = {100, 0, -24}; + int damage; + + if (!self->enemy) + { + return; + } + + VectorSubtract(self->s.origin, self->enemy->s.origin, dir); + + if (VectorLength(dir) > 100.0) + { + return; + } + + damage = (random() + random() + random()) * 3; + + fire_hit(self, aim, damage, damage); +} + +// Attack +static mframe_t knight_frames_attack [] = +{ + {ai_charge, 16, knight_attack_swing}, + {ai_charge, 20, NULL}, + {ai_charge, 13, NULL}, + {ai_charge, 7, NULL}, + + {ai_charge, 16, swing_sword_step}, + {ai_charge, 20, swing_sword_step}, + {ai_charge, 14, swing_sword_step}, + {ai_charge, 6, swing_sword_step}, + + {ai_charge, 14, swing_sword_step}, + {ai_charge, 10, NULL}, + {ai_charge, 7, NULL} +}; +mmove_t knight_move_attack = +{ + FRAME_runattack1, + FRAME_runattack11, + knight_frames_attack, + knight_run +}; + +void +knight_attack(edict_t *self) +{ + if (self->enemy && realrange(self, self->enemy) < (MELEE_DISTANCE * 4)) + { + self->monsterinfo.currentmove = &knight_move_attack; + } + else + { + self->monsterinfo.currentmove = &knight_move_run; + } +} + +static void +knight_melee_swing(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_melee1, 1, ATTN_NORM, 0); +} + +// Melee +static mframe_t knight_frames_melee [] = +{ + {ai_charge, 0, knight_melee_swing}, + {ai_charge, 7, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 4, swing_sword_step}, + {ai_charge, 4, swing_sword_step}, + {ai_charge, 1, swing_sword_step}, + {ai_charge, 3, NULL}, + + {ai_charge, 1, NULL}, + {ai_charge, 5, NULL} +}; +mmove_t knight_move_melee = +{ + FRAME_attackb1, + FRAME_attackb11, + knight_frames_melee, + knight_run +}; + +void +knight_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &knight_move_melee; +} + +// Pain (1) +static mframe_t knight_frames_pain1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t knight_move_pain1 = +{ + FRAME_pain1, + FRAME_pain3, + knight_frames_pain1, + knight_run +}; + +// Pain (2) +static mframe_t knight_frames_pain2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 3, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 2, NULL}, + {ai_move, 4, NULL}, + {ai_move, 2, NULL}, + {ai_move, 5, NULL}, + + {ai_move, 5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t knight_move_pain2 = +{ + FRAME_painb1, + FRAME_painb11, + knight_frames_pain2, + knight_run +}; + +void +knight_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + // decino: No pain animations in Nightmare mode + if (skill->value == SKILL_HARDPLUS) + return; + if (level.time < self->pain_debounce_time) + return; + if (random() < 0.85) + self->monsterinfo.currentmove = &knight_move_pain1; + else + self->monsterinfo.currentmove = &knight_move_pain2; + self->pain_debounce_time = level.time + 1; + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); +} + +void +knight_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); +} + +// Death (1) +static mframe_t knight_frames_die1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t knight_move_die1 = +{ + FRAME_death1, + FRAME_death10, + knight_frames_die1, + knight_dead +}; + +// Death (2) +static mframe_t knight_frames_die2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t knight_move_die2 = +{ + FRAME_deathb1, + FRAME_deathb11, + knight_frames_die2, + knight_dead +}; + +void +knight_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 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; + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (random() < 0.5) + self->monsterinfo.currentmove = &knight_move_die1; + else + self->monsterinfo.currentmove = &knight_move_die2; +} + +// Search +void +knight_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +// Sight +void +knight_sight(edict_t *self, edict_t *other /* unused */) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +/* + * QUAKED monster_knight (1 .5 0) (-16, -16, -24) (16, 16, 40) Ambush Trigger_Spawn Sight + */ +void +SP_monster_knight(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/knight/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 40); + self->health = 75; + + sound_melee1 = gi.soundindex("knight/sword1.wav"); + sound_melee2 = gi.soundindex("knight/sword2.wav"); + sound_death = gi.soundindex("knight/kdeath.wav"); + sound_pain = gi.soundindex("knight/khurt.wav"); + sound_sight = gi.soundindex("knight/ksight.wav"); + sound_search = gi.soundindex("knight/idle.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -40; + self->mass = 75; + + self->monsterinfo.stand = knight_stand; + self->monsterinfo.walk = knight_run; + self->monsterinfo.run = knight_run; + self->monsterinfo.attack = knight_attack; + self->monsterinfo.melee = knight_melee; + self->monsterinfo.sight = knight_sight; + self->monsterinfo.search = knight_search; + + self->pain = knight_pain; + self->die = knight_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/knight/knight.h b/src/game/monster/knight/knight.h new file mode 100644 index 000000000..9872dcb26 --- /dev/null +++ b/src/game/monster/knight/knight.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Knight animations. + * + * ======================================================================= + */ + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_stand8 7 +#define FRAME_stand9 8 +#define FRAME_runb1 9 +#define FRAME_runb2 10 +#define FRAME_runb3 11 +#define FRAME_runb4 12 +#define FRAME_runb5 13 +#define FRAME_runb6 14 +#define FRAME_runb7 15 +#define FRAME_runb8 16 +#define FRAME_runattack1 17 +#define FRAME_runattack2 18 +#define FRAME_runattack3 19 +#define FRAME_runattack4 20 +#define FRAME_runattack5 21 +#define FRAME_runattack6 22 +#define FRAME_runattack7 23 +#define FRAME_runattack8 24 +#define FRAME_runattack9 25 +#define FRAME_runattack10 26 +#define FRAME_runattack11 27 +#define FRAME_pain1 28 +#define FRAME_pain2 29 +#define FRAME_pain3 30 +#define FRAME_painb1 31 +#define FRAME_painb2 32 +#define FRAME_painb3 33 +#define FRAME_painb4 34 +#define FRAME_painb5 35 +#define FRAME_painb6 36 +#define FRAME_painb7 37 +#define FRAME_painb8 38 +#define FRAME_painb9 39 +#define FRAME_painb10 40 +#define FRAME_painb11 41 +#define FRAME_attackb1 42 +#define FRAME_attackb2 43 +#define FRAME_attackb3 44 +#define FRAME_attackb4 45 +#define FRAME_attackb5 46 +#define FRAME_attackb6 47 +#define FRAME_attackb7 48 +#define FRAME_attackb8 49 +#define FRAME_attackb9 50 +#define FRAME_attackb10 51 +#define FRAME_attackb11 52 +#define FRAME_walk1 53 +#define FRAME_walk2 54 +#define FRAME_walk3 55 +#define FRAME_walk4 56 +#define FRAME_walk5 57 +#define FRAME_walk6 58 +#define FRAME_walk7 59 +#define FRAME_walk8 60 +#define FRAME_walk9 61 +#define FRAME_walk10 62 +#define FRAME_walk11 63 +#define FRAME_walk12 64 +#define FRAME_walk13 65 +#define FRAME_walk14 66 +#define FRAME_kneel1 67 +#define FRAME_kneel2 68 +#define FRAME_kneel3 69 +#define FRAME_kneel4 70 +#define FRAME_kneel5 71 +#define FRAME_standing2 72 +#define FRAME_standing3 73 +#define FRAME_standing4 74 +#define FRAME_standing5 75 +#define FRAME_death1 76 +#define FRAME_death2 77 +#define FRAME_death3 78 +#define FRAME_death4 79 +#define FRAME_death5 80 +#define FRAME_death6 81 +#define FRAME_death7 82 +#define FRAME_death8 83 +#define FRAME_death9 84 +#define FRAME_death10 85 +#define FRAME_deathb1 86 +#define FRAME_deathb2 87 +#define FRAME_deathb3 88 +#define FRAME_deathb4 89 +#define FRAME_deathb5 90 +#define FRAME_deathb6 91 +#define FRAME_deathb7 92 +#define FRAME_deathb8 93 +#define FRAME_deathb9 94 +#define FRAME_deathb10 95 +#define FRAME_deathb11 96 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/medic/medic.c b/src/game/monster/medic/medic.c new file mode 100644 index 000000000..890b0ca3d --- /dev/null +++ b/src/game/monster/medic/medic.c @@ -0,0 +1,2144 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Medic and Medic commander. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "medic.h" + +#define MEDIC_MIN_DISTANCE 32 +#define MEDIC_MAX_HEAL_DISTANCE 400 +#define MEDIC_TRY_TIME 10.0 + +qboolean visible(edict_t *self, edict_t *other); +void M_SetEffects(edict_t *ent); +qboolean FindTarget(edict_t *self); +void HuntTarget(edict_t *self); +void FoundTarget(edict_t *self); +char *ED_NewString(const char *string); +void spawngrow_think(edict_t *self); +void SpawnGrow_Spawn(vec3_t startpos, int size); +void ED_CallSpawn(edict_t *ent); +void M_FliesOff(edict_t *self); +void M_FliesOn(edict_t *self); + +static int sound_idle1; +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_sight; +static int sound_search; +static int sound_hook_launch; +static int sound_hook_hit; +static int sound_hook_heal; +static int sound_hook_retract; + +static int sound_step; +static int sound_step2; + +/* commander sounds */ +static int commander_sound_idle1; +static int commander_sound_pain1; +static int commander_sound_pain2; +static int commander_sound_die; +static int commander_sound_sight; +static int commander_sound_search; +static int commander_sound_hook_launch; +static int commander_sound_hook_hit; +static int commander_sound_hook_heal; +static int commander_sound_hook_retract; +static int commander_sound_spawn; + +char *reinforcements[] = { + "monster_soldier_light", /* 0 */ + "monster_soldier", /* 1 */ + "monster_soldier_ss", /* 2 */ + "monster_infantry", /* 3 */ + "monster_gunner", /* 4 */ + "monster_medic", /* 5 */ + "monster_gladiator" /* 6 */ +}; + +vec3_t reinforcement_mins[] = { + {-16, -16, -24}, + {-16, -16, -24}, + {-16, -16, -24}, + {-16, -16, -24}, + {-16, -16, -24}, + {-16, -16, -24}, + {-32, -32, -24} +}; + +vec3_t reinforcement_maxs[] = { + {16, 16, 32}, + {16, 16, 32}, + {16, 16, 32}, + {16, 16, 32}, + {16, 16, 32}, + {16, 16, 32}, + {32, 32, 64} +}; + +vec3_t reinforcement_position[] = { + {80, 0, 0}, + {40, 60, 0}, + {40, -60, 0}, + {0, 80, 0}, + {0, -80, 0} +}; + +void +cleanupHeal(edict_t *self, qboolean change_frame) +{ + if (!self) + { + return; + } + + /* clean up target, if we have one and it's legit */ + if (self->enemy && self->enemy->inuse) + { + self->enemy->monsterinfo.healer = NULL; + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; + self->enemy->takedamage = DAMAGE_YES; + M_SetEffects(self->enemy); + } + + if (change_frame) + { + self->monsterinfo.nextframe = FRAME_attack52; + } +} + +void +abortHeal(edict_t *self, qboolean change_frame, qboolean gib, qboolean mark) +{ + int hurt; + static vec3_t pain_normal = {0, 0, 1}; + + if (!self) + { + return; + } + + /* clean up target */ + cleanupHeal(self, change_frame); + + /* gib em! */ + if ((mark) && (self->enemy) && (self->enemy->inuse)) + { + if ((self->enemy->monsterinfo.badMedic1) && + (self->enemy->monsterinfo.badMedic1->inuse) && + (!strncmp(self->enemy->monsterinfo.badMedic1->classname, "monster_medic", 13))) + { + self->enemy->monsterinfo.badMedic2 = self; + } + else + { + self->enemy->monsterinfo.badMedic1 = self; + } + } + + if ((gib) && (self->enemy) && (self->enemy->inuse)) + { + if (self->enemy->gib_health) + { + hurt = -self->enemy->gib_health; + } + else + { + hurt = 500; + } + + T_Damage(self->enemy, self, self, vec3_origin, self->enemy->s.origin, + pain_normal, hurt, 0, 0, MOD_UNKNOWN); + } + + /* clean up self */ + self->monsterinfo.aiflags &= ~AI_MEDIC; + + if ((self->oldenemy) && (self->oldenemy->inuse)) + { + self->enemy = self->oldenemy; + } + else + { + self->enemy = NULL; + } + + self->monsterinfo.medicTries = 0; +} + +qboolean +canReach(edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + if (!self || !other) + { + return false; + } + + VectorCopy(self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy(other->s.origin, spot2); + spot2[2] += other->viewheight; + trace = gi.trace(spot1, vec3_origin, vec3_origin, spot2, + self, MASK_SHOT | MASK_WATER); + + if ((trace.fraction == 1.0) || (trace.ent == other)) + { + return true; + } + + return false; +} + +void +medic_footstep(edict_t *self) +{ + if (!g_monsterfootsteps->value) + return; + + // Lazy loading for savegame compatibility. + if (sound_step == 0 || sound_step2 == 0) + { + sound_step = gi.soundindex("medic/step1.wav"); + sound_step2 = gi.soundindex("medic/step2.wav"); + } + + if (randk() % 2 == 0) + { + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + } +} + + +static edict_t * +medic_FindDeadMonster(edict_t *self) +{ + float radius; + edict_t *ent = NULL; + edict_t *best = NULL; + + if (!self) + { + return NULL; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + radius = MEDIC_MAX_HEAL_DISTANCE; + } + else + { + radius = 1024; + } + + while ((ent = findradius(ent, self->s.origin, radius)) != NULL) + { + if (ent == self) + { + continue; + } + + if (!(ent->svflags & SVF_MONSTER)) + { + continue; + } + + if (ent->monsterinfo.aiflags & AI_GOOD_GUY) + { + continue; + } + + if (ent->owner) + { + continue; + } + + /* check to make sure we haven't bailed on this guy already */ + if ((ent->monsterinfo.badMedic1 == self) || + (ent->monsterinfo.badMedic2 == self)) + { + continue; + } + + if (ent->monsterinfo.healer) + { + if ((ent->monsterinfo.healer->inuse) && + (ent->monsterinfo.healer->health > 0) && + (ent->monsterinfo.healer->svflags & SVF_MONSTER) && + (ent->monsterinfo.healer->monsterinfo.aiflags & AI_MEDIC)) + { + continue; + } + } + + if (ent->health > 0) + { + continue; + } + + if ((ent->nextthink) && + !((ent->think == M_FliesOn) || (ent->think == M_FliesOff))) + { + continue; + } + + if (!visible(self, ent)) + { + continue; + } + + if (!strncmp(ent->classname, "player", 6)) /* stop it from trying to heal player_noise entities */ + { + continue; + } + + /* make sure we don't spawn people right on top of us */ + if (realrange(self, ent) <= MEDIC_MIN_DISTANCE) + { + continue; + } + + if (!best) + { + best = ent; + continue; + } + + if (ent->max_health <= best->max_health) + { + continue; + } + + best = ent; + } + + if (best) + { + self->timestamp = level.time + MEDIC_TRY_TIME; + } + + return best; +} + +void +medic_idle(edict_t *self) +{ + edict_t *ent; + + if (!self) + { + return; + } + + /* commander sounds */ + if (self->mass == 400) + { + gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); + } + else + { + gi.sound(self, CHAN_VOICE, commander_sound_idle1, 1, ATTN_IDLE, 0); + } + + if (!self->oldenemy) + { + ent = medic_FindDeadMonster(self); + + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->owner = self; + self->enemy->monsterinfo.healer = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget(self); + } + } +} + +void +medic_search(edict_t *self) +{ + edict_t *ent; + + if (!self) + { + return; + } + + /* commander sounds */ + if (self->mass == 400) + { + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0); + } + else + { + gi.sound(self, CHAN_VOICE, commander_sound_search, 1, ATTN_IDLE, 0); + } + + if (!self->oldenemy) + { + ent = medic_FindDeadMonster(self); + + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->owner = self; + self->enemy->monsterinfo.healer = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget(self); + } + } +} + +void +medic_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + /* commander sounds */ + if (self->mass == 400) + { + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, commander_sound_sight, 1, ATTN_NORM, 0); + } +} + +static mframe_t medic_frames_stand[] = { + {ai_stand, 0, medic_idle}, + {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}, + {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}, + {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 medic_move_stand = +{ + FRAME_wait1, + FRAME_wait90, + medic_frames_stand, + NULL +}; + +void +medic_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &medic_move_stand; +} + +static mframe_t medic_frames_walk[] = { + {ai_walk, 6.2, NULL}, + {ai_walk, 18.1, medic_footstep}, + {ai_walk, 1, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 11, NULL}, + {ai_walk, 11.6, medic_footstep}, + {ai_walk, 2, NULL}, + {ai_walk, 9.9, NULL}, + {ai_walk, 14, NULL}, + {ai_walk, 9.3, NULL} +}; + +mmove_t medic_move_walk = +{ + FRAME_walk1, + FRAME_walk12, + medic_frames_walk, + NULL +}; + +void +medic_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &medic_move_walk; +} + +static mframe_t medic_frames_run[] = { + {ai_run, 18, medic_footstep}, + {ai_run, 22.5, NULL}, + {ai_run, 25.4, monster_done_dodge}, + {ai_run, 23.4, NULL}, + {ai_run, 24, medic_footstep}, + {ai_run, 35.6, NULL} +}; + +mmove_t medic_move_run = +{ + FRAME_run1, + FRAME_run6, + medic_frames_run, + NULL +}; + +void +medic_run(edict_t *self) +{ + if (!self) + { + return; + } + + monster_done_dodge(self); + + if (!(self->monsterinfo.aiflags & AI_MEDIC)) + { + edict_t *ent; + + ent = medic_FindDeadMonster(self); + + if (ent) + { + self->oldenemy = self->enemy; + self->enemy = ent; + self->enemy->owner = self; + self->enemy->monsterinfo.healer = self; + self->monsterinfo.aiflags |= AI_MEDIC; + FoundTarget(self); + return; + } + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &medic_move_stand; + } + else + { + self->monsterinfo.currentmove = &medic_move_run; + } +} + +static mframe_t medic_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t medic_move_pain1 = +{ + FRAME_paina1, + FRAME_paina8, + medic_frames_pain1, + medic_run +}; + +static mframe_t medic_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, medic_footstep}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, medic_footstep} +}; + +mmove_t medic_move_pain2 = +{ + FRAME_painb1, + FRAME_painb15, + medic_frames_pain2, + medic_run +}; + +void +medic_pain(edict_t *self, edict_t *other /* unused */, + float kick, int damage /* unused */) +{ + if (!self) + { + return; + } + + monster_done_dodge(self); + + if ((self->health < (self->max_health / 2))) + { + if (self->mass > 400) + { + self->s.skinnum = 3; + } + else + { + self->s.skinnum = 1; + } + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 3; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &medic_move_pain1; + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + self->monsterinfo.currentmove = &medic_move_pain2; + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + + /* clear duck flag */ + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } +} + +void +medic_fire_blaster(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t end; + vec3_t dir; + int effect; + int damage = 2; + + if (!self) + { + return; + } + + /* paranoia checking */ + if (!(self->enemy && self->enemy->inuse)) + { + return; + } + + if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12)) + { + effect = EF_BLASTER; + } + else if ((self->s.frame == FRAME_attack19) || + (self->s.frame == FRAME_attack22) || + (self->s.frame == FRAME_attack25) || + (self->s.frame == FRAME_attack28)) + { + effect = EF_HYPERBLASTER; + } + else + { + effect = 0; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1], + forward, right, start); + + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, dir); + + if (!strcmp(self->enemy->classname, "tesla")) + { + damage = 3; + } + + /* medic commander shoots blaster2 */ + if (self->mass > 400) + { + monster_fire_blaster2(self, start, dir, damage, + 1000, MZ2_MEDIC_BLASTER_2, effect); + } + else + { + monster_fire_blaster(self, start, dir, damage, + 1000, MZ2_MEDIC_BLASTER_1, effect); + } +} + +void +medic_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +static mframe_t medic_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t medic_move_death = +{ + FRAME_death1, + FRAME_death30, + medic_frames_death, + medic_dead +}; + +void +medic_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + /* if we had a pending patient, free him up for another medic */ + if ((self->enemy) && (self->enemy->owner == self)) + { + self->enemy->owner = NULL; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 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 */ + if (self->mass == 400) + { + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, commander_sound_die, 1, ATTN_NORM, 0); + } + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &medic_move_death; +} + +void +medic_duck_down(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + return; + } + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity(self); +} + +void +medic_duck_hold(edict_t *self) +{ + if (!self) + { + return; + } + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +void +medic_duck_up(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + +static mframe_t medic_frames_duck[] = { + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, medic_duck_down}, + {ai_move, -1, medic_duck_hold}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, monster_duck_up}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL} +}; + +mmove_t medic_move_duck = +{ + FRAME_duck1, + FRAME_duck16, + medic_frames_duck, + medic_run +}; + +void +medic_dodge(edict_t *self, edict_t *attacker, float eta, + trace_t *tr /* unused */) +{ + if (!self || !attacker) + { + return; + } + + if (random() > 0.25) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + } + + self->monsterinfo.currentmove = &medic_move_duck; +} + +static mframe_t medic_frames_attackHyperBlaster[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, medic_fire_blaster} +}; + +mmove_t medic_move_attackHyperBlaster = +{ + FRAME_attack15, + FRAME_attack30, + medic_frames_attackHyperBlaster, + medic_run +}; + +void +medic_continue(edict_t *self) +{ + if (!self) + { + return; + } + + if (visible(self, self->enemy)) + { + if (random() <= 0.95) + { + self->monsterinfo.currentmove = &medic_move_attackHyperBlaster; + } + } +} + +static mframe_t medic_frames_attackBlaster[] = { + {ai_charge, 0, NULL}, + {ai_charge, 5, NULL}, + {ai_charge, 5, NULL}, + {ai_charge, 3, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, medic_fire_blaster}, + {ai_charge, 0, NULL}, + {ai_charge, 0, medic_continue} +}; + +mmove_t medic_move_attackBlaster = +{ + FRAME_attack1, + FRAME_attack14, + medic_frames_attackBlaster, + medic_run +}; + +void +medic_hook_launch(edict_t *self) +{ + if (!self) + { + return; + } + + /* commander sounds */ + if (self->mass == 400) + { + gi.sound(self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, commander_sound_hook_launch, 1, ATTN_NORM, 0); + } +} + +static vec3_t medic_cable_offsets[] = { + {45.0, -9.2, 15.5}, + {48.4, -9.7, 15.2}, + {47.8, -9.8, 15.8}, + {47.3, -9.3, 14.3}, + {45.4, -10.1, 13.1}, + {41.9, -12.7, 12.0}, + {37.8, -15.8, 11.2}, + {34.3, -18.4, 10.7}, + {32.7, -19.7, 10.4}, + {32.7, -19.7, 10.4} +}; + +void +medic_cable_attack(edict_t *self) +{ + vec3_t offset, start, end, f, r; + trace_t tr; + vec3_t dir, angles; + float distance; + + if (!self) + { + return; + } + + if ((!self->enemy) || (!self->enemy->inuse) || + (self->enemy->s.effects & EF_GIB)) + { + abortHeal(self, true, false, false); + return; + } + + /* see if our enemy has changed to a client, or our + target has more than 0 health, abort it .. we got + switched to someone else due to damage */ + if ((self->enemy->client) || (self->enemy->health > 0)) + { + abortHeal(self, true, false, false); + return; + } + + AngleVectors(self->s.angles, f, r, NULL); + VectorCopy(medic_cable_offsets[self->s.frame - FRAME_attack42], offset); + G_ProjectSource(self->s.origin, offset, f, r, start); + + /* check for max distance */ + VectorSubtract(start, self->enemy->s.origin, dir); + distance = VectorLength(dir); + + if (distance < MEDIC_MIN_DISTANCE) + { + abortHeal(self, true, true, false); + return; + } + + /* check for min/max pitch */ + vectoangles(dir, angles); + + if (angles[0] < -180) + { + angles[0] += 360; + } + + if (fabs(angles[0]) > 45) + { + return; + } + + tr = gi.trace(start, NULL, NULL, self->enemy->s.origin, self, MASK_SOLID); + + if ((tr.fraction != 1.0) && (tr.ent != self->enemy)) + { + if (tr.ent == world) + { + /* give up on second try */ + if (self->monsterinfo.medicTries > 1) + { + abortHeal(self, true, false, true); + return; + } + + self->monsterinfo.medicTries++; + cleanupHeal(self, 1); + return; + } + + abortHeal(self, true, false, false); + return; + } + + if (self->s.frame == FRAME_attack43) + { + /* commander sounds */ + if (self->mass == 400) + { + gi.sound(self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self->enemy, CHAN_AUTO, commander_sound_hook_hit, + 1, ATTN_NORM, 0); + } + + self->enemy->monsterinfo.aiflags |= AI_RESURRECTING; + self->enemy->takedamage = DAMAGE_NO; + M_SetEffects(self->enemy); + } + else if (self->s.frame == FRAME_attack50) + { + vec3_t maxs; + self->enemy->spawnflags = 0; + self->enemy->monsterinfo.aiflags = 0; + self->enemy->target = NULL; + self->enemy->targetname = NULL; + self->enemy->combattarget = NULL; + self->enemy->deathtarget = NULL; + self->enemy->owner = self; + self->enemy->monsterinfo.healer = self; + + VectorCopy(self->enemy->maxs, maxs); + maxs[2] += 48; + + tr = gi.trace(self->enemy->s.origin, self->enemy->mins, + maxs, self->enemy->s.origin, self->enemy, + MASK_MONSTERSOLID); + + if (tr.startsolid || tr.allsolid) + { + abortHeal(self, true, true, false); + return; + } + else if (tr.ent != world) + { + abortHeal(self, true, true, false); + return; + } + else + { + self->enemy->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + ED_CallSpawn(self->enemy); + + if (self->enemy->think) + { + self->enemy->nextthink = level.time; + self->enemy->think(self->enemy); + } + + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; + self->enemy->monsterinfo.aiflags |= AI_IGNORE_SHOTS | + AI_DO_NOT_COUNT; + /* turn off flies */ + self->enemy->s.effects &= ~EF_FLIES; + self->enemy->monsterinfo.healer = NULL; + + if ((self->oldenemy) && (self->oldenemy->inuse) && + (self->oldenemy->health > 0)) + { + self->enemy->enemy = self->oldenemy; + FoundTarget(self->enemy); + } + else + { + self->enemy->enemy = NULL; + + if (!FindTarget(self->enemy)) + { + /* no valid enemy, so stop acting */ + self->enemy->monsterinfo.pausetime = level.time + 100000000; + self->enemy->monsterinfo.stand(self->enemy); + } + + self->enemy = NULL; + self->oldenemy = NULL; + + if (!FindTarget(self)) + { + /* no valid enemy, so stop acting */ + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand(self); + return; + } + } + } + } + else + { + if (self->s.frame == FRAME_attack44) + { + /* medic commander sounds */ + if (self->mass == 400) + { + gi.sound(self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, commander_sound_hook_heal, + 1, ATTN_NORM, 0); + } + } + } + + /* adjust start for beam origin being in middle of a segment */ + VectorMA(start, 8, f, start); + + /* adjust end z for end spot since the monster is currently dead */ + VectorCopy(self->enemy->s.origin, end); + end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_MEDIC_CABLE_ATTACK); + gi.WriteShort(self - g_edicts); + gi.WritePosition(start); + gi.WritePosition(end); + gi.multicast(self->s.origin, MULTICAST_PVS); +} + +void +medic_hook_retract(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->mass == 400) + { + gi.sound(self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, commander_sound_hook_retract, 1, ATTN_NORM, 0); + } + + self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING; + self->monsterinfo.aiflags &= ~AI_MEDIC; + + if ((self->oldenemy) && (self->oldenemy->inuse)) + { + self->enemy = self->oldenemy; + } + else + { + self->enemy = NULL; + self->oldenemy = NULL; + + if (!FindTarget(self)) + { + /* no valid enemy, so stop acting */ + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand(self); + return; + } + } +} + +static mframe_t medic_frames_attackCable[] = { + {ai_move, 2, NULL}, + {ai_move, 3, NULL}, + {ai_move, 5, NULL}, + {ai_move, 4.4, NULL}, + {ai_charge, 4.7, NULL}, + {ai_charge, 5, NULL}, + {ai_charge, 6, NULL}, + {ai_charge, 4, medic_footstep}, + {ai_charge, 0, NULL}, + {ai_move, 0, medic_hook_launch}, + {ai_move, 0, medic_cable_attack}, + {ai_move, 0, medic_cable_attack}, + {ai_move, 0, medic_cable_attack}, + {ai_move, 0, medic_cable_attack}, + {ai_move, 0, medic_cable_attack}, + {ai_move, 0, medic_cable_attack}, + {ai_move, 0, medic_cable_attack}, + {ai_move, 0, medic_cable_attack}, + {ai_move, 0, medic_cable_attack}, + {ai_move, -15, medic_hook_retract}, + {ai_move, -1.5, NULL}, + {ai_move, -1.2, medic_footstep}, + {ai_move, -3, NULL}, + {ai_move, -2, NULL}, + {ai_move, 0.3, NULL}, + {ai_move, 0.7, NULL}, + {ai_move, 1.2, NULL}, + {ai_move, 1.3, NULL} /* 60 */ +}; + +mmove_t medic_move_attackCable = +{ + FRAME_attack33, + FRAME_attack60, + medic_frames_attackCable, + medic_run +}; + +void +medic_start_spawn(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0); + self->monsterinfo.nextframe = FRAME_attack48; +} + +void +medic_determine_spawn(edict_t *self) +{ + vec3_t f, r, offset, startpoint, spawnpoint; + float lucky; + int summonStr; + int count; + int inc; + int num_summoned; /* should be 1, 3, or 5 */ + int num_success = 0; + + lucky = random(); + summonStr = skill->value; + + if (!self) + { + return; + } + + if (lucky < 0.05) + { + summonStr -= 3; + } + else if (lucky < 0.15) + { + summonStr -= 2; + } + else if (lucky < 0.3) + { + summonStr -= 1; + } + else if (lucky > 0.95) + { + summonStr += 3; + } + else if (lucky > 0.85) + { + summonStr += 2; + } + else if (lucky > 0.7) + { + summonStr += 1; + } + + if (summonStr < 0) + { + summonStr = 0; + } + + self->plat2flags = summonStr; + AngleVectors(self->s.angles, f, r, NULL); + + /* this yields either 1, 3, or 5 */ + if (summonStr) + { + num_summoned = (summonStr - 1) + (summonStr % 2); + } + else + { + num_summoned = 1; + } + + for (count = 0; count < num_summoned; count++) + { + inc = count + (count % 2); /* 0, 2, 2, 4, 4 */ + VectorCopy(reinforcement_position[count], offset); + + G_ProjectSource(self->s.origin, offset, f, r, startpoint); + startpoint[2] += 10; + + if (FindSpawnPoint(startpoint, reinforcement_mins[summonStr - inc], + reinforcement_maxs[summonStr - inc], spawnpoint, 32)) + { + if (CheckGroundSpawnPoint(spawnpoint, reinforcement_mins[summonStr - inc], + reinforcement_maxs[summonStr - inc], 256, -1)) + { + num_success++; + /* we found a spot, we're done here */ + count = num_summoned; + } + } + } + + if (num_success == 0) + { + for (count = 0; count < num_summoned; count++) + { + inc = count + (count % 2); /* 0, 2, 2, 4, 4 */ + VectorCopy(reinforcement_position[count], offset); + + /* check behind */ + offset[0] *= -1.0; + offset[1] *= -1.0; + G_ProjectSource(self->s.origin, offset, f, r, startpoint); + /* a little off the ground */ + startpoint[2] += 10; + + if (FindSpawnPoint(startpoint, reinforcement_mins[summonStr - inc], + reinforcement_maxs[summonStr - inc], spawnpoint, 32)) + { + if (CheckGroundSpawnPoint(spawnpoint, reinforcement_mins[summonStr - inc], + reinforcement_maxs[summonStr - inc], 256, -1)) + { + num_success++; + /* we found a spot, we're done here */ + count = num_summoned; + } + } + } + + if (num_success) + { + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->ideal_yaw = anglemod(self->s.angles[YAW]) + 180; + + if (self->ideal_yaw > 360.0) + { + self->ideal_yaw -= 360.0; + } + } + } + + if (num_success == 0) + { + self->monsterinfo.nextframe = FRAME_attack53; + } +} + +void +medic_spawngrows(edict_t *self) +{ + vec3_t f, r, offset, startpoint, spawnpoint; + int summonStr; + int count; + int inc; + int num_summoned; /* should be 1, 3, or 5 */ + int num_success = 0; + float current_yaw; + + if (!self) + { + return; + } + + /* if we've been directed to turn around */ + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + current_yaw = anglemod(self->s.angles[YAW]); + + if (fabs(current_yaw - self->ideal_yaw) > 0.1) + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + return; + } + + /* done turning around */ + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + } + + summonStr = self->plat2flags; + AngleVectors(self->s.angles, f, r, NULL); + + if (summonStr) + { + num_summoned = (summonStr - 1) + (summonStr % 2); + } + else + { + num_summoned = 1; + } + + for (count = 0; count < num_summoned; count++) + { + inc = count + (count % 2); /* 0, 2, 2, 4, 4 */ + VectorCopy(reinforcement_position[count], offset); + + G_ProjectSource(self->s.origin, offset, f, r, startpoint); + /* a little off the ground */ + startpoint[2] += 10; + + if (FindSpawnPoint(startpoint, reinforcement_mins[summonStr - inc], + reinforcement_maxs[summonStr - inc], spawnpoint, 32)) + { + if (CheckGroundSpawnPoint(spawnpoint, reinforcement_mins[summonStr - inc], + reinforcement_maxs[summonStr - inc], 256, -1)) + { + num_success++; + + if ((summonStr - inc) > 3) + { + SpawnGrow_Spawn(spawnpoint, 1); /* big monster */ + } + else + { + SpawnGrow_Spawn(spawnpoint, 0); /* normal size */ + } + } + } + } + + if (num_success == 0) + { + self->monsterinfo.nextframe = FRAME_attack53; + } +} + +void +medic_finish_spawn(edict_t *self) +{ + edict_t *ent; + vec3_t f, r, offset, startpoint, spawnpoint; + int summonStr; + int count; + int inc; + int num_summoned; /* should be 1, 3, or 5 */ + edict_t *designated_enemy; + + if (!self) + { + return; + } + + if (self->plat2flags < 0) + { + self->plat2flags *= -1; + } + + summonStr = self->plat2flags; + + AngleVectors(self->s.angles, f, r, NULL); + + if (summonStr) + { + num_summoned = (summonStr - 1) + (summonStr % 2); + } + else + { + num_summoned = 1; + } + + for (count = 0; count < num_summoned; count++) + { + inc = count + (count % 2); /* 0, 2, 2, 4, 4 */ + VectorCopy(reinforcement_position[count], offset); + + G_ProjectSource(self->s.origin, offset, f, r, startpoint); + + /* a little off the ground */ + startpoint[2] += 10; + + ent = NULL; + + if (FindSpawnPoint(startpoint, reinforcement_mins[summonStr - inc], + reinforcement_maxs[summonStr - inc], spawnpoint, 32)) + { + if (CheckSpawnPoint(spawnpoint, reinforcement_mins[summonStr - inc], + reinforcement_maxs[summonStr - inc])) + { + ent = CreateGroundMonster(spawnpoint, self->s.angles, + reinforcement_mins[summonStr - inc], + reinforcement_maxs[summonStr - inc], + reinforcements[summonStr - inc], 256); + } + } + + if (!ent) + { + continue; + } + + + if (ent->think) + { + ent->nextthink = level.time; + ent->think(ent); + } + + ent->monsterinfo.aiflags |= AI_IGNORE_SHOTS | AI_DO_NOT_COUNT | + AI_SPAWNED_MEDIC_C; + ent->monsterinfo.commander = self; + self->monsterinfo.monster_slots--; + + if (self->monsterinfo.aiflags & AI_MEDIC) + { + designated_enemy = self->oldenemy; + } + else + { + designated_enemy = self->enemy; + } + + if (coop && coop->value) + { + designated_enemy = PickCoopTarget(ent); + + if (designated_enemy) + { + /* try to avoid using my enemy */ + if (designated_enemy == self->enemy) + { + designated_enemy = PickCoopTarget(ent); + + if (!designated_enemy) + { + designated_enemy = self->enemy; + } + } + } + else + { + designated_enemy = self->enemy; + } + } + + if ((designated_enemy) && (designated_enemy->inuse) && + (designated_enemy->health > 0)) + { + ent->enemy = designated_enemy; + FoundTarget(ent); + } + else + { + ent->enemy = NULL; + ent->monsterinfo.stand(ent); + } + } +} + +static mframe_t medic_frames_callReinforcements[] = { + {ai_charge, 2, NULL}, /* 33 */ + {ai_charge, 3, NULL}, + {ai_charge, 5, NULL}, + {ai_charge, 4.4, NULL}, /* 36 */ + {ai_charge, 4.7, NULL}, + {ai_charge, 5, NULL}, + {ai_charge, 6, NULL}, + {ai_charge, 4, NULL}, /* 40 */ + {ai_charge, 0, NULL}, + {ai_move, 0, medic_start_spawn}, /* 42 */ + {ai_move, 0, NULL}, /* 43 -- 43 through 47 are skipped */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, medic_determine_spawn}, /* 48 */ + {ai_charge, 0, medic_spawngrows}, /* 49 */ + {ai_move, 0, NULL}, /* 50 */ + {ai_move, 0, NULL}, /* 51 */ + {ai_move, -15, medic_finish_spawn}, /* 52 */ + {ai_move, -1.5, NULL}, + {ai_move, -1.2, NULL}, + {ai_move, -3, NULL}, + {ai_move, -2, NULL}, + {ai_move, 0.3, NULL}, + {ai_move, 0.7, NULL}, + {ai_move, 1.2, NULL}, + {ai_move, 1.3, NULL} /* 60 */ +}; + +mmove_t medic_move_callReinforcements = { + FRAME_attack33, + FRAME_attack60, + medic_frames_callReinforcements, + medic_run +}; + +void +medic_attack(edict_t *self) +{ + int enemy_range; + float r; + + if (!self) + { + return; + } + + monster_done_dodge(self); + + enemy_range = range(self, self->enemy); + + /* signal from checkattack to spawn */ + if (self->monsterinfo.aiflags & AI_BLOCKED) + { + self->monsterinfo.currentmove = &medic_move_callReinforcements; + self->monsterinfo.aiflags &= ~AI_BLOCKED; + } + + r = random(); + + if (self->monsterinfo.aiflags & AI_MEDIC) + { + if ((self->mass > 400) && (r > 0.8) && + (self->monsterinfo.monster_slots > 2)) + { + self->monsterinfo.currentmove = &medic_move_callReinforcements; + } + else + { + self->monsterinfo.currentmove = &medic_move_attackCable; + } + } + else + { + if (self->monsterinfo.attack_state == AS_BLIND) + { + self->monsterinfo.currentmove = &medic_move_callReinforcements; + return; + } + + if ((self->mass > 400) && (r > 0.2) && (enemy_range != RANGE_MELEE) && + (self->monsterinfo.monster_slots > 2)) + { + self->monsterinfo.currentmove = &medic_move_callReinforcements; + } + else + { + self->monsterinfo.currentmove = &medic_move_attackBlaster; + } + } +} + +qboolean +medic_checkattack(edict_t *self) +{ + if (!self) + { + return false; + } + + if (self->monsterinfo.aiflags & AI_MEDIC) + { + /* if our target went away */ + if ((!self->enemy) || (!self->enemy->inuse)) + { + abortHeal(self, true, false, false); + return false; + } + + /* if we ran out of time, give up */ + if (self->timestamp < level.time) + { + abortHeal(self, true, false, true); + self->timestamp = 0; + return false; + } + + if (realrange(self, self->enemy) < MEDIC_MAX_HEAL_DISTANCE + 10) + { + medic_attack(self); + return true; + } + else + { + self->monsterinfo.attack_state = AS_STRAIGHT; + return false; + } + } + + if (self->enemy && self->enemy->client && + !visible(self, self->enemy) && (self->monsterinfo.monster_slots > 2)) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + if ((random() < 0.8) && (self->monsterinfo.monster_slots > 5) && + (realrange(self, self->enemy) > 150)) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (skill->value > SKILL_EASY) + { + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + } + + return M_CheckAttack(self); +} + +static void +MedicCommanderCache(void) +{ + edict_t *newEnt; + int i; + + /* better way to do this? this is quick and dirty */ + for (i = 0; i < 7; i++) + { + newEnt = G_Spawn(); + + VectorCopy(vec3_origin, newEnt->s.origin); + VectorCopy(vec3_origin, newEnt->s.angles); + newEnt->classname = ED_NewString(reinforcements[i]); + + newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + ED_CallSpawn(newEnt); + G_FreeEdict(newEnt); + } + + gi.modelindex("models/items/spawngro/tris.md2"); + gi.modelindex("models/items/spawngro2/tris.md2"); +} + +void +medic_duck(edict_t *self, float eta) +{ + if (!self) + { + return; + } + + /* don't dodge if you're healing */ + if (self->monsterinfo.aiflags & AI_MEDIC) + { + return; + } + + if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) || + (self->monsterinfo.currentmove == &medic_move_attackCable) || + (self->monsterinfo.currentmove == &medic_move_attackBlaster) || + (self->monsterinfo.currentmove == &medic_move_callReinforcements)) + { + /* he ignores skill */ + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + + if (skill->value == SKILL_EASY) + { + /* stupid dodge */ + self->monsterinfo.duck_wait_time = level.time + eta + 1; + } + else + { + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + } + + /* has to be done immediately otherwise he can get stuck */ + monster_duck_down(self); + + self->monsterinfo.nextframe = FRAME_duck1; + self->monsterinfo.currentmove = &medic_move_duck; + return; +} + +void +medic_sidestep(edict_t *self) +{ + if (!self) + { + return; + } + + if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) || + (self->monsterinfo.currentmove == &medic_move_attackCable) || + (self->monsterinfo.currentmove == &medic_move_attackBlaster) || + (self->monsterinfo.currentmove == &medic_move_callReinforcements)) + { + /* if we're shooting, and not on easy, don't dodge */ + if (skill->value > SKILL_EASY) + { + self->monsterinfo.aiflags &= ~AI_DODGING; + return; + } + } + + if (self->monsterinfo.currentmove != &medic_move_run) + { + self->monsterinfo.currentmove = &medic_move_run; + } +} + +qboolean +medic_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + return false; +} + +/* + * QUAKED monster_medic_commander (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + * + * QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_medic(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/medic/tris.md2"); + VectorSet(self->mins, -24, -24, -24); + VectorSet(self->maxs, 24, 24, 32); + + if (strcmp(self->classname, "monster_medic_commander") == 0) + { + self->health = 600; + self->gib_health = -130; + self->mass = 600; + self->yaw_speed = 40; + MedicCommanderCache(); + } + else + { + self->health = 300; + self->gib_health = -130; + self->mass = 400; + } + + self->pain = medic_pain; + self->die = medic_die; + + self->monsterinfo.stand = medic_stand; + self->monsterinfo.walk = medic_walk; + self->monsterinfo.run = medic_run; + self->monsterinfo.dodge = medic_dodge; + self->monsterinfo.duck = medic_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = medic_sidestep; + self->monsterinfo.attack = medic_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = medic_sight; + self->monsterinfo.idle = medic_idle; + self->monsterinfo.search = medic_search; + self->monsterinfo.checkattack = medic_checkattack; + self->monsterinfo.blocked = medic_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &medic_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); + + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + if (self->mass > 400) + { + self->s.skinnum = 2; + + if (skill->value == SKILL_EASY) + { + self->monsterinfo.monster_slots = 3; + } + else if (skill->value == SKILL_MEDIUM) + { + self->monsterinfo.monster_slots = 4; + } + else if (skill->value == SKILL_HARD) + { + self->monsterinfo.monster_slots = 6; + } + else if (skill->value == SKILL_HARDPLUS) + { + self->monsterinfo.monster_slots = 6; + } + + /* commander sounds */ + commander_sound_idle1 = gi.soundindex("medic_commander/medidle.wav"); + commander_sound_pain1 = gi.soundindex("medic_commander/medpain1.wav"); + commander_sound_pain2 = gi.soundindex("medic_commander/medpain2.wav"); + commander_sound_die = gi.soundindex("medic_commander/meddeth.wav"); + commander_sound_sight = gi.soundindex("medic_commander/medsght.wav"); + commander_sound_search = gi.soundindex("medic_commander/medsrch.wav"); + commander_sound_hook_launch = gi.soundindex("medic_commander/medatck2c.wav"); + commander_sound_hook_hit = gi.soundindex("medic_commander/medatck3a.wav"); + commander_sound_hook_heal = gi.soundindex("medic_commander/medatck4a.wav"); + commander_sound_hook_retract = gi.soundindex("medic_commander/medatck5a.wav"); + commander_sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav"); + gi.soundindex("tank/tnkatck3.wav"); + } + else + { + sound_idle1 = gi.soundindex("medic/idle.wav"); + sound_pain1 = gi.soundindex("medic/medpain1.wav"); + sound_pain2 = gi.soundindex("medic/medpain2.wav"); + sound_die = gi.soundindex("medic/meddeth1.wav"); + sound_sight = gi.soundindex("medic/medsght1.wav"); + sound_search = gi.soundindex("medic/medsrch1.wav"); + sound_hook_launch = gi.soundindex("medic/medatck2.wav"); + sound_hook_hit = gi.soundindex("medic/medatck3.wav"); + sound_hook_heal = gi.soundindex("medic/medatck4.wav"); + sound_hook_retract = gi.soundindex("medic/medatck5.wav"); + gi.soundindex("medic/medatck1.wav"); + + self->s.skinnum = 0; + } +} diff --git a/src/game/monster/medic/medic.h b/src/game/monster/medic/medic.h new file mode 100644 index 000000000..7f7091a0a --- /dev/null +++ b/src/game/monster/medic/medic.h @@ -0,0 +1,266 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Medic and Medic Commander animations. + * + * ======================================================================= + */ + +#define FRAME_walk1 0 +#define FRAME_walk2 1 +#define FRAME_walk3 2 +#define FRAME_walk4 3 +#define FRAME_walk5 4 +#define FRAME_walk6 5 +#define FRAME_walk7 6 +#define FRAME_walk8 7 +#define FRAME_walk9 8 +#define FRAME_walk10 9 +#define FRAME_walk11 10 +#define FRAME_walk12 11 +#define FRAME_wait1 12 +#define FRAME_wait2 13 +#define FRAME_wait3 14 +#define FRAME_wait4 15 +#define FRAME_wait5 16 +#define FRAME_wait6 17 +#define FRAME_wait7 18 +#define FRAME_wait8 19 +#define FRAME_wait9 20 +#define FRAME_wait10 21 +#define FRAME_wait11 22 +#define FRAME_wait12 23 +#define FRAME_wait13 24 +#define FRAME_wait14 25 +#define FRAME_wait15 26 +#define FRAME_wait16 27 +#define FRAME_wait17 28 +#define FRAME_wait18 29 +#define FRAME_wait19 30 +#define FRAME_wait20 31 +#define FRAME_wait21 32 +#define FRAME_wait22 33 +#define FRAME_wait23 34 +#define FRAME_wait24 35 +#define FRAME_wait25 36 +#define FRAME_wait26 37 +#define FRAME_wait27 38 +#define FRAME_wait28 39 +#define FRAME_wait29 40 +#define FRAME_wait30 41 +#define FRAME_wait31 42 +#define FRAME_wait32 43 +#define FRAME_wait33 44 +#define FRAME_wait34 45 +#define FRAME_wait35 46 +#define FRAME_wait36 47 +#define FRAME_wait37 48 +#define FRAME_wait38 49 +#define FRAME_wait39 50 +#define FRAME_wait40 51 +#define FRAME_wait41 52 +#define FRAME_wait42 53 +#define FRAME_wait43 54 +#define FRAME_wait44 55 +#define FRAME_wait45 56 +#define FRAME_wait46 57 +#define FRAME_wait47 58 +#define FRAME_wait48 59 +#define FRAME_wait49 60 +#define FRAME_wait50 61 +#define FRAME_wait51 62 +#define FRAME_wait52 63 +#define FRAME_wait53 64 +#define FRAME_wait54 65 +#define FRAME_wait55 66 +#define FRAME_wait56 67 +#define FRAME_wait57 68 +#define FRAME_wait58 69 +#define FRAME_wait59 70 +#define FRAME_wait60 71 +#define FRAME_wait61 72 +#define FRAME_wait62 73 +#define FRAME_wait63 74 +#define FRAME_wait64 75 +#define FRAME_wait65 76 +#define FRAME_wait66 77 +#define FRAME_wait67 78 +#define FRAME_wait68 79 +#define FRAME_wait69 80 +#define FRAME_wait70 81 +#define FRAME_wait71 82 +#define FRAME_wait72 83 +#define FRAME_wait73 84 +#define FRAME_wait74 85 +#define FRAME_wait75 86 +#define FRAME_wait76 87 +#define FRAME_wait77 88 +#define FRAME_wait78 89 +#define FRAME_wait79 90 +#define FRAME_wait80 91 +#define FRAME_wait81 92 +#define FRAME_wait82 93 +#define FRAME_wait83 94 +#define FRAME_wait84 95 +#define FRAME_wait85 96 +#define FRAME_wait86 97 +#define FRAME_wait87 98 +#define FRAME_wait88 99 +#define FRAME_wait89 100 +#define FRAME_wait90 101 +#define FRAME_run1 102 +#define FRAME_run2 103 +#define FRAME_run3 104 +#define FRAME_run4 105 +#define FRAME_run5 106 +#define FRAME_run6 107 +#define FRAME_paina1 108 +#define FRAME_paina2 109 +#define FRAME_paina3 110 +#define FRAME_paina4 111 +#define FRAME_paina5 112 +#define FRAME_paina6 113 +#define FRAME_paina7 114 +#define FRAME_paina8 115 +#define FRAME_painb1 116 +#define FRAME_painb2 117 +#define FRAME_painb3 118 +#define FRAME_painb4 119 +#define FRAME_painb5 120 +#define FRAME_painb6 121 +#define FRAME_painb7 122 +#define FRAME_painb8 123 +#define FRAME_painb9 124 +#define FRAME_painb10 125 +#define FRAME_painb11 126 +#define FRAME_painb12 127 +#define FRAME_painb13 128 +#define FRAME_painb14 129 +#define FRAME_painb15 130 +#define FRAME_duck1 131 +#define FRAME_duck2 132 +#define FRAME_duck3 133 +#define FRAME_duck4 134 +#define FRAME_duck5 135 +#define FRAME_duck6 136 +#define FRAME_duck7 137 +#define FRAME_duck8 138 +#define FRAME_duck9 139 +#define FRAME_duck10 140 +#define FRAME_duck11 141 +#define FRAME_duck12 142 +#define FRAME_duck13 143 +#define FRAME_duck14 144 +#define FRAME_duck15 145 +#define FRAME_duck16 146 +#define FRAME_death1 147 +#define FRAME_death2 148 +#define FRAME_death3 149 +#define FRAME_death4 150 +#define FRAME_death5 151 +#define FRAME_death6 152 +#define FRAME_death7 153 +#define FRAME_death8 154 +#define FRAME_death9 155 +#define FRAME_death10 156 +#define FRAME_death11 157 +#define FRAME_death12 158 +#define FRAME_death13 159 +#define FRAME_death14 160 +#define FRAME_death15 161 +#define FRAME_death16 162 +#define FRAME_death17 163 +#define FRAME_death18 164 +#define FRAME_death19 165 +#define FRAME_death20 166 +#define FRAME_death21 167 +#define FRAME_death22 168 +#define FRAME_death23 169 +#define FRAME_death24 170 +#define FRAME_death25 171 +#define FRAME_death26 172 +#define FRAME_death27 173 +#define FRAME_death28 174 +#define FRAME_death29 175 +#define FRAME_death30 176 +#define FRAME_attack1 177 +#define FRAME_attack2 178 +#define FRAME_attack3 179 +#define FRAME_attack4 180 +#define FRAME_attack5 181 +#define FRAME_attack6 182 +#define FRAME_attack7 183 +#define FRAME_attack8 184 +#define FRAME_attack9 185 +#define FRAME_attack10 186 +#define FRAME_attack11 187 +#define FRAME_attack12 188 +#define FRAME_attack13 189 +#define FRAME_attack14 190 +#define FRAME_attack15 191 +#define FRAME_attack16 192 +#define FRAME_attack17 193 +#define FRAME_attack18 194 +#define FRAME_attack19 195 +#define FRAME_attack20 196 +#define FRAME_attack21 197 +#define FRAME_attack22 198 +#define FRAME_attack23 199 +#define FRAME_attack24 200 +#define FRAME_attack25 201 +#define FRAME_attack26 202 +#define FRAME_attack27 203 +#define FRAME_attack28 204 +#define FRAME_attack29 205 +#define FRAME_attack30 206 +#define FRAME_attack31 207 +#define FRAME_attack32 208 +#define FRAME_attack33 209 +#define FRAME_attack34 210 +#define FRAME_attack35 211 +#define FRAME_attack36 212 +#define FRAME_attack37 213 +#define FRAME_attack38 214 +#define FRAME_attack39 215 +#define FRAME_attack40 216 +#define FRAME_attack41 217 +#define FRAME_attack42 218 +#define FRAME_attack43 219 +#define FRAME_attack44 220 +#define FRAME_attack45 221 +#define FRAME_attack46 222 +#define FRAME_attack47 223 +#define FRAME_attack48 224 +#define FRAME_attack49 225 +#define FRAME_attack50 226 +#define FRAME_attack51 227 +#define FRAME_attack52 228 +#define FRAME_attack53 229 +#define FRAME_attack54 230 +#define FRAME_attack55 231 +#define FRAME_attack56 232 +#define FRAME_attack57 233 +#define FRAME_attack58 234 +#define FRAME_attack59 235 +#define FRAME_attack60 236 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/mutant/mutant.c b/src/game/monster/mutant/mutant.c new file mode 100644 index 000000000..e5b8854f7 --- /dev/null +++ b/src/game/monster/mutant/mutant.c @@ -0,0 +1,1018 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Mutant. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "mutant.h" + +static int sound_swing; +static int sound_hit; +static int sound_hit2; +static int sound_death; +static int sound_idle; +static int sound_pain1; +static int sound_pain2; +static int sound_sight; +static int sound_search; +static int sound_step1; +static int sound_step2; +static int sound_step3; +static int sound_thud; + +void mutant_walk(edict_t *self); + +void +mutant_step(edict_t *self) +{ + int n; + + if (!self) + { + return; + } + + n = (randk() + 1) % 3; + + if (n == 0) + { + gi.sound(self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0); + } + else if (n == 1) + { + gi.sound(self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0); + } +} + +void +mutant_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +mutant_search(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void +mutant_swing(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0); +} + +static mframe_t mutant_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}, /* 10 */ + + {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}, /* 20 */ + + {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}, /* 30 */ + + {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}, /* 40 */ + + {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}, /* 50 */ + + {ai_stand, 0, NULL} +}; + +mmove_t mutant_move_stand = +{ + FRAME_stand101, + FRAME_stand151, + mutant_frames_stand, + NULL +}; + +void +mutant_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &mutant_move_stand; +} + +void +mutant_idle_loop(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.75) + { + self->monsterinfo.nextframe = FRAME_stand155; + } +} + +static mframe_t mutant_frames_idle[] = { + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, /* scratch loop start */ + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, mutant_idle_loop}, /* scratch loop end */ + {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 mutant_move_idle = +{ + FRAME_stand152, + FRAME_stand164, + mutant_frames_idle, + mutant_stand +}; + +void +mutant_idle(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &mutant_move_idle; + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +static mframe_t mutant_frames_walk[] = { + {ai_walk, 3, mutant_step}, + {ai_walk, 1, NULL}, + {ai_walk, 5, mutant_step}, + {ai_walk, 10, NULL}, + {ai_walk, 13, NULL}, + {ai_walk, 10, NULL}, + {ai_walk, 0, mutant_step}, + {ai_walk, 5, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 16, NULL}, + {ai_walk, 15, NULL}, + {ai_walk, 6, NULL} +}; + +mmove_t mutant_move_walk = +{ + FRAME_walk05, + FRAME_walk16, + mutant_frames_walk, + NULL +}; + +void +mutant_walk_loop(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &mutant_move_walk; +} + +static mframe_t mutant_frames_start_walk[] = { + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, -2, mutant_step}, + {ai_walk, 1, NULL} +}; + +mmove_t mutant_move_start_walk = +{ + FRAME_walk01, + FRAME_walk04, + mutant_frames_start_walk, + mutant_walk_loop +}; + +void +mutant_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &mutant_move_start_walk; +} + +static mframe_t mutant_frames_run[] = { + {ai_run, 40, NULL}, + {ai_run, 40, mutant_step}, + {ai_run, 24, NULL}, + {ai_run, 5, mutant_step}, + {ai_run, 17, NULL}, + {ai_run, 10, NULL} +}; + +mmove_t mutant_move_run = +{ + FRAME_run03, + FRAME_run08, + mutant_frames_run, + NULL +}; + +void +mutant_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &mutant_move_stand; + } + else + { + self->monsterinfo.currentmove = &mutant_move_run; + } +} + +void +mutant_hit_left(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->mins[0], 8); + + if (fire_hit(self, aim, (10 + (randk() % 5)), 100)) + { + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + } +} + +void +mutant_hit_right(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 8); + + if (fire_hit(self, aim, (10 + (randk() % 5)), 100)) + { + gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0); + } +} + +void +mutant_check_refire(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->enemy || !self->enemy->inuse || (self->enemy->health <= 0)) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attack09; + } +} + +static mframe_t mutant_frames_attack[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, mutant_hit_left}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, mutant_hit_right}, + {ai_charge, 0, mutant_check_refire} +}; + +mmove_t mutant_move_attack = +{ + FRAME_attack09, + FRAME_attack15, + mutant_frames_attack, + mutant_run +}; + +void +mutant_melee(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &mutant_move_attack; +} + +void +mutant_jump_touch(edict_t *self, edict_t *other, + cplane_t *plane /* unused */, csurface_t *surf /* unused */) +{ + if (!self || !other) + { + return; + } + + if (self->health <= 0) + { + self->touch = NULL; + return; + } + + if (other->takedamage) + { + if (VectorLength(self->velocity) > 400) + { + vec3_t point; + vec3_t normal; + int damage; + + VectorCopy(self->velocity, normal); + VectorNormalize(normal); + VectorMA(self->s.origin, self->maxs[0], normal, point); + damage = 40 + 10 * random(); + T_Damage(other, self, self, self->velocity, point, + normal, damage, damage, 0, MOD_UNKNOWN); + } + } + + if (!M_CheckBottom(self)) + { + if (self->groundentity) + { + self->monsterinfo.nextframe = FRAME_attack02; + self->touch = NULL; + } + + return; + } + + self->touch = NULL; +} + +void +mutant_jump_takeoff(edict_t *self) +{ + vec3_t forward; + + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + AngleVectors(self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + VectorScale(forward, 600, self->velocity); + self->velocity[2] = 250; + self->groundentity = NULL; + self->monsterinfo.aiflags |= AI_DUCKED; + self->monsterinfo.attack_finished = level.time + 3; + self->touch = mutant_jump_touch; +} + +void +mutant_check_landing(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->groundentity) + { + gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0); + self->monsterinfo.attack_finished = 0; + self->monsterinfo.aiflags &= ~AI_DUCKED; + return; + } + + if (level.time > self->monsterinfo.attack_finished) + { + self->monsterinfo.nextframe = FRAME_attack02; + } + else + { + self->monsterinfo.nextframe = FRAME_attack05; + } +} + +static mframe_t mutant_frames_jump[] = { + {ai_charge, 0, NULL}, + {ai_charge, 17, NULL}, + {ai_charge, 15, mutant_jump_takeoff}, + {ai_charge, 15, NULL}, + {ai_charge, 15, mutant_check_landing}, + {ai_charge, 0, NULL}, + {ai_charge, 3, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t mutant_move_jump = +{ + FRAME_attack01, + FRAME_attack08, + mutant_frames_jump, + mutant_run +}; + +void +mutant_jump(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &mutant_move_jump; +} + +qboolean +mutant_check_melee(edict_t *self) +{ + if (!self) + { + return false; + } + + if (range(self, self->enemy) == RANGE_MELEE) + { + return true; + } + + return false; +} + +qboolean +mutant_check_jump(edict_t *self) +{ + vec3_t v; + float distance; + + if (!self) + { + return false; + } + + if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2])) + { + return false; + } + + if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2])) + { + return false; + } + + v[0] = self->s.origin[0] - self->enemy->s.origin[0]; + v[1] = self->s.origin[1] - self->enemy->s.origin[1]; + v[2] = 0; + distance = VectorLength(v); + + if (distance < 100) + { + return false; + } + + if (distance > 100) + { + if (random() < 0.9) + { + return false; + } + } + + return true; +} + +qboolean +mutant_checkattack(edict_t *self) +{ + if (!self) + { + return false; + } + + if (!self->enemy || (self->enemy->health <= 0)) + { + return false; + } + + if (mutant_check_melee(self)) + { + self->monsterinfo.attack_state = AS_MELEE; + return true; + } + + if (mutant_check_jump(self)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return false; +} + +static mframe_t mutant_frames_pain1[] = { + {ai_move, 4, NULL}, + {ai_move, -3, NULL}, + {ai_move, -8, NULL}, + {ai_move, 2, NULL}, + {ai_move, 5, NULL} +}; + +mmove_t mutant_move_pain1 = +{ + FRAME_pain101, + FRAME_pain105, + mutant_frames_pain1, + mutant_run +}; + +static mframe_t mutant_frames_pain2[] = { + {ai_move, -24, NULL}, + {ai_move, 11, NULL}, + {ai_move, 5, NULL}, + {ai_move, -2, NULL}, + {ai_move, 6, NULL}, + {ai_move, 4, NULL} +}; + +mmove_t mutant_move_pain2 = +{ + FRAME_pain201, + FRAME_pain206, + mutant_frames_pain2, + mutant_run +}; + +static mframe_t mutant_frames_pain3[] = { + {ai_move, -22, NULL}, + {ai_move, 3, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 6, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL} +}; + +mmove_t mutant_move_pain3 = +{ + FRAME_pain301, + FRAME_pain311, + mutant_frames_pain3, + mutant_run +}; + +void +mutant_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) +{ + float r; + + if (!self) + { + return; + } + + 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; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + r = random(); + + if (r < 0.33) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain1; + } + else if (r < 0.66) + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain2; + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &mutant_move_pain3; + } +} + +void +mutant_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); + + M_FlyCheck(self); +} + +static mframe_t mutant_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t mutant_move_death1 = +{ + FRAME_death101, + FRAME_death109, + mutant_frames_death1, + mutant_dead +}; + +static mframe_t mutant_frames_death2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t mutant_move_death2 = +{ + FRAME_death201, + FRAME_death210, + mutant_frames_death2, + mutant_dead +}; + +void +mutant_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 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; + } + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum = 1; + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &mutant_move_death1; + } + else + { + self->monsterinfo.currentmove = &mutant_move_death2; + } +} + +static void +mutant_jump_down(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +static void +mutant_jump_up(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 200, forward, self->velocity); + VectorMA(self->velocity, 450, up, self->velocity); +} + +static void +mutant_jump_wait_land(edict_t *self) +{ + if (self->groundentity == NULL) + { + self->monsterinfo.nextframe = self->s.frame; + } + else + { + self->monsterinfo.nextframe = self->s.frame + 1; + } +} + +static mframe_t mutant_frames_jump_up[] = { + {ai_move, -8, NULL}, + {ai_move, -8, mutant_jump_up}, + {ai_move, 0, mutant_jump_wait_land}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t mutant_move_jump_up = { + FRAME_jump01, + FRAME_jump05, + mutant_frames_jump_up, + mutant_run +}; + +static mframe_t mutant_frames_jump_down[] = { + {ai_move, 0, NULL}, + {ai_move, 0, mutant_jump_down}, + {ai_move, 0, mutant_jump_wait_land}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t mutant_move_jump_down = { + FRAME_jump01, + FRAME_jump05, + mutant_frames_jump_down, + mutant_run +}; + +static void +mutant_jump_updown(edict_t *self) +{ + if (!self || !self->enemy) + { + return; + } + + if (self->enemy->absmin[2] > self->absmin[2]) + { + self->monsterinfo.currentmove = &mutant_move_jump_up; + } + else + { + self->monsterinfo.currentmove = &mutant_move_jump_down; + } +} + +qboolean +mutant_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + if (blocked_checkjump(self, dist, 256, 68)) + { + mutant_jump_updown(self); + return true; + } + + if (blocked_checkplat(self, dist)) + return true; + + return false; +} + +/* + * QUAKED monster_mutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_mutant(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_swing = gi.soundindex("mutant/mutatck1.wav"); + sound_hit = gi.soundindex("mutant/mutatck2.wav"); + sound_hit2 = gi.soundindex("mutant/mutatck3.wav"); + sound_death = gi.soundindex("mutant/mutdeth1.wav"); + sound_idle = gi.soundindex("mutant/mutidle1.wav"); + sound_pain1 = gi.soundindex("mutant/mutpain1.wav"); + sound_pain2 = gi.soundindex("mutant/mutpain2.wav"); + sound_sight = gi.soundindex("mutant/mutsght1.wav"); + sound_search = gi.soundindex("mutant/mutsrch1.wav"); + sound_step1 = gi.soundindex("mutant/step1.wav"); + sound_step2 = gi.soundindex("mutant/step2.wav"); + sound_step3 = gi.soundindex("mutant/step3.wav"); + sound_thud = gi.soundindex("mutant/thud1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/mutant/tris.md2"); + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, 48); + + self->health = 300; + self->gib_health = -120; + self->mass = 300; + + self->pain = mutant_pain; + self->die = mutant_die; + + self->monsterinfo.stand = mutant_stand; + self->monsterinfo.walk = mutant_walk; + self->monsterinfo.run = mutant_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = mutant_jump; + self->monsterinfo.melee = mutant_melee; + self->monsterinfo.sight = mutant_sight; + self->monsterinfo.search = mutant_search; + self->monsterinfo.idle = mutant_idle; + self->monsterinfo.checkattack = mutant_checkattack; + self->monsterinfo.blocked = mutant_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &mutant_move_stand; + + self->monsterinfo.scale = MODEL_SCALE; + walkmonster_start(self); +} diff --git a/src/game/monster/mutant/mutant.h b/src/game/monster/mutant/mutant.h new file mode 100644 index 000000000..239ed0b4c --- /dev/null +++ b/src/game/monster/mutant/mutant.h @@ -0,0 +1,183 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Mutant animations. + * + * ======================================================================= + */ + +#define FRAME_attack01 0 +#define FRAME_attack02 1 +#define FRAME_attack03 2 +#define FRAME_attack04 3 +#define FRAME_attack05 4 +#define FRAME_attack06 5 +#define FRAME_attack07 6 +#define FRAME_attack08 7 +#define FRAME_attack09 8 +#define FRAME_attack10 9 +#define FRAME_attack11 10 +#define FRAME_attack12 11 +#define FRAME_attack13 12 +#define FRAME_attack14 13 +#define FRAME_attack15 14 +#define FRAME_death101 15 +#define FRAME_death102 16 +#define FRAME_death103 17 +#define FRAME_death104 18 +#define FRAME_death105 19 +#define FRAME_death106 20 +#define FRAME_death107 21 +#define FRAME_death108 22 +#define FRAME_death109 23 +#define FRAME_death201 24 +#define FRAME_death202 25 +#define FRAME_death203 26 +#define FRAME_death204 27 +#define FRAME_death205 28 +#define FRAME_death206 29 +#define FRAME_death207 30 +#define FRAME_death208 31 +#define FRAME_death209 32 +#define FRAME_death210 33 +#define FRAME_pain101 34 +#define FRAME_pain102 35 +#define FRAME_pain103 36 +#define FRAME_pain104 37 +#define FRAME_pain105 38 +#define FRAME_pain201 39 +#define FRAME_pain202 40 +#define FRAME_pain203 41 +#define FRAME_pain204 42 +#define FRAME_pain205 43 +#define FRAME_pain206 44 +#define FRAME_pain301 45 +#define FRAME_pain302 46 +#define FRAME_pain303 47 +#define FRAME_pain304 48 +#define FRAME_pain305 49 +#define FRAME_pain306 50 +#define FRAME_pain307 51 +#define FRAME_pain308 52 +#define FRAME_pain309 53 +#define FRAME_pain310 54 +#define FRAME_pain311 55 +#define FRAME_run03 56 +#define FRAME_run04 57 +#define FRAME_run05 58 +#define FRAME_run06 59 +#define FRAME_run07 60 +#define FRAME_run08 61 +#define FRAME_stand101 62 +#define FRAME_stand102 63 +#define FRAME_stand103 64 +#define FRAME_stand104 65 +#define FRAME_stand105 66 +#define FRAME_stand106 67 +#define FRAME_stand107 68 +#define FRAME_stand108 69 +#define FRAME_stand109 70 +#define FRAME_stand110 71 +#define FRAME_stand111 72 +#define FRAME_stand112 73 +#define FRAME_stand113 74 +#define FRAME_stand114 75 +#define FRAME_stand115 76 +#define FRAME_stand116 77 +#define FRAME_stand117 78 +#define FRAME_stand118 79 +#define FRAME_stand119 80 +#define FRAME_stand120 81 +#define FRAME_stand121 82 +#define FRAME_stand122 83 +#define FRAME_stand123 84 +#define FRAME_stand124 85 +#define FRAME_stand125 86 +#define FRAME_stand126 87 +#define FRAME_stand127 88 +#define FRAME_stand128 89 +#define FRAME_stand129 90 +#define FRAME_stand130 91 +#define FRAME_stand131 92 +#define FRAME_stand132 93 +#define FRAME_stand133 94 +#define FRAME_stand134 95 +#define FRAME_stand135 96 +#define FRAME_stand136 97 +#define FRAME_stand137 98 +#define FRAME_stand138 99 +#define FRAME_stand139 100 +#define FRAME_stand140 101 +#define FRAME_stand141 102 +#define FRAME_stand142 103 +#define FRAME_stand143 104 +#define FRAME_stand144 105 +#define FRAME_stand145 106 +#define FRAME_stand146 107 +#define FRAME_stand147 108 +#define FRAME_stand148 109 +#define FRAME_stand149 110 +#define FRAME_stand150 111 +#define FRAME_stand151 112 +#define FRAME_stand152 113 +#define FRAME_stand153 114 +#define FRAME_stand154 115 +#define FRAME_stand155 116 +#define FRAME_stand156 117 +#define FRAME_stand157 118 +#define FRAME_stand158 119 +#define FRAME_stand159 120 +#define FRAME_stand160 121 +#define FRAME_stand161 122 +#define FRAME_stand162 123 +#define FRAME_stand163 124 +#define FRAME_stand164 125 +#define FRAME_walk01 126 +#define FRAME_walk02 127 +#define FRAME_walk03 128 +#define FRAME_walk04 129 +#define FRAME_walk05 130 +#define FRAME_walk06 131 +#define FRAME_walk07 132 +#define FRAME_walk08 133 +#define FRAME_walk09 134 +#define FRAME_walk10 135 +#define FRAME_walk11 136 +#define FRAME_walk12 137 +#define FRAME_walk13 138 +#define FRAME_walk14 139 +#define FRAME_walk15 140 +#define FRAME_walk16 141 +#define FRAME_walk17 142 +#define FRAME_walk18 143 +#define FRAME_walk19 144 +#define FRAME_walk20 145 +#define FRAME_walk21 146 +#define FRAME_walk22 147 +#define FRAME_walk23 148 +#define FRAME_jump01 149 +#define FRAME_jump02 150 +#define FRAME_jump03 151 +#define FRAME_jump04 152 +#define FRAME_jump05 153 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/ogre/ogre.c b/src/game/monster/ogre/ogre.c new file mode 100644 index 000000000..bbbe3ddbe --- /dev/null +++ b/src/game/monster/ogre/ogre.c @@ -0,0 +1,526 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "ogre.h" + +static int sound_death; +static int sound_attack; +static int sound_melee; +static int sound_sight; +static int sound_search; +static int sound_idle; +static int sound_pain; + +static void +ogre_idle(edict_t *self) +{ + if (random() < 0.2) + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_NORM, 0); +} + +// Stand +static mframe_t ogre_frames_stand [] = +{ + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + + {ai_stand, 0, ogre_idle}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + + {ai_stand, 0, NULL}, +}; +mmove_t ogre_move_stand = +{ + FRAME_stand1, + FRAME_stand9, + ogre_frames_stand, + NULL +}; + +void +ogre_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &ogre_move_stand; +} + +// Run +static mframe_t ogre_frames_run [] = +{ + {ai_run, 9, NULL}, + {ai_run, 12, NULL}, + {ai_run, 8, NULL}, + {ai_run, 22, NULL}, + {ai_run, 16, NULL}, + + {ai_run, 4, NULL}, + {ai_run, 13, NULL}, + {ai_run, 24, NULL} +}; +mmove_t ogre_move_run = +{ + FRAME_run1, + FRAME_run8, + ogre_frames_run, + NULL +}; + +void +ogre_run(edict_t *self) +{ + self->monsterinfo.currentmove = &ogre_move_run; +} + +static void +OgreChainsaw(edict_t *self) +{ + vec3_t dir; + static vec3_t aim = {100, 0, -24}; + int damage; + + if (!self->enemy) + return; + VectorSubtract(self->s.origin, self->enemy->s.origin, dir); + + if (VectorLength(dir) > 100.0) + return; + damage = (random() + random() + random()) * 4; + + fire_hit(self, aim, damage, damage); +} + +// Smash +static mframe_t ogre_frames_smash [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, 4, NULL}, + + {ai_charge, 14, OgreChainsaw}, + {ai_charge, 14, OgreChainsaw}, + {ai_charge, 20, OgreChainsaw}, + {ai_charge, 23, OgreChainsaw}, + + {ai_charge, 10, OgreChainsaw}, + {ai_charge, 12, OgreChainsaw}, + {ai_charge, 1, NULL}, + {ai_charge, 4, NULL}, + + {ai_charge, 12, NULL}, + {ai_charge, 0, NULL} +}; +mmove_t ogre_move_smash = +{ + FRAME_smash1, + FRAME_smash14, + ogre_frames_smash, + ogre_run +}; + +// Swing +static mframe_t ogre_frames_swing [] = +{ + {ai_charge, 11, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 19, OgreChainsaw}, + + {ai_charge, 13, OgreChainsaw}, + {ai_charge, 10, OgreChainsaw}, + {ai_charge, 10, OgreChainsaw}, + {ai_charge, 10, OgreChainsaw}, + + {ai_charge, 10, OgreChainsaw}, + {ai_charge, 10, OgreChainsaw}, + {ai_charge, 3, NULL}, + {ai_charge, 8, NULL}, + + {ai_charge, 9, NULL}, + {ai_charge, 0, NULL} +}; +mmove_t ogre_move_swing = +{ + FRAME_swing1, + FRAME_swing14, + ogre_frames_swing, + ogre_run +}; + +// Melee +void +ogre_melee(edict_t *self) +{ + if (random() > 0.5) + self->monsterinfo.currentmove = &ogre_move_smash; + else + self->monsterinfo.currentmove = &ogre_move_swing; + gi.sound(self, CHAN_WEAPON, sound_melee, 1, ATTN_NORM, 0); +} + +static void +FireOgreGrenade(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t aim; + vec3_t offset = {0, 0, 16}; + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, offset, forward, right, start); + VectorCopy(forward, aim); + + monster_fire_grenade(self, start, aim, 40, 600, MZ2_GUNNER_GRENADE_1); +} + +// Grenade +static mframe_t ogre_frames_attack [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, FireOgreGrenade}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; +mmove_t ogre_move_attack = +{ + FRAME_shoot1, + FRAME_shoot6, + ogre_frames_attack, + ogre_run +}; + +void +ogre_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &ogre_move_attack; +} + +// Pain (1) +static mframe_t ogre_frames_pain1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t ogre_move_pain1 = +{ + FRAME_pain1, + FRAME_pain5, + ogre_frames_pain1, + ogre_run +}; + +// Pain (2) +static mframe_t ogre_frames_pain2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t ogre_move_pain2 = +{ + FRAME_painb1, + FRAME_painb3, + ogre_frames_pain2, + ogre_run +}; + +// Pain (3) +static mframe_t ogre_frames_pain3 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t ogre_move_pain3 = +{ + FRAME_painc1, + FRAME_painc6, + ogre_frames_pain3, + ogre_run +}; + +// Pain (4) +static mframe_t ogre_frames_pain4 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 10, NULL}, + {ai_move, 9, NULL}, + {ai_move, 4, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t ogre_move_pain4 = +{ + FRAME_paind1, + FRAME_paind16, + ogre_frames_pain4, + ogre_run +}; + +// Pain (5) +static mframe_t ogre_frames_pain5 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 10, NULL}, + {ai_move, 9, NULL}, + {ai_move, 4, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t ogre_move_pain5 = +{ + FRAME_paine1, + FRAME_paine15, + ogre_frames_pain5, + ogre_run +}; + +// Pain +void +ogre_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + float r; + + // decino: No pain animations in Nightmare mode + if (skill->value == SKILL_HARDPLUS) + return; + if (self->pain_debounce_time > level.time) + return; + r = random(); + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (r < 0.25) + { + self->monsterinfo.currentmove = &ogre_move_pain1; + self->pain_debounce_time = level.time + 1.0; + } + else if (r < 0.5) + { + self->monsterinfo.currentmove = &ogre_move_pain2; + self->pain_debounce_time = level.time + 1.0; + } + else if (r < 0.75) + { + self->monsterinfo.currentmove = &ogre_move_pain3; + self->pain_debounce_time = level.time + 1.0; + } + else if (r < 0.88) + { + self->monsterinfo.currentmove = &ogre_move_pain4; + self->pain_debounce_time = level.time + 2.0; + } + else + { + self->monsterinfo.currentmove = &ogre_move_pain5; + self->pain_debounce_time = level.time + 2.0; + } +} + +void +ogre_dead(edict_t *self) +{ + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +// Death (1) +static mframe_t ogre_frames_death1 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t ogre_move_death1 = +{ + FRAME_death1, + FRAME_death14, + ogre_frames_death1, + ogre_dead +}; + +// Death (2) +static mframe_t ogre_frames_death2 [] = +{ + {ai_move, 0, NULL}, + {ai_move, 5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + + {ai_move, 3, NULL}, + {ai_move, 7, NULL}, + {ai_move, 25, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t ogre_move_death2 = +{ + FRAME_bdeath1, + FRAME_bdeath10, + ogre_frames_death2, + ogre_dead +}; + +// Death +void +ogre_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 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; + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + if (random() < 0.5) + self->monsterinfo.currentmove = &ogre_move_death1; + else + self->monsterinfo.currentmove = &ogre_move_death2; +} + +// Sight +void +ogre_sight(edict_t *self, edict_t *other /* unused */) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +// Search +void +ogre_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +/* + * QUAKED monster_ogre (1 .5 0) (-32, -32, -24) (32, 32, 64) Ambush Trigger_Spawn Sight + */ +void +SP_monster_ogre(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/ogre/tris.md2"); + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, 64); + self->health = 200; + + sound_death = gi.soundindex("ogre/ogdth.wav"); + sound_attack = gi.soundindex("ogre/grenade.wav"); + sound_melee = gi.soundindex("ogre/ogsawatk.wav"); + sound_sight = gi.soundindex("ogre/ogwake.wav"); + sound_search = gi.soundindex("ogre/ogidle2.wav"); + sound_idle = gi.soundindex("ogre/ogidle.wav"); + sound_pain = gi.soundindex("ogre/ogpain1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -80; + self->mass = 200; + + self->monsterinfo.stand = ogre_stand; + self->monsterinfo.walk = ogre_run; + self->monsterinfo.run = ogre_run; + self->monsterinfo.attack = ogre_attack; + self->monsterinfo.melee = ogre_melee; + self->monsterinfo.sight = ogre_sight; + self->monsterinfo.search = ogre_search; + + self->pain = ogre_pain; + self->die = ogre_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/ogre/ogre.h b/src/game/monster/ogre/ogre.h new file mode 100644 index 000000000..4fefea9c4 --- /dev/null +++ b/src/game/monster/ogre/ogre.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Ogre animations. + * + * ======================================================================= + */ + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_stand8 7 +#define FRAME_stand9 8 +#define FRAME_walk1 9 +#define FRAME_walk2 10 +#define FRAME_walk3 11 +#define FRAME_walk4 12 +#define FRAME_walk5 13 +#define FRAME_walk6 14 +#define FRAME_walk7 15 +#define FRAME_walk8 16 +#define FRAME_walk9 17 +#define FRAME_walk10 18 +#define FRAME_walk11 19 +#define FRAME_walk12 20 +#define FRAME_walk13 21 +#define FRAME_walk14 22 +#define FRAME_walk15 23 +#define FRAME_walk16 24 +#define FRAME_run1 25 +#define FRAME_run2 26 +#define FRAME_run3 27 +#define FRAME_run4 28 +#define FRAME_run5 29 +#define FRAME_run6 30 +#define FRAME_run7 31 +#define FRAME_run8 32 +#define FRAME_swing1 33 +#define FRAME_swing2 34 +#define FRAME_swing3 35 +#define FRAME_swing4 36 +#define FRAME_swing5 37 +#define FRAME_swing6 38 +#define FRAME_swing7 39 +#define FRAME_swing8 40 +#define FRAME_swing9 41 +#define FRAME_swing10 42 +#define FRAME_swing11 43 +#define FRAME_swing12 44 +#define FRAME_swing13 45 +#define FRAME_swing14 46 +#define FRAME_smash1 47 +#define FRAME_smash2 48 +#define FRAME_smash3 49 +#define FRAME_smash4 50 +#define FRAME_smash5 51 +#define FRAME_smash6 52 +#define FRAME_smash7 53 +#define FRAME_smash8 54 +#define FRAME_smash9 55 +#define FRAME_smash10 56 +#define FRAME_smash11 57 +#define FRAME_smash12 58 +#define FRAME_smash13 59 +#define FRAME_smash14 60 +#define FRAME_shoot1 61 +#define FRAME_shoot2 62 +#define FRAME_shoot3 63 +#define FRAME_shoot4 64 +#define FRAME_shoot5 65 +#define FRAME_shoot6 66 +#define FRAME_pain1 67 +#define FRAME_pain2 68 +#define FRAME_pain3 69 +#define FRAME_pain4 70 +#define FRAME_pain5 71 +#define FRAME_painb1 72 +#define FRAME_painb2 73 +#define FRAME_painb3 74 +#define FRAME_painc1 75 +#define FRAME_painc2 76 +#define FRAME_painc3 77 +#define FRAME_painc4 78 +#define FRAME_painc5 79 +#define FRAME_painc6 80 +#define FRAME_paind1 81 +#define FRAME_paind2 82 +#define FRAME_paind3 83 +#define FRAME_paind4 84 +#define FRAME_paind5 85 +#define FRAME_paind6 86 +#define FRAME_paind7 87 +#define FRAME_paind8 88 +#define FRAME_paind9 89 +#define FRAME_paind10 90 +#define FRAME_paind11 91 +#define FRAME_paind12 92 +#define FRAME_paind13 93 +#define FRAME_paind14 94 +#define FRAME_paind15 95 +#define FRAME_paind16 96 +#define FRAME_paine1 97 +#define FRAME_paine2 98 +#define FRAME_paine3 99 +#define FRAME_paine4 100 +#define FRAME_paine5 101 +#define FRAME_paine6 102 +#define FRAME_paine7 103 +#define FRAME_paine8 104 +#define FRAME_paine9 105 +#define FRAME_paine10 106 +#define FRAME_paine11 107 +#define FRAME_paine12 108 +#define FRAME_paine13 109 +#define FRAME_paine14 110 +#define FRAME_paine15 111 +#define FRAME_death1 112 +#define FRAME_death2 113 +#define FRAME_death3 114 +#define FRAME_death4 115 +#define FRAME_death5 116 +#define FRAME_death6 117 +#define FRAME_death7 118 +#define FRAME_death8 119 +#define FRAME_death9 120 +#define FRAME_death10 121 +#define FRAME_death11 122 +#define FRAME_death12 123 +#define FRAME_death13 124 +#define FRAME_death14 125 +#define FRAME_bdeath1 126 +#define FRAME_bdeath2 127 +#define FRAME_bdeath3 128 +#define FRAME_bdeath4 129 +#define FRAME_bdeath5 130 +#define FRAME_bdeath6 131 +#define FRAME_bdeath7 132 +#define FRAME_bdeath8 133 +#define FRAME_bdeath9 134 +#define FRAME_bdeath10 135 +#define FRAME_pull1 136 +#define FRAME_pull2 137 +#define FRAME_pull3 138 +#define FRAME_pull4 139 +#define FRAME_pull5 140 +#define FRAME_pull6 141 +#define FRAME_pull7 142 +#define FRAME_pull8 143 +#define FRAME_pull9 144 +#define FRAME_pull10 145 +#define FRAME_pull11 146 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/parasite/parasite.c b/src/game/monster/parasite/parasite.c new file mode 100644 index 000000000..9c48b3c07 --- /dev/null +++ b/src/game/monster/parasite/parasite.c @@ -0,0 +1,1046 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Parasite. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "parasite.h" + +static int sound_pain1; +static int sound_pain2; +static int sound_die; +static int sound_launch; +static int sound_impact; +static int sound_suck; +static int sound_reelin; +static int sound_sight; +static int sound_tap; +static int sound_scratch; +static int sound_search; + +void parasite_stand(edict_t *self); +void parasite_start_run(edict_t *self); +void parasite_run(edict_t *self); +void parasite_walk(edict_t *self); +void parasite_start_walk(edict_t *self); +void parasite_end_fidget(edict_t *self); +void parasite_do_fidget(edict_t *self); +void parasite_refidget(edict_t *self); + +void +parasite_launch(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_launch, 1, ATTN_NORM, 0); +} + +void +parasite_reel_in(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_reelin, 1, ATTN_NORM, 0); +} + +void +parasite_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0); +} + +void +parasite_tap(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_tap, 1, ATTN_IDLE, 0); +} + +static void +parasite_footstep(edict_t *self) +{ + if (g_monsterfootsteps->value) + { + parasite_tap(self); + } +} + +void +parasite_scratch(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_scratch, 1, ATTN_IDLE, 0); +} + +void +parasite_search(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_search, 1, ATTN_IDLE, 0); +} + +static mframe_t parasite_frames_start_fidget[] = { + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL} +}; + +mmove_t parasite_move_start_fidget = +{ + FRAME_stand18, + FRAME_stand21, + parasite_frames_start_fidget, + parasite_do_fidget +}; + +static mframe_t parasite_frames_fidget[] = { + {ai_stand, 0, parasite_scratch}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, parasite_scratch}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL} +}; + +mmove_t parasite_move_fidget = +{ + FRAME_stand22, + FRAME_stand27, + parasite_frames_fidget, + parasite_refidget +}; + +static mframe_t parasite_frames_end_fidget[] = { + {ai_stand, 0, parasite_scratch}, + {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 parasite_move_end_fidget = +{ + FRAME_stand28, + FRAME_stand35, + parasite_frames_end_fidget, + parasite_stand +}; + +void +parasite_end_fidget(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = ¶site_move_end_fidget; +} + +void +parasite_do_fidget(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = ¶site_move_fidget; +} + +void +parasite_refidget(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() <= 0.8) + { + self->monsterinfo.currentmove = ¶site_move_fidget; + } + else + { + self->monsterinfo.currentmove = ¶site_move_end_fidget; + } +} + +void +parasite_idle(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = ¶site_move_start_fidget; +} + +static mframe_t parasite_frames_stand[] = { + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, parasite_tap}, + {ai_stand, 0, NULL}, + {ai_stand, 0, parasite_tap}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, parasite_tap}, + {ai_stand, 0, NULL}, + {ai_stand, 0, parasite_tap}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, parasite_tap}, + {ai_stand, 0, NULL}, + {ai_stand, 0, parasite_tap} +}; + +mmove_t parasite_move_stand = +{ + FRAME_stand01, + FRAME_stand17, + parasite_frames_stand, + parasite_stand +}; + +void +parasite_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = ¶site_move_stand; +} + +static mframe_t parasite_frames_run[] = { + {ai_run, 30, NULL}, + {ai_run, 30, NULL}, + {ai_run, 22, parasite_footstep}, + {ai_run, 19, parasite_footstep}, + {ai_run, 24, NULL}, + {ai_run, 28, parasite_footstep}, + {ai_run, 25, NULL} +}; + +mmove_t parasite_move_run = +{ + FRAME_run03, + FRAME_run09, + parasite_frames_run, + NULL +}; + +static mframe_t parasite_frames_start_run[] = { + {ai_run, 0, NULL}, + {ai_run, 30, NULL}, +}; + +mmove_t parasite_move_start_run = +{ + FRAME_run01, + FRAME_run02, + parasite_frames_start_run, + parasite_run +}; + +static mframe_t parasite_frames_stop_run[] = { + {ai_run, 20, NULL}, + {ai_run, 20, NULL}, + {ai_run, 12, parasite_footstep}, + {ai_run, 10, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL} +}; + +mmove_t parasite_move_stop_run = +{ + FRAME_run10, + FRAME_run15, + parasite_frames_stop_run, + NULL +}; + +void +parasite_start_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = ¶site_move_stand; + } + else + { + self->monsterinfo.currentmove = ¶site_move_start_run; + } +} + +void +parasite_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = ¶site_move_stand; + } + else + { + self->monsterinfo.currentmove = ¶site_move_run; + } +} + +static mframe_t parasite_frames_walk[] = { + {ai_walk, 30, NULL}, + {ai_walk, 30, NULL}, + {ai_walk, 22, parasite_footstep}, + {ai_walk, 19, NULL}, + {ai_walk, 24, parasite_footstep}, + {ai_walk, 28, parasite_footstep}, + {ai_walk, 25, NULL} +}; + +mmove_t parasite_move_walk = +{ + FRAME_run03, + FRAME_run09, + parasite_frames_walk, + parasite_walk +}; + +static mframe_t parasite_frames_start_walk[] = { + {ai_walk, 0, NULL}, + {ai_walk, 30, parasite_walk} +}; + +mmove_t parasite_move_start_walk = +{ + FRAME_run01, + FRAME_run02, + parasite_frames_start_walk, + NULL +}; + +static mframe_t parasite_frames_stop_walk[] = { + {ai_walk, 20, NULL}, + {ai_walk, 20, NULL}, + {ai_walk, 12, parasite_footstep}, + {ai_walk, 10, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL} +}; + +mmove_t parasite_move_stop_walk = +{ + FRAME_run10, + FRAME_run15, + parasite_frames_stop_walk, + NULL +}; + +void +parasite_start_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = ¶site_move_start_walk; +} + +void +parasite_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = ¶site_move_walk; +} + +static mframe_t parasite_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 6, NULL}, + {ai_move, 16, NULL}, + {ai_move, -6, NULL}, + {ai_move, -7, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t parasite_move_pain1 = +{ + FRAME_pain101, + FRAME_pain111, + parasite_frames_pain1, + parasite_start_run +}; + +void +parasite_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) +{ + if (!self) + { + return; + } + + 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; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + } + + self->monsterinfo.currentmove = ¶site_move_pain1; +} + +static qboolean +parasite_drain_attack_ok(vec3_t start, vec3_t end) +{ + vec3_t dir, angles; + + /* check for max distance */ + VectorSubtract(start, end, dir); + + if (VectorLength(dir) > 256) + { + return false; + } + + /* check for min/max pitch */ + vectoangles(dir, angles); + + if (angles[0] < -180) + { + angles[0] += 360; + } + + if (fabs(angles[0]) > 30) + { + return false; + } + + return true; +} + +void +parasite_drain_attack(edict_t *self) +{ + vec3_t offset, start, origStart, f, r, end, dir; + trace_t tr; + int damage; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, NULL); + VectorSet(offset, 24, 0, 6); + G_ProjectSource(self->s.origin, offset, f, r, start); + + VectorCopy(self->enemy->s.origin, end); + VectorSubtract(end, start, dir); + + { + // will use the original startPoint for the actual effect etc, + // the modified start is just for the traces + VectorCopy(start, origStart); + vec3_t dir2; // need normalized dir for offset + VectorCopy(dir, dir2); + VectorNormalize(dir2); + // start = start - 8*dir => move start back a bit + // so trace doesn't start in wall in case parasite is too close to wall + VectorMA(start, -8.0f, dir2, start); + } + + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + + if (!parasite_drain_attack_ok(start, end)) + { + return; + } + } + } + + VectorCopy(self->enemy->s.origin, end); + + tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT); + + if (tr.ent != self->enemy) + { + return; + } + + if (self->s.frame == FRAME_drain03) + { + damage = 5; + gi.sound(self->enemy, CHAN_AUTO, sound_impact, 1, ATTN_NORM, 0); + } + else + { + if (self->s.frame == FRAME_drain04) + { + gi.sound(self, CHAN_WEAPON, sound_suck, 1, ATTN_NORM, 0); + } + + damage = 2; + } + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PARASITE_ATTACK); + gi.WriteShort(self - g_edicts); + gi.WritePosition(origStart); + gi.WritePosition(end); + gi.multicast(self->s.origin, MULTICAST_PVS); + + T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, + vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN); +} + +static mframe_t parasite_frames_drain[] = { + {ai_charge, 0, parasite_launch}, + {ai_charge, 0, NULL}, + {ai_charge, 15, parasite_drain_attack}, /* Target hits */ + {ai_charge, 0, parasite_drain_attack}, /* drain */ + {ai_charge, 0, parasite_drain_attack}, /* drain */ + {ai_charge, 0, parasite_drain_attack}, /* drain */ + {ai_charge, 0, parasite_drain_attack}, /* drain */ + {ai_charge, -2, parasite_drain_attack}, /* drain */ + {ai_charge, -2, parasite_drain_attack}, /* drain */ + {ai_charge, -3, parasite_drain_attack}, /* drain */ + {ai_charge, -2, parasite_drain_attack}, /* drain */ + {ai_charge, 0, parasite_drain_attack}, /* drain */ + {ai_charge, -1, parasite_drain_attack}, /* drain */ + {ai_charge, 0, parasite_reel_in}, /* let go */ + {ai_charge, -2, NULL}, + {ai_charge, -2, NULL}, + {ai_charge, -3, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t parasite_move_drain = +{ + FRAME_drain01, + FRAME_drain18, + parasite_frames_drain, + parasite_start_run +}; + +static mframe_t parasite_frames_break[] = { + {ai_charge, 0, NULL}, + {ai_charge, -3, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, -3, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, 3, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, -18, NULL}, + {ai_charge, 3, NULL}, + {ai_charge, 9, NULL}, + {ai_charge, 6, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, -18, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 8, NULL}, + {ai_charge, 9, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, -18, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, /* airborne */ + {ai_charge, 0, NULL}, /* airborne */ + {ai_charge, 0, NULL}, /* slides */ + {ai_charge, 0, NULL}, /* slides */ + {ai_charge, 0, NULL}, /* slides */ + {ai_charge, 0, NULL}, /* slides */ + {ai_charge, 4, NULL}, + {ai_charge, 11, NULL}, + {ai_charge, -2, NULL}, + {ai_charge, -5, NULL}, + {ai_charge, 1, NULL} +}; + +mmove_t parasite_move_break = +{ + FRAME_break01, + FRAME_break32, + parasite_frames_break, + parasite_start_run +}; + +void +parasite_attack(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = ¶site_move_drain; +} + +void +parasite_jump_down(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + monster_jump_start(self); + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +void +parasite_jump_up(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + monster_jump_start(self); + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 200, forward, self->velocity); + VectorMA(self->velocity, 450, up, self->velocity); +} + +void +parasite_jump_wait_land(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->groundentity == NULL) + { + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + { + self->monsterinfo.nextframe = self->s.frame + 1; + } + } + else + { + self->monsterinfo.nextframe = self->s.frame + 1; + } +} + +static mframe_t parasite_frames_jump_up[] = { + {ai_move, -8, NULL}, + {ai_move, -8, NULL}, + {ai_move, -8, NULL}, + {ai_move, -8, parasite_jump_up}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, parasite_jump_wait_land}, + {ai_move, 0, NULL} +}; + +mmove_t parasite_move_jump_up = { + FRAME_jump01, + FRAME_jump08, + parasite_frames_jump_up, + parasite_run +}; + +static mframe_t parasite_frames_jump_down[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, parasite_jump_down}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, parasite_jump_wait_land}, + {ai_move, 0, NULL} +}; + +mmove_t parasite_move_jump_down = { + FRAME_jump01, + FRAME_jump08, + parasite_frames_jump_down, + parasite_run +}; + +void +parasite_jump(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->enemy) + { + return; + } + + if (self->enemy->absmin[2] > self->absmin[2]) + { + self->monsterinfo.currentmove = ¶site_move_jump_up; + } + else + { + self->monsterinfo.currentmove = ¶site_move_jump_down; + } +} + +qboolean +parasite_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + if (self->enemy && self->enemy->client && random() >= (0.25 + (0.05 * skill->value))) + { + vec3_t f, r, offset, start, end; + + AngleVectors(self->s.angles, f, r, NULL); + VectorSet(offset, 24, 0, 6); + G_ProjectSource(self->s.origin, offset, f, r, start); + + VectorCopy(self->enemy->s.origin, end); + + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + + if (!parasite_drain_attack_ok(start, end)) + { + return false; + } + } + } + + VectorCopy(self->enemy->s.origin, end); + + if (visible(self, self->enemy)) + { + parasite_attack(self); + return true; + } + } + + if (blocked_checkjump(self, dist, 256, 68)) + { + parasite_jump(self); + return true; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + return false; +} + +qboolean +parasite_checkattack(edict_t *self) +{ + vec3_t f, r, offset, start, end; + trace_t tr; + qboolean retval; + + if (!self) + { + return false; + } + + retval = M_CheckAttack(self); + + if (!retval) + { + return false; + } + + AngleVectors(self->s.angles, f, r, NULL); + VectorSet(offset, 24, 0, 6); + G_ProjectSource(self->s.origin, offset, f, r, start); + + VectorCopy(self->enemy->s.origin, end); + + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + + if (!parasite_drain_attack_ok(start, end)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + + if (!parasite_drain_attack_ok(start, end)) + { + return false; + } + } + } + + VectorCopy(self->enemy->s.origin, end); + + tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT); + + if (tr.ent != self->enemy) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + + if (self->monsterinfo.attack) + { + self->monsterinfo.attack(self); + } + + self->monsterinfo.aiflags &= ~AI_BLOCKED; + return true; + } + + return true; +} + +void +parasite_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +static mframe_t parasite_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t parasite_move_death = +{ + FRAME_death101, + FRAME_death107, + parasite_frames_death, + parasite_dead +}; + +void +parasite_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 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, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = ¶site_move_death; +} + +/* + * QUAKED monster_parasite (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_parasite(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("parasite/parpain1.wav"); + sound_pain2 = gi.soundindex("parasite/parpain2.wav"); + sound_die = gi.soundindex("parasite/pardeth1.wav"); + sound_launch = gi.soundindex("parasite/paratck1.wav"); + sound_impact = gi.soundindex("parasite/paratck2.wav"); + sound_suck = gi.soundindex("parasite/paratck3.wav"); + sound_reelin = gi.soundindex("parasite/paratck4.wav"); + sound_sight = gi.soundindex("parasite/parsght1.wav"); + sound_tap = gi.soundindex("parasite/paridle1.wav"); + sound_scratch = gi.soundindex("parasite/paridle2.wav"); + sound_search = gi.soundindex("parasite/parsrch1.wav"); + + self->s.modelindex = gi.modelindex("models/monsters/parasite/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 24); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 175; + self->gib_health = -50; + self->mass = 250; + self->viewheight = 16; + + self->pain = parasite_pain; + self->die = parasite_die; + + self->monsterinfo.stand = parasite_stand; + self->monsterinfo.walk = parasite_start_walk; + self->monsterinfo.run = parasite_start_run; + self->monsterinfo.attack = parasite_attack; + self->monsterinfo.sight = parasite_sight; + self->monsterinfo.idle = parasite_idle; + self->monsterinfo.blocked = parasite_blocked; + self->monsterinfo.checkattack = parasite_checkattack; + + gi.linkentity(self); + + self->monsterinfo.currentmove = ¶site_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/src/game/monster/parasite/parasite.h b/src/game/monster/parasite/parasite.h new file mode 100644 index 000000000..579c3c7c1 --- /dev/null +++ b/src/game/monster/parasite/parasite.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Parasite animations. + * + * ======================================================================= + */ + +#define FRAME_break01 0 +#define FRAME_break02 1 +#define FRAME_break03 2 +#define FRAME_break04 3 +#define FRAME_break05 4 +#define FRAME_break06 5 +#define FRAME_break07 6 +#define FRAME_break08 7 +#define FRAME_break09 8 +#define FRAME_break10 9 +#define FRAME_break11 10 +#define FRAME_break12 11 +#define FRAME_break13 12 +#define FRAME_break14 13 +#define FRAME_break15 14 +#define FRAME_break16 15 +#define FRAME_break17 16 +#define FRAME_break18 17 +#define FRAME_break19 18 +#define FRAME_break20 19 +#define FRAME_break21 20 +#define FRAME_break22 21 +#define FRAME_break23 22 +#define FRAME_break24 23 +#define FRAME_break25 24 +#define FRAME_break26 25 +#define FRAME_break27 26 +#define FRAME_break28 27 +#define FRAME_break29 28 +#define FRAME_break30 29 +#define FRAME_break31 30 +#define FRAME_break32 31 +#define FRAME_death101 32 +#define FRAME_death102 33 +#define FRAME_death103 34 +#define FRAME_death104 35 +#define FRAME_death105 36 +#define FRAME_death106 37 +#define FRAME_death107 38 +#define FRAME_drain01 39 +#define FRAME_drain02 40 +#define FRAME_drain03 41 +#define FRAME_drain04 42 +#define FRAME_drain05 43 +#define FRAME_drain06 44 +#define FRAME_drain07 45 +#define FRAME_drain08 46 +#define FRAME_drain09 47 +#define FRAME_drain10 48 +#define FRAME_drain11 49 +#define FRAME_drain12 50 +#define FRAME_drain13 51 +#define FRAME_drain14 52 +#define FRAME_drain15 53 +#define FRAME_drain16 54 +#define FRAME_drain17 55 +#define FRAME_drain18 56 +#define FRAME_pain101 57 +#define FRAME_pain102 58 +#define FRAME_pain103 59 +#define FRAME_pain104 60 +#define FRAME_pain105 61 +#define FRAME_pain106 62 +#define FRAME_pain107 63 +#define FRAME_pain108 64 +#define FRAME_pain109 65 +#define FRAME_pain110 66 +#define FRAME_pain111 67 +#define FRAME_run01 68 +#define FRAME_run02 69 +#define FRAME_run03 70 +#define FRAME_run04 71 +#define FRAME_run05 72 +#define FRAME_run06 73 +#define FRAME_run07 74 +#define FRAME_run08 75 +#define FRAME_run09 76 +#define FRAME_run10 77 +#define FRAME_run11 78 +#define FRAME_run12 79 +#define FRAME_run13 80 +#define FRAME_run14 81 +#define FRAME_run15 82 +#define FRAME_stand01 83 +#define FRAME_stand02 84 +#define FRAME_stand03 85 +#define FRAME_stand04 86 +#define FRAME_stand05 87 +#define FRAME_stand06 88 +#define FRAME_stand07 89 +#define FRAME_stand08 90 +#define FRAME_stand09 91 +#define FRAME_stand10 92 +#define FRAME_stand11 93 +#define FRAME_stand12 94 +#define FRAME_stand13 95 +#define FRAME_stand14 96 +#define FRAME_stand15 97 +#define FRAME_stand16 98 +#define FRAME_stand17 99 +#define FRAME_stand18 100 +#define FRAME_stand19 101 +#define FRAME_stand20 102 +#define FRAME_stand21 103 +#define FRAME_stand22 104 +#define FRAME_stand23 105 +#define FRAME_stand24 106 +#define FRAME_stand25 107 +#define FRAME_stand26 108 +#define FRAME_stand27 109 +#define FRAME_stand28 110 +#define FRAME_stand29 111 +#define FRAME_stand30 112 +#define FRAME_stand31 113 +#define FRAME_stand32 114 +#define FRAME_stand33 115 +#define FRAME_stand34 116 +#define FRAME_stand35 117 +#define FRAME_jump01 118 +#define FRAME_jump02 119 +#define FRAME_jump03 120 +#define FRAME_jump04 121 +#define FRAME_jump05 122 +#define FRAME_jump06 123 +#define FRAME_jump07 124 +#define FRAME_jump08 125 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/rotfish/fish.c b/src/game/monster/rotfish/fish.c new file mode 100644 index 000000000..faf5cf999 --- /dev/null +++ b/src/game/monster/rotfish/fish.c @@ -0,0 +1,332 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "fish.h" + +static int sound_search; +static int sound_death; +static int sound_melee; + +// Stand +static mframe_t fish_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}, +}; +mmove_t fish_move_stand = +{ + FRAME_swim1, + FRAME_swim18, + fish_frames_stand, + NULL +}; + +void +fish_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &fish_move_stand; +} + +// Run +static mframe_t fish_frames_run [] = +{ + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + + {ai_run, 12, NULL}, + {ai_run, 12, NULL} +}; +mmove_t fish_move_run = +{ + FRAME_swim1, + FRAME_swim18, + fish_frames_run, + NULL +}; + +void +fish_run(edict_t *self) +{ + self->monsterinfo.currentmove = &fish_move_run; +} + +static void +fish_bite_step(edict_t *self) +{ + vec3_t dir; + static vec3_t aim = {100, 0, 0}; + int damage; + + if (!self->enemy) + return; + VectorSubtract(self->s.origin, self->enemy->s.origin, dir); + + // decino: In Q1 it's 60 units, but it will never hit anything in Q2 for some reason + if (VectorLength(dir) > 100) + return; + damage = (random() + random() + random()) * 3; + + if (fire_hit(self, aim, damage, damage)) + gi.sound(self, CHAN_WEAPON, sound_melee, 1, ATTN_NORM, 0); +} + +// Melee +static mframe_t fish_frames_melee [] = +{ + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 0, fish_bite_step}, + {ai_run, 10, NULL}, + + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + + {ai_run, 0, fish_bite_step}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + + {ai_run, 10, NULL}, + {ai_run, 10, NULL}, + {ai_run, 0, fish_bite_step}, + {ai_run, 10, NULL}, + + {ai_run, 10, NULL}, + {ai_run, 10, NULL} +}; +mmove_t fish_move_melee = +{ + FRAME_attack1, + FRAME_attack18, + fish_frames_melee, + fish_run +}; + +void +fish_melee(edict_t *self) +{ + self->monsterinfo.currentmove = &fish_move_melee; +} + +// Search +void +fish_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +void +fish_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); +} + +// Death +static mframe_t fish_frames_death [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t fish_move_death = +{ + FRAME_death1, + FRAME_death21, + fish_frames_death, + fish_dead +}; + +void +fish_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 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; + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &fish_move_death; +} + +// Pain +static mframe_t fish_frames_pain [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t fish_move_pain = +{ + FRAME_pain1, + FRAME_pain9, + fish_frames_pain, + fish_run +}; + +void +fish_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (level.time < self->pain_debounce_time) + return; + self->pain_debounce_time = level.time + 3; + + // decino: No pain animations in Nightmare mode + if (skill->value == SKILL_HARDPLUS) + return; + self->monsterinfo.currentmove = &fish_move_pain; +} + +/* + * QUAKED monster_rotfish (1 .5 0) (-16, -16, -24) (16, 16, 24) Ambush Trigger_Spawn Sight + */ +void +SP_monster_rotfish(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/rotfish/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 24); + self->health = 25; + + sound_search = gi.soundindex("fish/idle.wav"); + sound_death = gi.soundindex("fish/death.wav"); + sound_melee = gi.soundindex("fish/bite.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -25; + self->mass = 25; + + self->monsterinfo.stand = fish_stand; + self->monsterinfo.walk = fish_run; + self->monsterinfo.run = fish_run; + self->monsterinfo.melee = fish_melee; + self->monsterinfo.search = fish_search; + + self->pain = fish_pain; + self->die = fish_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + flymonster_start(self); +} diff --git a/src/game/monster/rotfish/fish.h b/src/game/monster/rotfish/fish.h new file mode 100644 index 000000000..b1e666378 --- /dev/null +++ b/src/game/monster/rotfish/fish.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Fish animations. + * + * ======================================================================= + */ + +#define FRAME_attack1 0 +#define FRAME_attack2 1 +#define FRAME_attack3 2 +#define FRAME_attack4 3 +#define FRAME_attack5 4 +#define FRAME_attack6 5 +#define FRAME_attack7 6 +#define FRAME_attack8 7 +#define FRAME_attack9 8 +#define FRAME_attack10 9 +#define FRAME_attack11 10 +#define FRAME_attack12 11 +#define FRAME_attack13 12 +#define FRAME_attack14 13 +#define FRAME_attack15 14 +#define FRAME_attack16 15 +#define FRAME_attack17 16 +#define FRAME_attack18 17 +#define FRAME_death1 18 +#define FRAME_death2 19 +#define FRAME_death3 20 +#define FRAME_death4 21 +#define FRAME_death5 22 +#define FRAME_death6 23 +#define FRAME_death7 24 +#define FRAME_death8 25 +#define FRAME_death9 26 +#define FRAME_death10 27 +#define FRAME_death11 28 +#define FRAME_death12 29 +#define FRAME_death13 30 +#define FRAME_death14 31 +#define FRAME_death15 32 +#define FRAME_death16 33 +#define FRAME_death17 34 +#define FRAME_death18 35 +#define FRAME_death19 36 +#define FRAME_death20 37 +#define FRAME_death21 38 +#define FRAME_swim1 39 +#define FRAME_swim2 40 +#define FRAME_swim3 41 +#define FRAME_swim4 42 +#define FRAME_swim5 43 +#define FRAME_swim6 44 +#define FRAME_swim7 45 +#define FRAME_swim8 46 +#define FRAME_swim9 47 +#define FRAME_swim10 48 +#define FRAME_swim11 49 +#define FRAME_swim12 50 +#define FRAME_swim13 51 +#define FRAME_swim14 52 +#define FRAME_swim15 53 +#define FRAME_swim16 54 +#define FRAME_swim17 55 +#define FRAME_swim18 56 +#define FRAME_pain1 57 +#define FRAME_pain2 58 +#define FRAME_pain3 59 +#define FRAME_pain4 60 +#define FRAME_pain5 61 +#define FRAME_pain6 62 +#define FRAME_pain7 63 +#define FRAME_pain8 64 +#define FRAME_pain9 65 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/shalrath/shalrath.c b/src/game/monster/shalrath/shalrath.c new file mode 100644 index 000000000..8de7f131e --- /dev/null +++ b/src/game/monster/shalrath/shalrath.c @@ -0,0 +1,378 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "shalrath.h" + +static int sound_death; +static int sound_search; +static int sound_pain; +static int sound_attack; +static int sound_fire; +static int sound_sight; + +// Stand +static mframe_t shalrath_frames_stand [] = +{ + {ai_stand, 0, NULL}, +}; + +mmove_t shalrath_move_stand = +{ + FRAME_attack1, + FRAME_attack1, + shalrath_frames_stand, + NULL +}; + +void +shalrath_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &shalrath_move_stand; +} + +// Run +static mframe_t shalrath_frames_run [] = +{ + {ai_run, 6, NULL}, + {ai_run, 4, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 5, NULL}, + {ai_run, 6, NULL}, + + {ai_run, 5, NULL}, + {ai_run, 0, NULL}, + {ai_run, 4, NULL}, + {ai_run, 5, NULL} +}; +mmove_t shalrath_move_run = +{ + FRAME_walk1, + FRAME_walk12, + shalrath_frames_run, + NULL +}; + +void +shalrath_run(edict_t *self) +{ + self->monsterinfo.currentmove = &shalrath_move_run; +} + +static void +shalrath_roar(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_attack, 1, ATTN_NORM, 0); +} + +void +shalrath_pod_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + if (strcmp(other->classname, "monster_zombie") == 0) // decino: According to shalrath.qc + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 110, 0, 0, 0); + T_RadiusDamage(self, self->owner, self->dmg, NULL, self->dmg + 40, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_ROCKET_EXPLOSION); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PVS); + + G_FreeEdict(self); +} + +void +shalrath_pod_home(edict_t *self) +{ + static qboolean think = false; + vec3_t end; + vec3_t dir; + + // decino: Only home every 0.2 frames + if (think) + { + if (self->enemy && self->enemy->health < 1 && self->owner->health > 0) + { + // decino: Not a big fan of this, just wait until it hits something + /*if (!self->owner->enemy || (self->owner->enemy == self->owner)) + { + G_FreeEdict(self); + return; + }*/ + } + if (self->owner->enemy) + { + self->enemy = self->owner->enemy; + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, self->s.origin, dir); + VectorNormalize(dir); + VectorScale(dir, (skill->value >= SKILL_HARDPLUS) ? 350 : 250, self->velocity); + } + } + + // decino: Draw particles each frame + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(15); + gi.WritePosition(self->s.origin); + gi.WriteDir(vec3_origin); + gi.WriteByte(255); + gi.multicast(self->s.origin, MULTICAST_PVS); + + self->nextthink = level.time + 0.1; + self->think = shalrath_pod_home; + think = !think; +} + +static void +fire_shalrath_pod(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed) +{ + edict_t *pod; + + if (!self) + { + return; + } + + pod = G_Spawn(); + VectorCopy(start, pod->s.origin); + VectorCopy(start, pod->s.old_origin); + vectoangles(dir, pod->s.angles); + VectorScale(dir, speed, pod->velocity); + + VectorSet(pod->avelocity, 300, 300, 300); + pod->movetype = MOVETYPE_FLYMISSILE; + pod->clipmask = MASK_SHOT; + pod->solid = SOLID_BBOX; + VectorClear(pod->mins); + VectorClear(pod->maxs); + pod->s.modelindex = gi.modelindex("models/proj/pod/tris.md2"); + pod->owner = self; + pod->touch = shalrath_pod_touch; + pod->nextthink = level.time + 0.1; + pod->think = shalrath_pod_home; + pod->dmg = damage; + pod->classname = "shalrath_pod"; + pod->enemy = self->enemy; + + gi.linkentity(pod); + gi.sound(self, CHAN_WEAPON, sound_fire, 1, ATTN_NORM, 0); +} + +static void +FireShalrathPod(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + vec3_t offset = {16, 0, 16}; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, offset, forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + + fire_shalrath_pod(self, start, dir, 40, 400); +} + +// Attack +static mframe_t shalrath_frames_attack [] = +{ + {ai_charge, 0, shalrath_roar}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, FireShalrathPod}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; +mmove_t shalrath_move_attack = +{ + FRAME_attack1, + FRAME_attack11, + shalrath_frames_attack, + shalrath_run +}; + +void +shalrath_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &shalrath_move_attack; +} + +// Pain +static mframe_t shalrath_frames_pain [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t shalrath_move_pain = +{ + FRAME_pain1, + FRAME_pain5, + shalrath_frames_pain, + shalrath_run +}; + +void +shalrath_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + // decino: No pain animations in Nightmare mode + if (skill->value == SKILL_HARDPLUS) + return; + if (level.time < self->pain_debounce_time) + return; + self->monsterinfo.currentmove = &shalrath_move_pain; + self->pain_debounce_time = level.time + 3.0; + + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); +} + +void +shalrath_dead(edict_t *self) +{ + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +// Death +static mframe_t shalrath_frames_death [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t shalrath_move_death = +{ + FRAME_death1, + FRAME_death7, + shalrath_frames_death, + shalrath_dead +}; + +void +shalrath_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 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; + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &shalrath_move_death; +} + +// Sight +void +shalrath_sight(edict_t *self, edict_t *other /* unused */) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +// Search +void +shalrath_search(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); +} + +/* + * QUAKED monster_shalrath (1 .5 0) (-32, -32, -24) (32, 32, 48) Ambush Trigger_Spawn Sight + */ +void +SP_monster_shalrath(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/shalrath/tris.md2"); + VectorSet(self->mins, -32, -32, -24); + VectorSet(self->maxs, 32, 32, 48); + self->health = 400; + + sound_death = gi.soundindex("shalrath/death.wav"); + sound_search = gi.soundindex("shalrath/idle.wav"); + sound_pain = gi.soundindex("shalrath/pain.wav"); + sound_attack = gi.soundindex("shalrath/attack.wav"); + sound_fire = gi.soundindex("shalrath/attack2.wav"); + sound_sight = gi.soundindex("shalrath/sight.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -90; + self->mass = 400; + + self->monsterinfo.stand = shalrath_stand; + self->monsterinfo.walk = shalrath_run; + self->monsterinfo.run = shalrath_run; + self->monsterinfo.attack = shalrath_attack; + self->monsterinfo.sight = shalrath_sight; + self->monsterinfo.search = shalrath_search; + + self->pain = shalrath_pain; + self->die = shalrath_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/shalrath/shalrath.h b/src/game/monster/shalrath/shalrath.h new file mode 100644 index 000000000..14197cd27 --- /dev/null +++ b/src/game/monster/shalrath/shalrath.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Shalrath animations. + * + * ======================================================================= + */ + +#define FRAME_attack1 0 +#define FRAME_attack2 1 +#define FRAME_attack3 2 +#define FRAME_attack4 3 +#define FRAME_attack5 4 +#define FRAME_attack6 5 +#define FRAME_attack7 6 +#define FRAME_attack8 7 +#define FRAME_attack9 8 +#define FRAME_attack10 9 +#define FRAME_attack11 10 +#define FRAME_pain1 11 +#define FRAME_pain2 12 +#define FRAME_pain3 13 +#define FRAME_pain4 14 +#define FRAME_pain5 15 +#define FRAME_death1 16 +#define FRAME_death2 17 +#define FRAME_death3 18 +#define FRAME_death4 19 +#define FRAME_death5 20 +#define FRAME_death6 21 +#define FRAME_death7 22 +#define FRAME_walk1 23 +#define FRAME_walk2 24 +#define FRAME_walk3 25 +#define FRAME_walk4 26 +#define FRAME_walk5 27 +#define FRAME_walk6 28 +#define FRAME_walk7 29 +#define FRAME_walk8 30 +#define FRAME_walk9 31 +#define FRAME_walk10 32 +#define FRAME_walk11 33 +#define FRAME_walk12 34 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/shambler/shambler.c b/src/game/monster/shambler/shambler.c new file mode 100644 index 000000000..fe1bbb9b9 --- /dev/null +++ b/src/game/monster/shambler/shambler.c @@ -0,0 +1,757 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * SHAMBLER + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "shambler.h" + +static int sound_pain; +static int sound_idle; +static int sound_die; +static int sound_sight; +static int sound_windup; +static int sound_melee1; +static int sound_melee2; +static int sound_smack; +static int sound_boom; + +// +// misc +// + +void +shambler_sight(edict_t* self, edict_t* other) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +static vec3_t lightning_left_hand[] = { + { 44, 36, 25}, + { 10, 44, 57}, + { -1, 40, 70}, + { -10, 34, 75}, + { 7.4f, 24, 89 } +}; + +static vec3_t lightning_right_hand[] = { + { 28, -38, 25}, + { 31, -7, 70}, + { 20, 0, 80}, + { 16, 1.2f, 81}, + { 27, -11, 83 } +}; + +void +shambler_lightning_update(edict_t *self) +{ + edict_t *lightning; + vec3_t f, r; + + if (self->s.frame >= FRAME_magic01 + sizeof(lightning_left_hand) / sizeof(*lightning_left_hand)) + { + return; + } + + lightning = G_Spawn(); + lightning->s.modelindex = gi.modelindex("models/proj/lightning/tris.md2"); + lightning->s.renderfx |= RF_BEAM; + lightning->owner = self; + + AngleVectors(self->s.angles, f, r, NULL); + G_ProjectSource(self->s.origin, lightning_left_hand[self->s.frame - FRAME_magic01], f, r, lightning->s.origin); + G_ProjectSource(self->s.origin, lightning_right_hand[self->s.frame - FRAME_magic01], f, r, lightning->s.old_origin); + gi.linkentity(lightning); +} + +void +shambler_windup(edict_t* self) +{ + gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); + + shambler_lightning_update(self); +} + +void +shambler_idle(edict_t* self) +{ + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +void +shambler_maybe_idle(edict_t* self) +{ + if (random() > 0.8) + { + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); + } +} + +// +// stand +// + +static mframe_t shambler_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} +}; + +mmove_t shambler_move_stand = +{ + FRAME_stand01, + FRAME_stand17, + shambler_frames_stand, + NULL +}; + +void +shambler_stand(edict_t* self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &shambler_move_stand; +} + +// +// walk +// + +void shambler_walk(edict_t* self); + +static mframe_t shambler_frames_walk[] = +{ + {ai_walk, 10, NULL}, /* FIXME: add footsteps? */ + {ai_walk, 9, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 12, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 3, NULL}, + {ai_walk, 13, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 7, shambler_maybe_idle}, + {ai_walk, 5, NULL}, +}; + +mmove_t shambler_move_walk = +{ + FRAME_walk01, + FRAME_walk12, + shambler_frames_walk, + NULL +}; + +void +shambler_walk(edict_t* self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &shambler_move_walk; +} + +// +// run +// + +void shambler_run(edict_t* self); + +static mframe_t shambler_frames_run[] = +{ + {ai_run, 20, NULL}, /* FIXME: add footsteps? */ + {ai_run, 24, NULL}, + {ai_run, 20, NULL}, + {ai_run, 20, NULL}, + {ai_run, 24, NULL}, + {ai_run, 20, shambler_maybe_idle}, +}; + +mmove_t shambler_move_run = +{ + FRAME_run01, + FRAME_run06, + shambler_frames_run, + NULL +}; + +void +shambler_run(edict_t* self) +{ + if (!self) + { + return; + } + + if (self->enemy && self->enemy->client) + { + self->monsterinfo.aiflags |= AI_BRUTAL; + } + else + { + self->monsterinfo.aiflags &= ~AI_BRUTAL; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &shambler_move_stand; + return; + } + + self->monsterinfo.currentmove = &shambler_move_run; +} + +// +// pain +// + +// FIXME: needs halved explosion damage + +static mframe_t shambler_frames_pain[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, +}; + +mmove_t shambler_move_pain = +{ + FRAME_pain01, + FRAME_pain06, + shambler_frames_pain, + shambler_run +}; + +void +shambler_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) +{ + if (!self) + { + return; + } + + if (level.time < self->timestamp) + { + return; + } + + self->timestamp = level.time + (1.0f / 1000.0f); + gi.sound(self, CHAN_AUTO, sound_pain, 1, ATTN_NORM, 0); + + if (damage <= 30 && random() > 0.2f) + { + return; + } + + /* If hard or nightmare, don't go into pain while attacking */ + if (skill->value >= SKILL_HARDPLUS) + { + if ((self->s.frame >= FRAME_smash01) && (self->s.frame <= FRAME_smash12)) + return; + + if ((self->s.frame >= FRAME_swingl01) && (self->s.frame <= FRAME_swingl09)) + return; + + if ((self->s.frame >= FRAME_swingr01) && (self->s.frame <= FRAME_swingr09)) + return; + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 2; + self->monsterinfo.currentmove = &shambler_move_pain; +} + +/* + * attacks + */ + +static void +ShamblerSaveLoc(edict_t* self) +{ + /* save for aiming the shot */ + VectorCopy(self->enemy->s.origin, self->pos1); + self->pos1[2] += self->enemy->viewheight; + self->monsterinfo.nextframe = FRAME_magic09; + + gi.sound(self, CHAN_WEAPON, sound_boom, 1, ATTN_NORM, 0); + shambler_lightning_update(self); +} + +static void +ShamblerCastLightning(edict_t* self) +{ + vec3_t start, dir, end; + trace_t tr; + + if (!self->enemy) + { + return; + } + + if (!infront(self, self->enemy)) + { + return; + } + + if (self->s.frame == FRAME_magic07) + { + gi.sound(self, CHAN_WEAPON, sound_boom, 1, ATTN_NORM, 0); + } + + /* decino: Shambler has an extra lightning frame on Nightmare mode */ + if ((self->s.frame == FRAME_magic10) && (skill->value < SKILL_HARDPLUS)) + { + return; + } + + VectorCopy(self->s.origin, start); + VectorCopy(self->enemy->s.origin, end); + + start[2] += 40; + end[2] += 16; + + VectorSubtract(end, start, dir); + VectorNormalize(dir); + VectorMA(start, 600, dir, end); + + tr = gi.trace(start, NULL, NULL, end, self, + (MASK_SHOT | CONTENTS_SLIME | CONTENTS_LAVA | CONTENTS_WATER)); + + if (!tr.ent) + { + return; + } + + T_Damage(tr.ent, self, self, dir, tr.endpos, tr.plane.normal, 10, 1, 0, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_LIGHTNING); + gi.WriteShort(tr.ent - g_edicts); + gi.WriteShort(self - g_edicts); + gi.WritePosition(end); + gi.WritePosition(start); + gi.multicast(start, MULTICAST_PVS); +} + +static mframe_t shambler_frames_magic[] = { + {ai_charge, 0, shambler_windup}, + {ai_charge, 0, shambler_lightning_update}, + {ai_charge, 0, shambler_lightning_update}, + {ai_move, 0, shambler_lightning_update}, + {ai_move, 0, shambler_lightning_update}, + {ai_move, 0, ShamblerSaveLoc}, + {ai_move, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_move, 0, ShamblerCastLightning}, + {ai_move, 0, ShamblerCastLightning}, + {ai_move, 0, ShamblerCastLightning}, + {ai_move, 0, NULL}, +}; + +mmove_t shambler_attack_magic = +{ + FRAME_magic01, + FRAME_magic12, + shambler_frames_magic, + shambler_run +}; + +void +shambler_attack(edict_t* self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &shambler_attack_magic; +} + +// +// melee +// + +void +shambler_melee1(edict_t* self) +{ + gi.sound(self, CHAN_WEAPON, sound_melee1, 1, ATTN_NORM, 0); +} + +void +shambler_melee2(edict_t* self) +{ + gi.sound(self, CHAN_WEAPON, sound_melee2, 1, ATTN_NORM, 0); +} + +static void sham_swingl9_step(edict_t* self); +static void sham_swingr9_step(edict_t* self); + +static void +sham_smash10_step(edict_t* self) +{ + if (!self->enemy) + return; + + ai_charge(self, 0); + + if (!CanDamage(self->enemy, self)) + return; + + vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 }; + if (fire_hit(self, aim, (110 + (randk() % 10)), 120)) + { + /* Slower attack */ + gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0); + } +}; + +static void +ShamClaw(edict_t* self) +{ + if (!self->enemy) + return; + + ai_charge(self, 10); + + if (!CanDamage(self->enemy, self)) + return; + + vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 }; + if (fire_hit(self, aim, (70 + (randk() % 10)), 80)) + { + /* Slower attack */ + gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0); + } +}; + +static mframe_t shambler_frames_smash[] = { + {ai_charge, 2, shambler_melee1}, + {ai_charge, 6}, + {ai_charge, 6}, + {ai_charge, 5}, + {ai_charge, 4}, + {ai_charge, 1}, + {ai_charge, 0}, + {ai_charge, 0}, + {ai_charge, 0}, + {ai_charge, 0, sham_smash10_step}, + {ai_charge, 5}, + {ai_charge, 4}, +}; + +mmove_t shambler_attack_smash = +{ + FRAME_smash01, + FRAME_smash12, + shambler_frames_smash, + shambler_run +}; + +static mframe_t shambler_frames_swingl[] = { + {ai_charge, 5, shambler_melee1}, + {ai_charge, 3}, + {ai_charge, 7}, + {ai_charge, 3}, + {ai_charge, 7}, + {ai_charge, 9}, + {ai_charge, 5, ShamClaw}, + {ai_charge, 4}, + {ai_charge, 8, sham_swingl9_step}, +}; + +mmove_t shambler_attack_swingl = +{ + FRAME_swingl01, + FRAME_swingl09, + shambler_frames_swingl, + shambler_run +}; + +static mframe_t shambler_frames_swingr[] = { + {ai_charge, 1, shambler_melee2}, + {ai_charge, 8}, + {ai_charge, 14}, + {ai_charge, 7}, + {ai_charge, 3}, + {ai_charge, 6}, + {ai_charge, 6, ShamClaw}, + {ai_charge, 3}, + {ai_charge, 8, sham_swingr9_step}, +}; + +mmove_t shambler_attack_swingr = +{ + FRAME_swingr01, + FRAME_swingr09, + shambler_frames_swingr, + shambler_run +}; + +static void +sham_swingl9_step(edict_t* self) +{ + if (!self) + { + return; + } + + ai_charge(self, 8); + + if (self->enemy) + { + float real_enemy_range; + + real_enemy_range = realrange(self, self->enemy); + + if ((randk() % 2) && self->enemy && real_enemy_range < MELEE_DISTANCE) + { + self->monsterinfo.currentmove = &shambler_attack_swingr; + } + } +} + +static void +sham_swingr9_step(edict_t* self) +{ + if (!self) + { + return; + } + + ai_charge(self, 1); + ai_charge(self, 10); + + if (self->enemy) + { + float real_enemy_range; + + real_enemy_range = realrange(self, self->enemy); + + if ((randk() % 2) && self->enemy && real_enemy_range < MELEE_DISTANCE) + { + self->monsterinfo.currentmove = &shambler_attack_swingl; + } + } +} + +void +shambler_melee(edict_t* self) +{ + float chance = random(); + if (chance > 0.6 || self->health == 600) + { + self->monsterinfo.currentmove = &shambler_attack_smash; + } + else if (chance > 0.3) + { + self->monsterinfo.currentmove = &shambler_attack_swingl; + } + else + { + self->monsterinfo.currentmove = &shambler_attack_swingr; + } +} + +// +// death +// + +void +shambler_dead(edict_t* self) +{ + if (!self) + { + return; + } + + 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); +} + +static void +shambler_shrink(edict_t* self) +{ + self->maxs[2] = 0; + self->svflags |= SVF_DEADMONSTER; + gi.linkentity(self); +} + +static mframe_t shambler_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, shambler_shrink}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* FIXME: thud? */ +}; + +mmove_t shambler_move_death = +{ + FRAME_death01, + FRAME_death11, + shambler_frames_death, + shambler_dead +}; + +void +shambler_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage, vec3_t point /* unused */) +{ + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + /* FIXME: better gibs for shambler, shambler head */ + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", + damage, GIB_ORGANIC); + ThrowGib(self, "models/objects/gibs/chest/tris.md2", + damage, GIB_ORGANIC); + + ThrowHead(self, "models/objects/gibs/head2/tris.md2", + damage, GIB_ORGANIC); + + self->deadflag = true; + return; + } + + if (self->deadflag) + { + return; + } + + /* regular death */ + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = true; + self->takedamage = true; + + self->monsterinfo.currentmove = &shambler_move_death; +} + +/* + * QUAKED monster_shambler (1 .5 0) (-32, -32, -24) (32, 32, 64) Ambush Trigger_Spawn Sight + */ +void +SP_monster_shambler(edict_t* self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->s.modelindex = gi.modelindex("models/monsters/shambler/tris.md2"); + VectorSet (self->mins, -32, -32, -24); + VectorSet (self->maxs, 32, 32, 64); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + gi.modelindex("models/proj/lightning/tris.md2"); + sound_pain = gi.soundindex("shambler/shurt2.wav"); + sound_idle = gi.soundindex("shambler/sidle.wav"); + sound_die = gi.soundindex("shambler/sdeath.wav"); + sound_windup = gi.soundindex("shambler/sattck1.wav"); + sound_melee1 = gi.soundindex("shambler/melee1.wav"); + sound_melee2 = gi.soundindex("shambler/melee2.wav"); + sound_sight = gi.soundindex("shambler/ssight.wav"); + sound_smack = gi.soundindex("shambler/smack.wav"); + sound_boom = gi.soundindex("shambler/sboom.wav"); + + self->health = 600; + self->gib_health = -60; + + self->mass = 500; + + self->pain = shambler_pain; + self->die = shambler_die; + self->monsterinfo.stand = shambler_stand; + self->monsterinfo.walk = shambler_walk; + self->monsterinfo.run = shambler_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = shambler_attack; + self->monsterinfo.melee = shambler_melee; + self->monsterinfo.sight = shambler_sight; + self->monsterinfo.idle = shambler_idle; + self->monsterinfo.blocked = NULL; + + gi.linkentity(self); + + if (self->spawnflags & 1) + { + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + } + + self->monsterinfo.currentmove = &shambler_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + walkmonster_start(self); +} diff --git a/src/game/monster/shambler/shambler.h b/src/game/monster/shambler/shambler.h new file mode 100644 index 000000000..65a2f7548 --- /dev/null +++ b/src/game/monster/shambler/shambler.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Shambler animations. + * + * ======================================================================= + */ + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_walk01 18 +#define FRAME_walk02 19 +#define FRAME_walk03 20 +#define FRAME_walk04 21 +#define FRAME_walk05 22 +#define FRAME_walk06 23 +#define FRAME_walk07 24 +#define FRAME_walk08 25 +#define FRAME_walk09 26 +#define FRAME_walk10 27 +#define FRAME_walk11 28 +#define FRAME_walk12 29 +#define FRAME_run01 30 +#define FRAME_run02 31 +#define FRAME_run03 32 +#define FRAME_run04 33 +#define FRAME_run05 34 +#define FRAME_run06 35 +#define FRAME_smash01 36 +#define FRAME_smash02 37 +#define FRAME_smash03 38 +#define FRAME_smash04 39 +#define FRAME_smash05 40 +#define FRAME_smash06 41 +#define FRAME_smash07 42 +#define FRAME_smash08 43 +#define FRAME_smash09 44 +#define FRAME_smash10 45 +#define FRAME_smash11 46 +#define FRAME_smash12 47 +#define FRAME_swingr01 48 +#define FRAME_swingr02 49 +#define FRAME_swingr03 50 +#define FRAME_swingr04 51 +#define FRAME_swingr05 52 +#define FRAME_swingr06 53 +#define FRAME_swingr07 54 +#define FRAME_swingr08 55 +#define FRAME_swingr09 56 +#define FRAME_swingl01 57 +#define FRAME_swingl02 58 +#define FRAME_swingl03 59 +#define FRAME_swingl04 60 +#define FRAME_swingl05 61 +#define FRAME_swingl06 62 +#define FRAME_swingl07 63 +#define FRAME_swingl08 64 +#define FRAME_swingl09 65 +#define FRAME_magic01 66 +#define FRAME_magic02 67 +#define FRAME_magic03 68 +#define FRAME_magic04 69 +#define FRAME_magic05 70 +#define FRAME_magic06 71 +#define FRAME_magic07 72 +#define FRAME_magic08 73 +#define FRAME_magic09 74 +#define FRAME_magic10 75 +#define FRAME_magic11 76 +#define FRAME_magic12 77 +#define FRAME_pain01 78 +#define FRAME_pain02 79 +#define FRAME_pain03 80 +#define FRAME_pain04 81 +#define FRAME_pain05 82 +#define FRAME_pain06 83 +#define FRAME_death01 84 +#define FRAME_death02 85 +#define FRAME_death03 86 +#define FRAME_death04 87 +#define FRAME_death05 88 +#define FRAME_death06 89 +#define FRAME_death07 90 +#define FRAME_death08 91 +#define FRAME_death09 92 +#define FRAME_death10 93 +#define FRAME_death11 94 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/soldier/soldier.c b/src/game/monster/soldier/soldier.c new file mode 100644 index 000000000..07f898807 --- /dev/null +++ b/src/game/monster/soldier/soldier.c @@ -0,0 +1,3891 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Soldier aka "Guard". This is the most complex enemy in Quake 2, since + * it uses all AI features (dodging, sight, crouching, etc) and comes + * in a myriad of variants. + * In Rogue it's even more complex due to the blindfire stuff. + * In Xatrix it's even more complex due to another 4 variants added. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "soldier.h" +#include "soldierh.h" + +static int sound_idle; +static int sound_sight1; +static int sound_sight2; +static int sound_pain_light; +static int sound_pain; +static int sound_pain_ss; +static int sound_death_light; +static int sound_death; +static int sound_death_ss; +static int sound_cock; +static int sound_step; +static int sound_step2; +static int sound_step3; +static int sound_step4; + +void soldier_duck_up(edict_t *self); +void soldier_stand(edict_t *self); +void soldier_run(edict_t *self); +void soldier_fire(edict_t *self, int); +void soldier_blind(edict_t *self); +void soldierh_stand(edict_t *self); +void soldierh_run(edict_t *self); +extern void brain_dabeam(edict_t *self); + +void +soldier_footstep(edict_t *self) +{ + if (!g_monsterfootsteps->value) + return; + + // Lazy loading for savegame compatibility. + if (sound_step == 0 || sound_step2 == 0 || sound_step3 == 0 || sound_step4 == 0) + { + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); + } + + int i; + i = randk() % 4; + + if (i == 0) + { + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); + } + else if (i == 1) + { + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + } + else if (i == 2) + { + gi.sound(self, CHAN_BODY, sound_step3, 1, ATTN_NORM, 0); + } + else if (i == 3) + { + gi.sound(self, CHAN_BODY, sound_step4, 1, ATTN_NORM, 0); + } +} + +void +soldier_start_charge(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags |= AI_CHARGING; +} + +void +soldier_stop_charge(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_CHARGING; +} + +void +soldier_idle(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() > 0.8) + { + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); + } +} + +void +soldier_cock(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.frame == FRAME_stand322) + { + gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0); + } +} + +static mframe_t soldier_frames_stand1[] = { + {ai_stand, 0, soldier_idle}, + {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 soldier_move_stand1 = +{ + FRAME_stand101, + FRAME_stand130, + soldier_frames_stand1, + soldier_stand +}; + +static mframe_t soldier_frames_stand3[] = { + {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, soldier_cock}, + {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 soldier_move_stand3 = +{ + FRAME_stand301, + FRAME_stand339, + soldier_frames_stand3, + soldier_stand +}; + +void +soldier_stand(edict_t *self) +{ + if (!self) + { + return; + } + + if ((self->monsterinfo.currentmove == &soldier_move_stand3) || + (random() < 0.8)) + { + self->monsterinfo.currentmove = &soldier_move_stand1; + } + else + { + self->monsterinfo.currentmove = &soldier_move_stand3; + } +} + +void +soldier_walk1_random(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() > 0.1) + { + self->monsterinfo.nextframe = FRAME_walk101; + } +} + +static mframe_t soldier_frames_walk1[] = { + {ai_walk, 3, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 2, soldier_footstep}, + {ai_walk, 2, NULL}, + {ai_walk, 1, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 3, soldier_footstep}, + {ai_walk, -1, soldier_walk1_random}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL} +}; + +mmove_t soldier_move_walk1 = +{ + FRAME_walk101, + FRAME_walk133, + soldier_frames_walk1, + NULL +}; + +static mframe_t soldier_frames_walk2[] = { + {ai_walk, 4, soldier_footstep}, + {ai_walk, 4, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 5, soldier_footstep}, + {ai_walk, 1, NULL}, + {ai_walk, 3, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 7, NULL} +}; + +mmove_t soldier_move_walk2 = +{ + FRAME_walk209, + FRAME_walk218, + soldier_frames_walk2, + NULL +}; + +void +soldier_walk(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &soldier_move_walk1; + } + else + { + self->monsterinfo.currentmove = &soldier_move_walk2; + } +} + +static mframe_t soldier_frames_start_run[] = { + {ai_run, 7, NULL}, + {ai_run, 5, NULL} +}; + +mmove_t soldier_move_start_run = +{ + FRAME_run01, + FRAME_run02, + soldier_frames_start_run, + soldier_run +}; + +void +soldier_fire_run(edict_t *self) +{ + if (!self) + { + return; + } + + if ((self->s.skinnum <= 1) && (self->enemy) && visible(self, self->enemy)) + { + soldier_fire(self, 0); + } +} + +static mframe_t soldier_frames_run[] = { + {ai_run, 10, NULL}, + {ai_run, 11, soldier_footstep}, + {ai_run, 11, NULL}, + {ai_run, 16, NULL}, + {ai_run, 10, soldier_footstep}, + {ai_run, 15, NULL} +}; + +mmove_t soldier_move_run = +{ + FRAME_run03, + FRAME_run08, + soldier_frames_run, + NULL +}; + +void +soldier_run(edict_t *self) +{ + if (!self) + { + return; + } + + monster_done_dodge(self); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &soldier_move_stand1; + return; + } + + if ((self->monsterinfo.currentmove == &soldier_move_walk1) || + (self->monsterinfo.currentmove == &soldier_move_walk2) || + (self->monsterinfo.currentmove == &soldier_move_start_run)) + { + self->monsterinfo.currentmove = &soldier_move_run; + } + else + { + self->monsterinfo.currentmove = &soldier_move_start_run; + } +} + +static mframe_t soldier_frames_pain1[] = { + {ai_move, -3, NULL}, + {ai_move, 4, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldier_move_pain1 = +{ + FRAME_pain101, + FRAME_pain105, + soldier_frames_pain1, + soldier_run +}; + +static mframe_t soldier_frames_pain2[] = { + {ai_move, -13, NULL}, + {ai_move, -1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 4, NULL}, + {ai_move, 2, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL} +}; + +mmove_t soldier_move_pain2 = +{ + FRAME_pain201, + FRAME_pain207, + soldier_frames_pain2, + soldier_run +}; + +static mframe_t soldier_frames_pain3[] = { + {ai_move, -8, NULL}, + {ai_move, 10, NULL}, + {ai_move, -4, soldier_footstep}, + {ai_move, -1, NULL}, + {ai_move, -3, NULL}, + {ai_move, 0, NULL}, + {ai_move, 3, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 4, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, soldier_footstep} +}; + +mmove_t soldier_move_pain3 = +{ + FRAME_pain301, + FRAME_pain318, + soldier_frames_pain3, + soldier_run +}; + +static mframe_t soldier_frames_pain4[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -10, NULL}, + {ai_move, -6, NULL}, + {ai_move, 8, NULL}, + {ai_move, 4, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 5, NULL}, + {ai_move, 2, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldier_move_pain4 = +{ + FRAME_pain401, + FRAME_pain417, + soldier_frames_pain4, + soldier_run +}; + +void +soldier_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) +{ + float r; + int n; + + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum |= 1; + } + + monster_done_dodge(self); + soldier_stop_charge(self); + + /* if we're blind firing, this needs to be turned off here */ + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && + ((self->monsterinfo.currentmove == &soldier_move_pain1) || + (self->monsterinfo.currentmove == &soldier_move_pain2) || + (self->monsterinfo.currentmove == &soldier_move_pain3))) + { + /* clear duck flag */ + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } + + self->monsterinfo.currentmove = &soldier_move_pain4; + } + + return; + } + + self->pain_debounce_time = level.time + 3; + + n = self->s.skinnum | 1; + + if (n == 1) + { + gi.sound(self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0); + } + else if (n == 3) + { + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0); + } + + if (self->velocity[2] > 100) + { + /* clear duck flag */ + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } + + self->monsterinfo.currentmove = &soldier_move_pain4; + return; + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + r = random(); + + if (r < 0.33) + { + self->monsterinfo.currentmove = &soldier_move_pain1; + } + else if (r < 0.66) + { + self->monsterinfo.currentmove = &soldier_move_pain2; + } + else + { + self->monsterinfo.currentmove = &soldier_move_pain3; + } + + /* clear duck flag */ + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } +} + +static int blaster_flash[] = +{ + MZ2_SOLDIER_BLASTER_1, + MZ2_SOLDIER_BLASTER_2, + MZ2_SOLDIER_BLASTER_3, + MZ2_SOLDIER_BLASTER_4, + MZ2_SOLDIER_BLASTER_5, + MZ2_SOLDIER_BLASTER_6, + MZ2_SOLDIER_BLASTER_7, + MZ2_SOLDIER_BLASTER_8 +}; + +static int shotgun_flash[] = +{ + MZ2_SOLDIER_SHOTGUN_1, + MZ2_SOLDIER_SHOTGUN_2, + MZ2_SOLDIER_SHOTGUN_3, + MZ2_SOLDIER_SHOTGUN_4, + MZ2_SOLDIER_SHOTGUN_5, + MZ2_SOLDIER_SHOTGUN_6, + MZ2_SOLDIER_SHOTGUN_7, + MZ2_SOLDIER_SHOTGUN_8 +}; + +static int machinegun_flash[] = +{ + MZ2_SOLDIER_MACHINEGUN_1, + MZ2_SOLDIER_MACHINEGUN_2, + MZ2_SOLDIER_MACHINEGUN_3, + MZ2_SOLDIER_MACHINEGUN_4, + MZ2_SOLDIER_MACHINEGUN_5, + MZ2_SOLDIER_MACHINEGUN_6, + MZ2_SOLDIER_MACHINEGUN_7, + MZ2_SOLDIER_MACHINEGUN_8 +}; + +void +soldier_fire(edict_t *self, int in_flash_number) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + vec3_t dir; + vec3_t end; + float r, u; + int flash_index; + int flash_number; + + if (!self) + { + return; + } + + vec3_t aim_norm; + float angle; + trace_t tr; + vec3_t aim_good; + + if ((!self->enemy) || (!self->enemy->inuse)) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + return; + } + + if (in_flash_number < 0) + { + flash_number = -1 * in_flash_number; + } + else + { + flash_number = in_flash_number; + } + + if (self->s.skinnum < 2) + { + flash_index = blaster_flash[flash_number]; + } + else if (self->s.skinnum < 4) + { + flash_index = shotgun_flash[flash_number]; + } + else + { + flash_index = machinegun_flash[flash_number]; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_index], + forward, right, start); + + if ((flash_number == 5) || (flash_number == 6)) /* he's dead */ + { + VectorCopy(forward, aim); + } + else + { + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, aim); + VectorCopy(end, aim_good); + + if (in_flash_number < 0) + { + VectorCopy(aim, aim_norm); + VectorNormalize(aim_norm); + angle = DotProduct(aim_norm, forward); + + if (angle < 0.9) /* ~25 degree angle */ + { + return; + } + } + + vectoangles(aim, dir); + AngleVectors(dir, forward, right, up); + + if (skill->value < SKILL_HARD) + { + r = crandom() * 1000; + u = crandom() * 500; + } + else + { + r = crandom() * 500; + u = crandom() * 250; + } + + VectorMA(start, 8192, forward, end); + VectorMA(end, r, right, end); + VectorMA(end, u, up, end); + + VectorSubtract(end, start, aim); + VectorNormalize(aim); + } + + if (!((flash_number == 5) || (flash_number == 6))) /* he's dead */ + { + tr = gi.trace(start, NULL, NULL, aim_good, self, MASK_SHOT); + + if ((tr.ent != self->enemy) && (tr.ent != world)) + { + return; + } + } + + if (self->s.skinnum <= 1) + { + monster_fire_blaster(self, start, aim, 5, 600, flash_index, EF_BLASTER); + } + else if (self->s.skinnum <= 3) + { + monster_fire_shotgun(self, start, aim, 2, 1, + DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, + DEFAULT_SHOTGUN_COUNT, flash_index); + } + else + { + /* changed to wait from pausetime to not interfere with dodge code */ + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->monsterinfo.pausetime = level.time + (3 + randk() % 8) * FRAMETIME; + } + + monster_fire_bullet(self, start, aim, 2, 4, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + flash_index); + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } + } +} + +/* ATTACK1 (blaster/shotgun) */ +void +soldier_fire1(edict_t *self) +{ + if (!self) + { + return; + } + + soldier_fire(self, 0); +} + +void +soldier_attack1_refire1(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return; + } + + if (!self->enemy) + { + return; + } + + if (self->s.skinnum > 1) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak102; + } + else + { + self->monsterinfo.nextframe = FRAME_attak110; + } +} + +void +soldier_attack1_refire2(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->enemy) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak102; + } +} + +static mframe_t soldier_frames_attack1[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_fire1}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_attack1_refire1}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_cock}, + {ai_charge, 0, soldier_attack1_refire2}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldier_move_attack1 = +{ + FRAME_attak101, + FRAME_attak112, + soldier_frames_attack1, + soldier_run +}; + +/* ATTACK2 (blaster/shotgun) */ +void +soldier_fire2(edict_t *self) +{ + if (!self) + { + return; + } + + soldier_fire(self, 1); +} + +void +soldier_attack2_refire1(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->enemy) + { + return; + } + + if (self->s.skinnum > 1) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak204; + } + else + { + self->monsterinfo.nextframe = FRAME_attak216; + } +} + +void +soldier_attack2_refire2(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->enemy) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak204; + } +} + +static mframe_t soldier_frames_attack2[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_fire2}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_attack2_refire1}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_cock}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_attack2_refire2}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldier_move_attack2 = +{ + FRAME_attak201, + FRAME_attak218, + soldier_frames_attack2, + soldier_run +}; + +/* ATTACK3 (duck and shoot) */ +void +soldier_duck_down(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + return; + } + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity(self); +} + +void +soldier_duck_up(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + +void +soldier_fire3(edict_t *self) +{ + if (!self) + { + return; + } + + soldier_duck_down(self); + soldier_fire(self, 2); +} + +void +soldier_attack3_refire(edict_t *self) +{ + if (!self) + { + return; + } + + if ((level.time + 0.4) < self->monsterinfo.duck_wait_time) + { + self->monsterinfo.nextframe = FRAME_attak303; + } +} + +static mframe_t soldier_frames_attack3[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_fire3}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_attack3_refire}, + {ai_charge, 0, soldier_duck_up}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldier_move_attack3 = +{ + FRAME_attak301, + FRAME_attak309, + soldier_frames_attack3, + soldier_run +}; + +/* ATTACK4 (machinegun) */ +void +soldier_fire4(edict_t *self) +{ + if (!self) + { + return; + } + + soldier_fire(self, 3); +} + +static mframe_t soldier_frames_attack4[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_footstep}, + {ai_charge, 0, soldier_fire4}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_footstep} +}; + +mmove_t soldier_move_attack4 = +{ + FRAME_attak401, + FRAME_attak406, + soldier_frames_attack4, + soldier_run +}; + +/* ATTACK6 (run & shoot) */ +void +soldier_fire8(edict_t *self) +{ + if (!self) + { + return; + } + + soldier_fire(self, 7); +} + +void +soldier_attack6_refire(edict_t *self) +{ + if (!self) + { + return; + } + + /* make sure dodge & charge bits are cleared */ + monster_done_dodge(self); + soldier_stop_charge(self); + + if (!self->enemy) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (range(self, self->enemy) < RANGE_NEAR) + { + return; + } + + if ((skill->value == SKILL_HARDPLUS) || ((random() < (0.25 * ((float)skill->value))))) + { + self->monsterinfo.nextframe = FRAME_runs03; + } +} + +static mframe_t soldier_frames_attack6[] = { + {ai_run, 10, soldier_start_charge}, + {ai_run, 4, NULL}, + {ai_run, 12, soldier_footstep}, + {ai_run, 11, soldier_fire8}, + {ai_run, 13, monster_done_dodge}, + {ai_run, 18, NULL}, + {ai_run, 15, soldier_footstep}, + {ai_run, 14, NULL}, + {ai_run, 11, NULL}, + {ai_run, 8, soldier_footstep}, + {ai_run, 11, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, soldier_footstep}, + {ai_run, 17, soldier_attack6_refire} +}; + +mmove_t soldier_move_attack6 = +{ + FRAME_runs01, + FRAME_runs14, + soldier_frames_attack6, + soldier_run +}; + +void +soldier_attack(edict_t *self) +{ + float r, chance; + + if (!self) + { + return; + } + + monster_done_dodge(self); + + /* blindfire! */ + if (self->monsterinfo.attack_state == AS_BLIND) + { + /* setup shot probabilities */ + if (self->monsterinfo.blind_fire_delay < 1.0) + { + chance = 1.0; + } + else if (self->monsterinfo.blind_fire_delay < 7.5) + { + chance = 0.4; + } + else + { + chance = 0.1; + } + + r = random(); + + /* minimum of 2 seconds, plus 0-3, after the shots are done */ + self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + random() * 3.0; + + /* don't shoot at the origin */ + if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin)) + { + return; + } + + /* don't shoot if the dice say not to */ + if (r > chance) + { + return; + } + + /* turn on manual steering to signal both manual steering and blindfire */ + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &soldier_move_attack1; + self->monsterinfo.attack_finished = level.time + 1.5 + random(); + return; + } + + r = random(); + + if ((!(self->monsterinfo.aiflags & (AI_BLOCKED | AI_STAND_GROUND))) && + (range(self, self->enemy) >= RANGE_NEAR) && + ((r < (skill->value * 0.25)) && + (self->s.skinnum <= 3))) + { + self->monsterinfo.currentmove = &soldier_move_attack6; + } + else + { + if (self->s.skinnum < 4) + { + if (random() < 0.5) + { + self->monsterinfo.currentmove = &soldier_move_attack1; + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack2; + } + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack4; + } + } +} + +void +soldier_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); + } + + if ((skill->value > SKILL_EASY) && (self->enemy) && + (range(self, self->enemy) >= RANGE_NEAR)) + { + /* don't let machinegunners run & shoot */ + if ((random() > 0.75) && (self->s.skinnum <= 3)) + { + self->monsterinfo.currentmove = &soldier_move_attack6; + } + } +} + +void +soldier_duck_hold(edict_t *self) +{ + if (!self) + { + return; + } + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +static mframe_t soldier_frames_duck[] = { + {ai_move, 5, soldier_duck_down}, + {ai_move, -1, soldier_duck_hold}, + {ai_move, 1, NULL}, + {ai_move, 0, soldier_duck_up}, + {ai_move, 5, NULL} +}; + +mmove_t soldier_move_duck = +{ + FRAME_duck01, + FRAME_duck05, + soldier_frames_duck, + soldier_run +}; + +qboolean +soldier_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + /* don't do anything if you're dodging */ + if ((self->monsterinfo.aiflags & AI_DODGING) || + (self->monsterinfo.aiflags & AI_DUCKED)) + { + return false; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + return false; +} + +void +soldier_dodge(edict_t *self, edict_t *attacker, float eta, + trace_t *tr /* unused */) +{ + float r; + + if (!self || !attacker) + { + return; + } + + r = random(); + + if (r > 0.25) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + } + + if (skill->value == SKILL_EASY) + { + self->monsterinfo.currentmove = &soldier_move_duck; + return; + } + + self->monsterinfo.pausetime = level.time + eta + 0.3; + r = random(); + + if (skill->value == SKILL_MEDIUM) + { + if (r > 0.33) + { + self->monsterinfo.currentmove = &soldier_move_duck; + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack3; + } + + return; + } + + if (skill->value >= SKILL_HARD) + { + if (r > 0.66) + { + self->monsterinfo.currentmove = &soldier_move_duck; + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack3; + } + + return; + } + + self->monsterinfo.currentmove = &soldier_move_attack3; +} + +void +soldier_fire6(edict_t *self) +{ + if (!self) + { + return; + } + + soldier_fire(self, 5); +} + +void +soldier_fire7(edict_t *self) +{ + if (!self) + { + return; + } + + soldier_fire(self, 6); +} + +void +soldier_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +void +soldier_dead2(edict_t *self) +{ + vec3_t tempmins, tempmaxs, temporg; + trace_t tr; + + if (!self) + { + return; + } + + VectorCopy(self->s.origin, temporg); + /* this is because location traces done at the + floor are guaranteed to hit the floor (inside + the sv_trace code it grows the bbox by 1 in + all directions) */ + temporg[2] += 1; + + VectorSet(tempmins, -32, -32, -24); + VectorSet(tempmaxs, 32, 32, -8); + + tr = gi.trace(temporg, tempmins, tempmaxs, temporg, self, MASK_SOLID); + + if (tr.startsolid || tr.allsolid) + { + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, -8); + } + else + { + VectorCopy(tempmins, self->mins); + VectorCopy(tempmaxs, self->maxs); + } + + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +static mframe_t soldier_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, -10, NULL}, + {ai_move, -10, NULL}, + {ai_move, -10, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, soldier_fire6}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, soldier_fire7}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldier_move_death1 = +{ + FRAME_death101, + FRAME_death136, + soldier_frames_death1, + soldier_dead +}; + +static mframe_t soldier_frames_death2[] = { + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldier_move_death2 = +{ + FRAME_death201, + FRAME_death235, + soldier_frames_death2, + soldier_dead +}; + +static mframe_t soldier_frames_death3[] = { + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, +}; + +mmove_t soldier_move_death3 = +{ + FRAME_death301, + FRAME_death345, + soldier_frames_death3, + soldier_dead +}; + +static mframe_t soldier_frames_death4[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldier_move_death4 = +{ + FRAME_death401, + FRAME_death453, + soldier_frames_death4, + soldier_dead2 +}; + +static mframe_t soldier_frames_death5[] = { + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldier_move_death5 = +{ + FRAME_death501, + FRAME_death524, + soldier_frames_death5, + soldier_dead +}; + +static mframe_t soldier_frames_death6[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldier_move_death6 = +{ + FRAME_death601, + FRAME_death610, + soldier_frames_death6, + soldier_dead +}; + +void +soldier_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + for (n = 0; n < 3; n++) + { + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", + damage, GIB_ORGANIC); + } + + ThrowGib(self, "models/objects/gibs/chest/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 */ + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum |= 1; + + if (self->s.skinnum == 1) + { + gi.sound(self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0); + } + else if (self->s.skinnum == 3) + { + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0); + } + + if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4) + { + /* head shot */ + self->monsterinfo.currentmove = &soldier_move_death3; + return; + } + + n = randk() % 5; + + if (n == 0) + { + self->monsterinfo.currentmove = &soldier_move_death1; + } + else if (n == 1) + { + self->monsterinfo.currentmove = &soldier_move_death2; + } + else if (n == 2) + { + self->monsterinfo.currentmove = &soldier_move_death4; + } + else if (n == 3) + { + self->monsterinfo.currentmove = &soldier_move_death5; + } + else + { + self->monsterinfo.currentmove = &soldier_move_death6; + } +} + +void +soldier_sidestep(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum <= 3) + { + if (self->monsterinfo.currentmove != &soldier_move_attack6) + { + self->monsterinfo.currentmove = &soldier_move_attack6; + } + } + else + { + if (self->monsterinfo.currentmove != &soldier_move_start_run) + { + self->monsterinfo.currentmove = &soldier_move_start_run; + } + } +} + +void +soldier_duck(edict_t *self, float eta) +{ + float r; + + if (!self) + { + return; + } + + /* has to be done immediately otherwise he can get stuck */ + monster_duck_down(self); + + if (skill->value == SKILL_EASY) + { + self->monsterinfo.currentmove = &soldier_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + 1; + return; + } + + r = random(); + + if (r > (skill->value * 0.3)) + { + self->monsterinfo.currentmove = &soldier_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack3; + self->monsterinfo.duck_wait_time = level.time + eta + 1; + } +} + +static mframe_t soldier_frames_blind[] = { + {ai_move, 0, soldier_idle}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldier_move_blind = { + FRAME_stand101, + FRAME_stand130, + soldier_frames_blind, + soldier_blind +}; + +void +soldier_blind(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &soldier_move_blind; +} + +void +SP_monster_soldier_x(edict_t *self) +{ + if (!self) + { + return; + } + + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + sound_step3 = 0; + sound_step4 = 0; + + self->s.modelindex = gi.modelindex("models/monsters/soldier/tris.md2"); + self->monsterinfo.scale = MODEL_SCALE; + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_idle = gi.soundindex("soldier/solidle1.wav"); + sound_sight1 = gi.soundindex("soldier/solsght1.wav"); + sound_sight2 = gi.soundindex("soldier/solsrch1.wav"); + sound_cock = gi.soundindex("infantry/infatck3.wav"); + + self->mass = 100; + + self->pain = soldier_pain; + self->die = soldier_die; + + self->monsterinfo.stand = soldier_stand; + self->monsterinfo.walk = soldier_walk; + self->monsterinfo.run = soldier_run; + self->monsterinfo.dodge = soldier_dodge; + self->monsterinfo.attack = soldier_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = soldier_sight; + + self->monsterinfo.blocked = soldier_blocked; + self->monsterinfo.duck = soldier_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = soldier_sidestep; + + if (self->spawnflags & 8) /* blind */ + { + self->monsterinfo.stand = soldier_blind; + } + + gi.linkentity(self); + + self->monsterinfo.stand(self); + + walkmonster_start(self); +} + +/* + * QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind + * + * Blind - monster will just stand there until triggered + */ +void +SP_monster_soldier_light(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->health = 20; + self->gib_health = -30; + + SP_monster_soldier_x(self); + + sound_pain_light = gi.soundindex("soldier/solpain2.wav"); + sound_death_light = gi.soundindex("soldier/soldeth2.wav"); + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); + gi.modelindex("models/objects/laser/tris.md2"); + gi.soundindex("misc/lasfly.wav"); + gi.soundindex("soldier/solatck2.wav"); + + self->s.skinnum = 0; + self->monsterinfo.blindfire = true; +} + +/* + * QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind + * + * Blind - monster will just stand there until triggered + */ +void +SP_monster_soldier(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->health = 30; + self->gib_health = -30; + + SP_monster_soldier_x(self); + + sound_pain = gi.soundindex("soldier/solpain1.wav"); + sound_death = gi.soundindex("soldier/soldeth1.wav"); + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); + gi.soundindex("soldier/solatck1.wav"); + + self->s.skinnum = 2; +} + +/* + * QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind + * + * Blind - monster will just stand there until triggered + */ +void +SP_monster_soldier_ss(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->health = 40; + self->gib_health = -30; + + SP_monster_soldier_x(self); + + sound_pain_ss = gi.soundindex("soldier/solpain3.wav"); + sound_death_ss = gi.soundindex("soldier/soldeth3.wav"); + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); + gi.soundindex("soldier/solatck3.wav"); + + self->s.skinnum = 4; +} + +void +soldierh_idle(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() > 0.8) + { + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); + } +} + +void +soldierh_cock(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.frame == FRAME_stand322) + { + gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0); + } +} + +static mframe_t soldierh_frames_stand1[] = { + {ai_stand, 0, soldierh_idle}, + {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 soldierh_move_stand1 = { + FRAME_stand101, + FRAME_stand130, + soldierh_frames_stand1, + soldierh_stand +}; + +static mframe_t soldierh_frames_stand3[] = { + {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, soldierh_cock}, + {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 soldierh_move_stand3 = { + FRAME_stand301, + FRAME_stand339, + soldierh_frames_stand3, + soldierh_stand +}; + +void +soldierh_stand(edict_t *self) +{ + if (!self) + { + return; + } + + if ((self->monsterinfo.currentmove == &soldierh_move_stand3) || (random() < 0.8)) + { + self->monsterinfo.currentmove = &soldierh_move_stand1; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_stand3; + } +} + +void +soldierh_walk1_random(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() > 0.1) + { + self->monsterinfo.nextframe = FRAME_walk101; + } +} + +static mframe_t soldierh_frames_walk1[] = { + {ai_walk, 3, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 1, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 3, NULL}, + {ai_walk, -1, soldierh_walk1_random}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL} +}; + +mmove_t soldierh_move_walk1 = { + FRAME_walk101, + FRAME_walk133, + soldierh_frames_walk1, + NULL +}; + +static mframe_t soldierh_frames_walk2[] = { + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 1, NULL}, + {ai_walk, 3, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 7, NULL} +}; + +mmove_t soldierh_move_walk2 = { + FRAME_walk209, + FRAME_walk218, + soldierh_frames_walk2, + NULL +}; + +void +soldierh_walk(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &soldierh_move_walk1; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_walk2; + } +} + +static mframe_t soldierh_frames_start_run[] = { + {ai_run, 7, NULL}, + {ai_run, 5, NULL} +}; + +mmove_t soldierh_move_start_run = { + FRAME_run01, + FRAME_run02, + soldierh_frames_start_run, + soldierh_run +}; + +static mframe_t soldierh_frames_run[] = { + {ai_run, 10, NULL}, + {ai_run, 11, NULL}, + {ai_run, 11, NULL}, + {ai_run, 16, NULL}, + {ai_run, 10, NULL}, + {ai_run, 15, NULL} +}; + +mmove_t soldierh_move_run = { + FRAME_run03, + FRAME_run08, + soldierh_frames_run, + NULL +}; + +void +soldierh_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &soldierh_move_stand1; + return; + } + + if ((self->monsterinfo.currentmove == &soldierh_move_walk1) || + (self->monsterinfo.currentmove == &soldierh_move_walk2) || + (self->monsterinfo.currentmove == &soldierh_move_start_run)) + { + self->monsterinfo.currentmove = &soldierh_move_run; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_start_run; + } +} + +static mframe_t soldierh_frames_pain1[] = { + {ai_move, -3, NULL}, + {ai_move, 4, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_pain1 = { + FRAME_pain101, + FRAME_pain105, + soldierh_frames_pain1, + soldierh_run +}; + +static mframe_t soldierh_frames_pain2[] = { + {ai_move, -13, NULL}, + {ai_move, -1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 4, NULL}, + {ai_move, 2, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL} +}; + +mmove_t soldierh_move_pain2 = { + FRAME_pain201, + FRAME_pain207, + soldierh_frames_pain2, + soldierh_run +}; + +static mframe_t soldierh_frames_pain3[] = { + {ai_move, -8, NULL}, + {ai_move, 10, NULL}, + {ai_move, -4, NULL}, + {ai_move, -1, NULL}, + {ai_move, -3, NULL}, + {ai_move, 0, NULL}, + {ai_move, 3, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 4, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL} +}; + +mmove_t soldierh_move_pain3 = { + FRAME_pain301, + FRAME_pain318, + soldierh_frames_pain3, + soldierh_run +}; + +static mframe_t soldierh_frames_pain4[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -10, NULL}, + {ai_move, -6, NULL}, + {ai_move, 8, NULL}, + {ai_move, 4, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 5, NULL}, + {ai_move, 2, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_pain4 = { + FRAME_pain401, + FRAME_pain417, + soldierh_frames_pain4, + soldierh_run +}; + +void +soldierh_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) +{ + float r; + int n; + + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum |= 1; + } + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && + ((self->monsterinfo.currentmove == &soldierh_move_pain1) || + (self->monsterinfo.currentmove == &soldierh_move_pain2) || + (self->monsterinfo.currentmove == &soldierh_move_pain3))) + { + self->monsterinfo.currentmove = &soldierh_move_pain4; + } + + return; + } + + self->pain_debounce_time = level.time + 3; + + n = self->s.skinnum | 1; + + if (n == 1) + { + gi.sound(self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0); + } + else if (n == 3) + { + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0); + } + + if (self->velocity[2] > 100) + { + self->monsterinfo.currentmove = &soldierh_move_pain4; + return; + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + r = random(); + + if (r < 0.33) + { + self->monsterinfo.currentmove = &soldierh_move_pain1; + } + else if (r < 0.66) + { + self->monsterinfo.currentmove = &soldierh_move_pain2; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_pain3; + } +} + +void +soldierh_laserbeam(edict_t *self, int flash_index) +{ + vec3_t forward, right, up; + vec3_t tempang, start; + vec3_t dir, angles, end; + vec3_t tempvec; + edict_t *ent; + + if (!self) + { + return; + } + + if (random() > 0.8) + { + gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), 1, ATTN_STATIC, 0); + } + + VectorCopy(self->s.origin, start); + VectorCopy(self->enemy->s.origin, end); + VectorSubtract(end, start, dir); + vectoangles(dir, angles); + VectorCopy(monster_flash_offset[flash_index], tempvec); + + ent = G_Spawn(); + VectorCopy(self->s.origin, ent->s.origin); + VectorCopy(angles, tempang); + AngleVectors(tempang, forward, right, up); + VectorCopy(tempang, ent->s.angles); + VectorCopy(ent->s.origin, start); + + if (flash_index == MZ2_SOLDIER_MACHINEGUN_3) + { + VectorMA(start, tempvec[0] - 14, right, start); + VectorMA(start, tempvec[2] + 8, up, start); + VectorMA(start, tempvec[1], forward, start); + } + else + { + VectorMA(start, tempvec[0] + 2, right, start); + VectorMA(start, tempvec[2] + 8, up, start); + VectorMA(start, tempvec[1], forward, start); + } + + VectorCopy(start, ent->s.origin); + ent->enemy = self->enemy; + ent->owner = self; + + ent->dmg = 1; + + monster_dabeam(ent); +} + +void +soldierh_fire(edict_t *self, int flash_number) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + vec3_t dir; + vec3_t end; + float r, u; + int flash_index; + + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + flash_index = blaster_flash[flash_number]; /* ripper */ + } + else if (self->s.skinnum < 4) + { + flash_index = blaster_flash[flash_number]; /* hyperblaster */ + } + else + { + flash_index = machinegun_flash[flash_number]; /* laserbeam */ + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_index], + forward, right, start); + + if ((flash_number == 5) || (flash_number == 6)) + { + VectorCopy(forward, aim); + } + else + { + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, aim); + vectoangles(aim, dir); + AngleVectors(dir, forward, right, up); + + r = crandom() * 100; + u = crandom() * 50; + VectorMA(start, 8192, forward, end); + VectorMA(end, r, right, end); + VectorMA(end, u, up, end); + + VectorSubtract(end, start, aim); + VectorNormalize(aim); + } + + if (self->s.skinnum <= 1) + { + monster_fire_ionripper(self, start, aim, 5, 600, + flash_index, EF_IONRIPPER); + } + else if (self->s.skinnum <= 3) + { + monster_fire_blueblaster(self, start, aim, 1, 600, + MZ_BLUEHYPERBLASTER, EF_BLUEHYPERBLASTER); + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->monsterinfo.pausetime = level.time + (3 + rand() % 8) * FRAMETIME; + } + + soldierh_laserbeam(self, flash_index); + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } + } +} + +void +soldierh_hyper_refire1(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + else if (self->s.skinnum < 4) + { + if (random() < 0.7) + { + self->s.frame = FRAME_attak103; + } + else + { + gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + } + } +} + +void +soldierh_ripper1(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + soldierh_fire(self, 0); + } + else if (self->s.skinnum < 4) + { + soldierh_fire(self, 0); + } +} + +void +soldierh_fire1(edict_t *self) +{ + if (!self) + { + return; + } + + soldierh_fire(self, 0); +} + +void +soldierh_attack1_refire1(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum > 1) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak102; + } + else + { + self->monsterinfo.nextframe = FRAME_attak110; + } +} + +void +soldierh_attack1_refire2(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak102; + } +} + +void +soldierh_hyper_sound(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + else if (self->s.skinnum < 4) + { + gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbl1a.wav"), 1, ATTN_NORM, 0); + } + else + { + return; + } +} + +static mframe_t soldierh_frames_attack1[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_hyper_sound}, + {ai_charge, 0, soldierh_fire1}, + {ai_charge, 0, soldierh_ripper1}, + {ai_charge, 0, soldierh_ripper1}, + {ai_charge, 0, soldierh_attack1_refire1}, + {ai_charge, 0, soldierh_hyper_refire1}, + {ai_charge, 0, soldierh_cock}, + {ai_charge, 0, soldierh_attack1_refire2}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldierh_move_attack1 = { + FRAME_attak101, + FRAME_attak112, + soldierh_frames_attack1, + soldierh_run +}; + +void +soldierh_hyper_refire2(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + else if (self->s.skinnum < 4) + { + if (random() < 0.7) + { + self->s.frame = FRAME_attak205; + } + else + { + gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + } + } +} + +void +soldierh_ripper2(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + soldierh_fire(self, 1); + } + else if (self->s.skinnum < 4) + { + soldierh_fire(self, 1); + } +} + +void +soldierh_fire2(edict_t *self) +{ + if (!self) + { + return; + } + + soldierh_fire(self, 1); +} + +void +soldierh_attack2_refire1(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum > 1) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak204; + } + else + { + self->monsterinfo.nextframe = FRAME_attak216; + } +} + +void +soldierh_attack2_refire2(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || + ((range(self, self->enemy) == RANGE_MELEE) && (self->s.skinnum < 4))) + { + self->monsterinfo.nextframe = FRAME_attak204; + } +} + +static mframe_t soldierh_frames_attack2[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_hyper_sound}, + {ai_charge, 0, soldierh_fire2}, + {ai_charge, 0, soldierh_ripper2}, + {ai_charge, 0, soldierh_ripper2}, + {ai_charge, 0, soldierh_attack2_refire1}, + {ai_charge, 0, soldierh_hyper_refire2}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_cock}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_attack2_refire2}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldierh_move_attack2 = { + FRAME_attak201, + FRAME_attak218, + soldierh_frames_attack2, + soldierh_run +}; + +void +soldierh_duck_down(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + return; + } + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity(self); +} + +void +soldierh_duck_up(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + +void +soldierh_fire3(edict_t *self) +{ + if (!self) + { + return; + } + + soldierh_duck_down(self); + soldierh_fire(self, 2); +} + +void +soldierh_attack3_refire(edict_t *self) +{ + if (!self) + { + return; + } + + if ((level.time + 0.4) < self->monsterinfo.pausetime) + { + self->monsterinfo.nextframe = FRAME_attak303; + } +} + +static mframe_t soldierh_frames_attack3[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_fire3}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_attack3_refire}, + {ai_charge, 0, soldierh_duck_up}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldierh_move_attack3 = { + FRAME_attak301, + FRAME_attak309, + soldierh_frames_attack3, + soldierh_run +}; + +void +soldierh_fire4(edict_t *self) +{ + if (!self) + { + return; + } + + soldierh_fire(self, 3); +} + +static mframe_t soldierh_frames_attack4[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_fire4}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldierh_move_attack4 = { + FRAME_attak401, + FRAME_attak406, + soldierh_frames_attack4, + soldierh_run +}; + +void +soldierh_fire8(edict_t *self) +{ + if (!self) + { + return; + } + + soldierh_fire(self, 7); +} + +void +soldierh_attack6_refire(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (range(self, self->enemy) < RANGE_MID) + { + return; + } + + if (skill->value == SKILL_HARDPLUS) + { + self->monsterinfo.nextframe = FRAME_runs03; + } +} + +static mframe_t soldierh_frames_attack6[] = { + {ai_charge, 10, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 12, NULL}, + {ai_charge, 11, soldierh_fire8}, + {ai_charge, 13, NULL}, + {ai_charge, 18, NULL}, + {ai_charge, 15, NULL}, + {ai_charge, 14, NULL}, + {ai_charge, 11, NULL}, + {ai_charge, 8, NULL}, + {ai_charge, 11, NULL}, + {ai_charge, 12, NULL}, + {ai_charge, 12, NULL}, + {ai_charge, 17, soldierh_attack6_refire} +}; + +mmove_t soldierh_move_attack6 = { + FRAME_runs01, + FRAME_runs14, + soldierh_frames_attack6, + soldierh_run +}; + +void +soldierh_attack(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 4) + { + if (random() < 0.5) + { + self->monsterinfo.currentmove = &soldierh_move_attack1; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack2; + } + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack4; + } +} + +void +soldierh_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); + } + + if ((skill->value > SKILL_EASY) && (range(self, self->enemy) >= RANGE_MID)) + { + if (random() > 0.5) + { + if (self->s.skinnum < 4) + { + self->monsterinfo.currentmove = &soldierh_move_attack6; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack4; + } + } + } +} + +void +soldierh_duck_hold(edict_t *self) +{ + if (!self) + { + return; + } + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +static mframe_t soldierh_frames_duck[] = { + {ai_move, 5, soldierh_duck_down}, + {ai_move, -1, soldierh_duck_hold}, + {ai_move, 1, NULL}, + {ai_move, 0, soldierh_duck_up}, + {ai_move, 5, NULL} +}; + +mmove_t soldierh_move_duck = { + FRAME_duck01, + FRAME_duck05, + soldierh_frames_duck, + soldierh_run +}; + +void +soldierh_dodge(edict_t *self, edict_t *attacker, float eta, + trace_t *tr /* unused */) +{ + float r; + + if (!self || !attacker) + { + return; + } + + r = random(); + + if (r > 0.25) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + } + + if (skill->value == SKILL_EASY) + { + self->monsterinfo.currentmove = &soldierh_move_duck; + return; + } + + self->monsterinfo.pausetime = level.time + eta + 0.3; + r = random(); + + if (skill->value == SKILL_MEDIUM) + { + if (r > 0.33) + { + self->monsterinfo.currentmove = &soldierh_move_duck; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack3; + } + + return; + } + + if (skill->value >= SKILL_HARD) + { + if (r > 0.66) + { + self->monsterinfo.currentmove = &soldierh_move_duck; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack3; + } + + return; + } + + self->monsterinfo.currentmove = &soldierh_move_attack3; +} + +void +soldierh_fire6(edict_t *self) +{ + if (!self) + { + return; + } + + /* no fire laser */ + if (self->s.skinnum < 4) + { + soldierh_fire(self, 5); + } +} + +void +soldierh_fire7(edict_t *self) +{ + if (!self) + { + return; + } + + /* no fire laser */ + if (self->s.skinnum < 4) + { + soldierh_fire(self, 6); + } +} + +void +soldierh_dead(edict_t *self) +{ + if (!self) + { + return; + } + + 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); +} + +static mframe_t soldierh_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, -10, NULL}, + {ai_move, -10, NULL}, + {ai_move, -10, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, soldierh_fire6}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, soldierh_fire7}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_death1 = { + FRAME_death101, + FRAME_death136, + soldierh_frames_death1, + soldierh_dead +}; + +static mframe_t soldierh_frames_death2[] = { + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_death2 = { + FRAME_death201, + FRAME_death235, + soldierh_frames_death2, + soldierh_dead +}; + +static mframe_t soldierh_frames_death3[] = { + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, +}; + +mmove_t soldierh_move_death3 = { + FRAME_death301, + FRAME_death345, + soldierh_frames_death3, + soldierh_dead +}; + +static mframe_t soldierh_frames_death4[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_death4 = { + FRAME_death401, + FRAME_death453, + soldierh_frames_death4, + soldierh_dead +}; + +static mframe_t soldierh_frames_death5[] = { + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_death5 = { + FRAME_death501, + FRAME_death524, + soldierh_frames_death5, + soldierh_dead +}; + +static mframe_t soldierh_frames_death6[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_death6 = { + FRAME_death601, + FRAME_death610, + soldierh_frames_death6, + soldierh_dead +}; + +void +soldierh_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, vec3_t point) +{ + int n; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + for (n = 0; n < 3; n++) + { + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + } + + ThrowGib(self, "models/objects/gibs/chest/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 */ + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum |= 1; + + if (self->s.skinnum == 1) + { + gi.sound(self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0); + } + else if (self->s.skinnum == 3) + { + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0); + } + + if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4) + { + /* head shot */ + self->monsterinfo.currentmove = &soldierh_move_death3; + return; + } + + n = (self->s.skinnum < 4) ? (rand() % 5) : (1 + (rand() % 4)); + + if (n == 0) + { + self->monsterinfo.currentmove = &soldierh_move_death1; + } + else if (n == 1) + { + self->monsterinfo.currentmove = &soldierh_move_death2; + } + else if (n == 2) + { + self->monsterinfo.currentmove = &soldierh_move_death4; + } + else if (n == 3) + { + self->monsterinfo.currentmove = &soldierh_move_death5; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_death6; + } +} + +void +SP_monster_soldier_h(edict_t *self) +{ + if (!self) + { + return; + } + + self->s.modelindex = gi.modelindex("models/monsters/soldierh/tris.md2"); + self->monsterinfo.scale = MODEL_SCALE; + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_idle = gi.soundindex("soldier/solidle1.wav"); + sound_sight1 = gi.soundindex("soldier/solsght1.wav"); + sound_sight2 = gi.soundindex("soldier/solsrch1.wav"); + sound_cock = gi.soundindex("infantry/infatck3.wav"); + + self->mass = 100; + + self->pain = soldierh_pain; + self->die = soldierh_die; + + self->monsterinfo.stand = soldierh_stand; + self->monsterinfo.walk = soldierh_walk; + self->monsterinfo.run = soldierh_run; + self->monsterinfo.dodge = soldierh_dodge; + self->monsterinfo.attack = soldierh_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = soldierh_sight; + + gi.linkentity(self); + + /* self->monsterinfo.stand (self); */ + self->monsterinfo.currentmove = &soldierh_move_stand3; + + walkmonster_start(self); +} + +/* + * QUAKED monster_soldier_ripper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_soldier_ripper(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->health = 50; + self->gib_health = -30; + + SP_monster_soldier_h(self); + + sound_pain_light = gi.soundindex("soldier/solpain2.wav"); + sound_death_light = gi.soundindex("soldier/soldeth2.wav"); + + gi.modelindex("models/objects/boomrang/tris.md2"); + gi.soundindex("misc/lasfly.wav"); + gi.soundindex("soldier/solatck2.wav"); + + self->s.skinnum = 0; +} + +/* + * QUAKED monster_soldier_hypergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_soldier_hypergun(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->health = 60; + self->gib_health = -30; + + SP_monster_soldier_h(self); + + gi.modelindex("models/objects/blaser/tris.md2"); + sound_pain = gi.soundindex("soldier/solpain1.wav"); + sound_death = gi.soundindex("soldier/soldeth1.wav"); + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); + gi.soundindex("soldier/solatck1.wav"); + + self->s.skinnum = 2; +} + +/* + * QUAKED monster_soldier_lasergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_soldier_lasergun(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->health = 70; + self->gib_health = -30; + + SP_monster_soldier_h(self); + + sound_pain_ss = gi.soundindex("soldier/solpain3.wav"); + sound_death_ss = gi.soundindex("soldier/soldeth3.wav"); + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); + gi.soundindex("soldier/solatck3.wav"); + + self->s.skinnum = 4; +} diff --git a/src/game/monster/soldier/soldier.h b/src/game/monster/soldier/soldier.h new file mode 100644 index 000000000..8088b1b6d --- /dev/null +++ b/src/game/monster/soldier/soldier.h @@ -0,0 +1,504 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Soldier aka "Guard" animations. + * + * ======================================================================= + */ + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak201 12 +#define FRAME_attak202 13 +#define FRAME_attak203 14 +#define FRAME_attak204 15 +#define FRAME_attak205 16 +#define FRAME_attak206 17 +#define FRAME_attak207 18 +#define FRAME_attak208 19 +#define FRAME_attak209 20 +#define FRAME_attak210 21 +#define FRAME_attak211 22 +#define FRAME_attak212 23 +#define FRAME_attak213 24 +#define FRAME_attak214 25 +#define FRAME_attak215 26 +#define FRAME_attak216 27 +#define FRAME_attak217 28 +#define FRAME_attak218 29 +#define FRAME_attak301 30 +#define FRAME_attak302 31 +#define FRAME_attak303 32 +#define FRAME_attak304 33 +#define FRAME_attak305 34 +#define FRAME_attak306 35 +#define FRAME_attak307 36 +#define FRAME_attak308 37 +#define FRAME_attak309 38 +#define FRAME_attak401 39 +#define FRAME_attak402 40 +#define FRAME_attak403 41 +#define FRAME_attak404 42 +#define FRAME_attak405 43 +#define FRAME_attak406 44 +#define FRAME_duck01 45 +#define FRAME_duck02 46 +#define FRAME_duck03 47 +#define FRAME_duck04 48 +#define FRAME_duck05 49 +#define FRAME_pain101 50 +#define FRAME_pain102 51 +#define FRAME_pain103 52 +#define FRAME_pain104 53 +#define FRAME_pain105 54 +#define FRAME_pain201 55 +#define FRAME_pain202 56 +#define FRAME_pain203 57 +#define FRAME_pain204 58 +#define FRAME_pain205 59 +#define FRAME_pain206 60 +#define FRAME_pain207 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_pain305 66 +#define FRAME_pain306 67 +#define FRAME_pain307 68 +#define FRAME_pain308 69 +#define FRAME_pain309 70 +#define FRAME_pain310 71 +#define FRAME_pain311 72 +#define FRAME_pain312 73 +#define FRAME_pain313 74 +#define FRAME_pain314 75 +#define FRAME_pain315 76 +#define FRAME_pain316 77 +#define FRAME_pain317 78 +#define FRAME_pain318 79 +#define FRAME_pain401 80 +#define FRAME_pain402 81 +#define FRAME_pain403 82 +#define FRAME_pain404 83 +#define FRAME_pain405 84 +#define FRAME_pain406 85 +#define FRAME_pain407 86 +#define FRAME_pain408 87 +#define FRAME_pain409 88 +#define FRAME_pain410 89 +#define FRAME_pain411 90 +#define FRAME_pain412 91 +#define FRAME_pain413 92 +#define FRAME_pain414 93 +#define FRAME_pain415 94 +#define FRAME_pain416 95 +#define FRAME_pain417 96 +#define FRAME_run01 97 +#define FRAME_run02 98 +#define FRAME_run03 99 +#define FRAME_run04 100 +#define FRAME_run05 101 +#define FRAME_run06 102 +#define FRAME_run07 103 +#define FRAME_run08 104 +#define FRAME_run09 105 +#define FRAME_run10 106 +#define FRAME_run11 107 +#define FRAME_run12 108 +#define FRAME_runs01 109 +#define FRAME_runs02 110 +#define FRAME_runs03 111 +#define FRAME_runs04 112 +#define FRAME_runs05 113 +#define FRAME_runs06 114 +#define FRAME_runs07 115 +#define FRAME_runs08 116 +#define FRAME_runs09 117 +#define FRAME_runs10 118 +#define FRAME_runs11 119 +#define FRAME_runs12 120 +#define FRAME_runs13 121 +#define FRAME_runs14 122 +#define FRAME_runs15 123 +#define FRAME_runs16 124 +#define FRAME_runs17 125 +#define FRAME_runs18 126 +#define FRAME_runt01 127 +#define FRAME_runt02 128 +#define FRAME_runt03 129 +#define FRAME_runt04 130 +#define FRAME_runt05 131 +#define FRAME_runt06 132 +#define FRAME_runt07 133 +#define FRAME_runt08 134 +#define FRAME_runt09 135 +#define FRAME_runt10 136 +#define FRAME_runt11 137 +#define FRAME_runt12 138 +#define FRAME_runt13 139 +#define FRAME_runt14 140 +#define FRAME_runt15 141 +#define FRAME_runt16 142 +#define FRAME_runt17 143 +#define FRAME_runt18 144 +#define FRAME_runt19 145 +#define FRAME_stand101 146 +#define FRAME_stand102 147 +#define FRAME_stand103 148 +#define FRAME_stand104 149 +#define FRAME_stand105 150 +#define FRAME_stand106 151 +#define FRAME_stand107 152 +#define FRAME_stand108 153 +#define FRAME_stand109 154 +#define FRAME_stand110 155 +#define FRAME_stand111 156 +#define FRAME_stand112 157 +#define FRAME_stand113 158 +#define FRAME_stand114 159 +#define FRAME_stand115 160 +#define FRAME_stand116 161 +#define FRAME_stand117 162 +#define FRAME_stand118 163 +#define FRAME_stand119 164 +#define FRAME_stand120 165 +#define FRAME_stand121 166 +#define FRAME_stand122 167 +#define FRAME_stand123 168 +#define FRAME_stand124 169 +#define FRAME_stand125 170 +#define FRAME_stand126 171 +#define FRAME_stand127 172 +#define FRAME_stand128 173 +#define FRAME_stand129 174 +#define FRAME_stand130 175 +#define FRAME_stand301 176 +#define FRAME_stand302 177 +#define FRAME_stand303 178 +#define FRAME_stand304 179 +#define FRAME_stand305 180 +#define FRAME_stand306 181 +#define FRAME_stand307 182 +#define FRAME_stand308 183 +#define FRAME_stand309 184 +#define FRAME_stand310 185 +#define FRAME_stand311 186 +#define FRAME_stand312 187 +#define FRAME_stand313 188 +#define FRAME_stand314 189 +#define FRAME_stand315 190 +#define FRAME_stand316 191 +#define FRAME_stand317 192 +#define FRAME_stand318 193 +#define FRAME_stand319 194 +#define FRAME_stand320 195 +#define FRAME_stand321 196 +#define FRAME_stand322 197 +#define FRAME_stand323 198 +#define FRAME_stand324 199 +#define FRAME_stand325 200 +#define FRAME_stand326 201 +#define FRAME_stand327 202 +#define FRAME_stand328 203 +#define FRAME_stand329 204 +#define FRAME_stand330 205 +#define FRAME_stand331 206 +#define FRAME_stand332 207 +#define FRAME_stand333 208 +#define FRAME_stand334 209 +#define FRAME_stand335 210 +#define FRAME_stand336 211 +#define FRAME_stand337 212 +#define FRAME_stand338 213 +#define FRAME_stand339 214 +#define FRAME_walk101 215 +#define FRAME_walk102 216 +#define FRAME_walk103 217 +#define FRAME_walk104 218 +#define FRAME_walk105 219 +#define FRAME_walk106 220 +#define FRAME_walk107 221 +#define FRAME_walk108 222 +#define FRAME_walk109 223 +#define FRAME_walk110 224 +#define FRAME_walk111 225 +#define FRAME_walk112 226 +#define FRAME_walk113 227 +#define FRAME_walk114 228 +#define FRAME_walk115 229 +#define FRAME_walk116 230 +#define FRAME_walk117 231 +#define FRAME_walk118 232 +#define FRAME_walk119 233 +#define FRAME_walk120 234 +#define FRAME_walk121 235 +#define FRAME_walk122 236 +#define FRAME_walk123 237 +#define FRAME_walk124 238 +#define FRAME_walk125 239 +#define FRAME_walk126 240 +#define FRAME_walk127 241 +#define FRAME_walk128 242 +#define FRAME_walk129 243 +#define FRAME_walk130 244 +#define FRAME_walk131 245 +#define FRAME_walk132 246 +#define FRAME_walk133 247 +#define FRAME_walk201 248 +#define FRAME_walk202 249 +#define FRAME_walk203 250 +#define FRAME_walk204 251 +#define FRAME_walk205 252 +#define FRAME_walk206 253 +#define FRAME_walk207 254 +#define FRAME_walk208 255 +#define FRAME_walk209 256 +#define FRAME_walk210 257 +#define FRAME_walk211 258 +#define FRAME_walk212 259 +#define FRAME_walk213 260 +#define FRAME_walk214 261 +#define FRAME_walk215 262 +#define FRAME_walk216 263 +#define FRAME_walk217 264 +#define FRAME_walk218 265 +#define FRAME_walk219 266 +#define FRAME_walk220 267 +#define FRAME_walk221 268 +#define FRAME_walk222 269 +#define FRAME_walk223 270 +#define FRAME_walk224 271 +#define FRAME_death101 272 +#define FRAME_death102 273 +#define FRAME_death103 274 +#define FRAME_death104 275 +#define FRAME_death105 276 +#define FRAME_death106 277 +#define FRAME_death107 278 +#define FRAME_death108 279 +#define FRAME_death109 280 +#define FRAME_death110 281 +#define FRAME_death111 282 +#define FRAME_death112 283 +#define FRAME_death113 284 +#define FRAME_death114 285 +#define FRAME_death115 286 +#define FRAME_death116 287 +#define FRAME_death117 288 +#define FRAME_death118 289 +#define FRAME_death119 290 +#define FRAME_death120 291 +#define FRAME_death121 292 +#define FRAME_death122 293 +#define FRAME_death123 294 +#define FRAME_death124 295 +#define FRAME_death125 296 +#define FRAME_death126 297 +#define FRAME_death127 298 +#define FRAME_death128 299 +#define FRAME_death129 300 +#define FRAME_death130 301 +#define FRAME_death131 302 +#define FRAME_death132 303 +#define FRAME_death133 304 +#define FRAME_death134 305 +#define FRAME_death135 306 +#define FRAME_death136 307 +#define FRAME_death201 308 +#define FRAME_death202 309 +#define FRAME_death203 310 +#define FRAME_death204 311 +#define FRAME_death205 312 +#define FRAME_death206 313 +#define FRAME_death207 314 +#define FRAME_death208 315 +#define FRAME_death209 316 +#define FRAME_death210 317 +#define FRAME_death211 318 +#define FRAME_death212 319 +#define FRAME_death213 320 +#define FRAME_death214 321 +#define FRAME_death215 322 +#define FRAME_death216 323 +#define FRAME_death217 324 +#define FRAME_death218 325 +#define FRAME_death219 326 +#define FRAME_death220 327 +#define FRAME_death221 328 +#define FRAME_death222 329 +#define FRAME_death223 330 +#define FRAME_death224 331 +#define FRAME_death225 332 +#define FRAME_death226 333 +#define FRAME_death227 334 +#define FRAME_death228 335 +#define FRAME_death229 336 +#define FRAME_death230 337 +#define FRAME_death231 338 +#define FRAME_death232 339 +#define FRAME_death233 340 +#define FRAME_death234 341 +#define FRAME_death235 342 +#define FRAME_death301 343 +#define FRAME_death302 344 +#define FRAME_death303 345 +#define FRAME_death304 346 +#define FRAME_death305 347 +#define FRAME_death306 348 +#define FRAME_death307 349 +#define FRAME_death308 350 +#define FRAME_death309 351 +#define FRAME_death310 352 +#define FRAME_death311 353 +#define FRAME_death312 354 +#define FRAME_death313 355 +#define FRAME_death314 356 +#define FRAME_death315 357 +#define FRAME_death316 358 +#define FRAME_death317 359 +#define FRAME_death318 360 +#define FRAME_death319 361 +#define FRAME_death320 362 +#define FRAME_death321 363 +#define FRAME_death322 364 +#define FRAME_death323 365 +#define FRAME_death324 366 +#define FRAME_death325 367 +#define FRAME_death326 368 +#define FRAME_death327 369 +#define FRAME_death328 370 +#define FRAME_death329 371 +#define FRAME_death330 372 +#define FRAME_death331 373 +#define FRAME_death332 374 +#define FRAME_death333 375 +#define FRAME_death334 376 +#define FRAME_death335 377 +#define FRAME_death336 378 +#define FRAME_death337 379 +#define FRAME_death338 380 +#define FRAME_death339 381 +#define FRAME_death340 382 +#define FRAME_death341 383 +#define FRAME_death342 384 +#define FRAME_death343 385 +#define FRAME_death344 386 +#define FRAME_death345 387 +#define FRAME_death401 388 +#define FRAME_death402 389 +#define FRAME_death403 390 +#define FRAME_death404 391 +#define FRAME_death405 392 +#define FRAME_death406 393 +#define FRAME_death407 394 +#define FRAME_death408 395 +#define FRAME_death409 396 +#define FRAME_death410 397 +#define FRAME_death411 398 +#define FRAME_death412 399 +#define FRAME_death413 400 +#define FRAME_death414 401 +#define FRAME_death415 402 +#define FRAME_death416 403 +#define FRAME_death417 404 +#define FRAME_death418 405 +#define FRAME_death419 406 +#define FRAME_death420 407 +#define FRAME_death421 408 +#define FRAME_death422 409 +#define FRAME_death423 410 +#define FRAME_death424 411 +#define FRAME_death425 412 +#define FRAME_death426 413 +#define FRAME_death427 414 +#define FRAME_death428 415 +#define FRAME_death429 416 +#define FRAME_death430 417 +#define FRAME_death431 418 +#define FRAME_death432 419 +#define FRAME_death433 420 +#define FRAME_death434 421 +#define FRAME_death435 422 +#define FRAME_death436 423 +#define FRAME_death437 424 +#define FRAME_death438 425 +#define FRAME_death439 426 +#define FRAME_death440 427 +#define FRAME_death441 428 +#define FRAME_death442 429 +#define FRAME_death443 430 +#define FRAME_death444 431 +#define FRAME_death445 432 +#define FRAME_death446 433 +#define FRAME_death447 434 +#define FRAME_death448 435 +#define FRAME_death449 436 +#define FRAME_death450 437 +#define FRAME_death451 438 +#define FRAME_death452 439 +#define FRAME_death453 440 +#define FRAME_death501 441 +#define FRAME_death502 442 +#define FRAME_death503 443 +#define FRAME_death504 444 +#define FRAME_death505 445 +#define FRAME_death506 446 +#define FRAME_death507 447 +#define FRAME_death508 448 +#define FRAME_death509 449 +#define FRAME_death510 450 +#define FRAME_death511 451 +#define FRAME_death512 452 +#define FRAME_death513 453 +#define FRAME_death514 454 +#define FRAME_death515 455 +#define FRAME_death516 456 +#define FRAME_death517 457 +#define FRAME_death518 458 +#define FRAME_death519 459 +#define FRAME_death520 460 +#define FRAME_death521 461 +#define FRAME_death522 462 +#define FRAME_death523 463 +#define FRAME_death524 464 +#define FRAME_death601 465 +#define FRAME_death602 466 +#define FRAME_death603 467 +#define FRAME_death604 468 +#define FRAME_death605 469 +#define FRAME_death606 470 +#define FRAME_death607 471 +#define FRAME_death608 472 +#define FRAME_death609 473 +#define FRAME_death610 474 + +#define MODEL_SCALE 1.200000 diff --git a/src/game/monster/soldier/soldierh.h b/src/game/monster/soldier/soldierh.h new file mode 100644 index 000000000..acc542187 --- /dev/null +++ b/src/game/monster/soldier/soldierh.h @@ -0,0 +1,505 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Soldier aka "Guard" animations. This is the new model added in + * Xatrix, used for the new variants of the enemy. + * + * ======================================================================= + */ + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak201 12 +#define FRAME_attak202 13 +#define FRAME_attak203 14 +#define FRAME_attak204 15 +#define FRAME_attak205 16 +#define FRAME_attak206 17 +#define FRAME_attak207 18 +#define FRAME_attak208 19 +#define FRAME_attak209 20 +#define FRAME_attak210 21 +#define FRAME_attak211 22 +#define FRAME_attak212 23 +#define FRAME_attak213 24 +#define FRAME_attak214 25 +#define FRAME_attak215 26 +#define FRAME_attak216 27 +#define FRAME_attak217 28 +#define FRAME_attak218 29 +#define FRAME_attak301 30 +#define FRAME_attak302 31 +#define FRAME_attak303 32 +#define FRAME_attak304 33 +#define FRAME_attak305 34 +#define FRAME_attak306 35 +#define FRAME_attak307 36 +#define FRAME_attak308 37 +#define FRAME_attak309 38 +#define FRAME_attak401 39 +#define FRAME_attak402 40 +#define FRAME_attak403 41 +#define FRAME_attak404 42 +#define FRAME_attak405 43 +#define FRAME_attak406 44 +#define FRAME_duck01 45 +#define FRAME_duck02 46 +#define FRAME_duck03 47 +#define FRAME_duck04 48 +#define FRAME_duck05 49 +#define FRAME_pain101 50 +#define FRAME_pain102 51 +#define FRAME_pain103 52 +#define FRAME_pain104 53 +#define FRAME_pain105 54 +#define FRAME_pain201 55 +#define FRAME_pain202 56 +#define FRAME_pain203 57 +#define FRAME_pain204 58 +#define FRAME_pain205 59 +#define FRAME_pain206 60 +#define FRAME_pain207 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_pain305 66 +#define FRAME_pain306 67 +#define FRAME_pain307 68 +#define FRAME_pain308 69 +#define FRAME_pain309 70 +#define FRAME_pain310 71 +#define FRAME_pain311 72 +#define FRAME_pain312 73 +#define FRAME_pain313 74 +#define FRAME_pain314 75 +#define FRAME_pain315 76 +#define FRAME_pain316 77 +#define FRAME_pain317 78 +#define FRAME_pain318 79 +#define FRAME_pain401 80 +#define FRAME_pain402 81 +#define FRAME_pain403 82 +#define FRAME_pain404 83 +#define FRAME_pain405 84 +#define FRAME_pain406 85 +#define FRAME_pain407 86 +#define FRAME_pain408 87 +#define FRAME_pain409 88 +#define FRAME_pain410 89 +#define FRAME_pain411 90 +#define FRAME_pain412 91 +#define FRAME_pain413 92 +#define FRAME_pain414 93 +#define FRAME_pain415 94 +#define FRAME_pain416 95 +#define FRAME_pain417 96 +#define FRAME_run01 97 +#define FRAME_run02 98 +#define FRAME_run03 99 +#define FRAME_run04 100 +#define FRAME_run05 101 +#define FRAME_run06 102 +#define FRAME_run07 103 +#define FRAME_run08 104 +#define FRAME_run09 105 +#define FRAME_run10 106 +#define FRAME_run11 107 +#define FRAME_run12 108 +#define FRAME_runs01 109 +#define FRAME_runs02 110 +#define FRAME_runs03 111 +#define FRAME_runs04 112 +#define FRAME_runs05 113 +#define FRAME_runs06 114 +#define FRAME_runs07 115 +#define FRAME_runs08 116 +#define FRAME_runs09 117 +#define FRAME_runs10 118 +#define FRAME_runs11 119 +#define FRAME_runs12 120 +#define FRAME_runs13 121 +#define FRAME_runs14 122 +#define FRAME_runs15 123 +#define FRAME_runs16 124 +#define FRAME_runs17 125 +#define FRAME_runs18 126 +#define FRAME_runt01 127 +#define FRAME_runt02 128 +#define FRAME_runt03 129 +#define FRAME_runt04 130 +#define FRAME_runt05 131 +#define FRAME_runt06 132 +#define FRAME_runt07 133 +#define FRAME_runt08 134 +#define FRAME_runt09 135 +#define FRAME_runt10 136 +#define FRAME_runt11 137 +#define FRAME_runt12 138 +#define FRAME_runt13 139 +#define FRAME_runt14 140 +#define FRAME_runt15 141 +#define FRAME_runt16 142 +#define FRAME_runt17 143 +#define FRAME_runt18 144 +#define FRAME_runt19 145 +#define FRAME_stand101 146 +#define FRAME_stand102 147 +#define FRAME_stand103 148 +#define FRAME_stand104 149 +#define FRAME_stand105 150 +#define FRAME_stand106 151 +#define FRAME_stand107 152 +#define FRAME_stand108 153 +#define FRAME_stand109 154 +#define FRAME_stand110 155 +#define FRAME_stand111 156 +#define FRAME_stand112 157 +#define FRAME_stand113 158 +#define FRAME_stand114 159 +#define FRAME_stand115 160 +#define FRAME_stand116 161 +#define FRAME_stand117 162 +#define FRAME_stand118 163 +#define FRAME_stand119 164 +#define FRAME_stand120 165 +#define FRAME_stand121 166 +#define FRAME_stand122 167 +#define FRAME_stand123 168 +#define FRAME_stand124 169 +#define FRAME_stand125 170 +#define FRAME_stand126 171 +#define FRAME_stand127 172 +#define FRAME_stand128 173 +#define FRAME_stand129 174 +#define FRAME_stand130 175 +#define FRAME_stand301 176 +#define FRAME_stand302 177 +#define FRAME_stand303 178 +#define FRAME_stand304 179 +#define FRAME_stand305 180 +#define FRAME_stand306 181 +#define FRAME_stand307 182 +#define FRAME_stand308 183 +#define FRAME_stand309 184 +#define FRAME_stand310 185 +#define FRAME_stand311 186 +#define FRAME_stand312 187 +#define FRAME_stand313 188 +#define FRAME_stand314 189 +#define FRAME_stand315 190 +#define FRAME_stand316 191 +#define FRAME_stand317 192 +#define FRAME_stand318 193 +#define FRAME_stand319 194 +#define FRAME_stand320 195 +#define FRAME_stand321 196 +#define FRAME_stand322 197 +#define FRAME_stand323 198 +#define FRAME_stand324 199 +#define FRAME_stand325 200 +#define FRAME_stand326 201 +#define FRAME_stand327 202 +#define FRAME_stand328 203 +#define FRAME_stand329 204 +#define FRAME_stand330 205 +#define FRAME_stand331 206 +#define FRAME_stand332 207 +#define FRAME_stand333 208 +#define FRAME_stand334 209 +#define FRAME_stand335 210 +#define FRAME_stand336 211 +#define FRAME_stand337 212 +#define FRAME_stand338 213 +#define FRAME_stand339 214 +#define FRAME_walk101 215 +#define FRAME_walk102 216 +#define FRAME_walk103 217 +#define FRAME_walk104 218 +#define FRAME_walk105 219 +#define FRAME_walk106 220 +#define FRAME_walk107 221 +#define FRAME_walk108 222 +#define FRAME_walk109 223 +#define FRAME_walk110 224 +#define FRAME_walk111 225 +#define FRAME_walk112 226 +#define FRAME_walk113 227 +#define FRAME_walk114 228 +#define FRAME_walk115 229 +#define FRAME_walk116 230 +#define FRAME_walk117 231 +#define FRAME_walk118 232 +#define FRAME_walk119 233 +#define FRAME_walk120 234 +#define FRAME_walk121 235 +#define FRAME_walk122 236 +#define FRAME_walk123 237 +#define FRAME_walk124 238 +#define FRAME_walk125 239 +#define FRAME_walk126 240 +#define FRAME_walk127 241 +#define FRAME_walk128 242 +#define FRAME_walk129 243 +#define FRAME_walk130 244 +#define FRAME_walk131 245 +#define FRAME_walk132 246 +#define FRAME_walk133 247 +#define FRAME_walk201 248 +#define FRAME_walk202 249 +#define FRAME_walk203 250 +#define FRAME_walk204 251 +#define FRAME_walk205 252 +#define FRAME_walk206 253 +#define FRAME_walk207 254 +#define FRAME_walk208 255 +#define FRAME_walk209 256 +#define FRAME_walk210 257 +#define FRAME_walk211 258 +#define FRAME_walk212 259 +#define FRAME_walk213 260 +#define FRAME_walk214 261 +#define FRAME_walk215 262 +#define FRAME_walk216 263 +#define FRAME_walk217 264 +#define FRAME_walk218 265 +#define FRAME_walk219 266 +#define FRAME_walk220 267 +#define FRAME_walk221 268 +#define FRAME_walk222 269 +#define FRAME_walk223 270 +#define FRAME_walk224 271 +#define FRAME_death101 272 +#define FRAME_death102 273 +#define FRAME_death103 274 +#define FRAME_death104 275 +#define FRAME_death105 276 +#define FRAME_death106 277 +#define FRAME_death107 278 +#define FRAME_death108 279 +#define FRAME_death109 280 +#define FRAME_death110 281 +#define FRAME_death111 282 +#define FRAME_death112 283 +#define FRAME_death113 284 +#define FRAME_death114 285 +#define FRAME_death115 286 +#define FRAME_death116 287 +#define FRAME_death117 288 +#define FRAME_death118 289 +#define FRAME_death119 290 +#define FRAME_death120 291 +#define FRAME_death121 292 +#define FRAME_death122 293 +#define FRAME_death123 294 +#define FRAME_death124 295 +#define FRAME_death125 296 +#define FRAME_death126 297 +#define FRAME_death127 298 +#define FRAME_death128 299 +#define FRAME_death129 300 +#define FRAME_death130 301 +#define FRAME_death131 302 +#define FRAME_death132 303 +#define FRAME_death133 304 +#define FRAME_death134 305 +#define FRAME_death135 306 +#define FRAME_death136 307 +#define FRAME_death201 308 +#define FRAME_death202 309 +#define FRAME_death203 310 +#define FRAME_death204 311 +#define FRAME_death205 312 +#define FRAME_death206 313 +#define FRAME_death207 314 +#define FRAME_death208 315 +#define FRAME_death209 316 +#define FRAME_death210 317 +#define FRAME_death211 318 +#define FRAME_death212 319 +#define FRAME_death213 320 +#define FRAME_death214 321 +#define FRAME_death215 322 +#define FRAME_death216 323 +#define FRAME_death217 324 +#define FRAME_death218 325 +#define FRAME_death219 326 +#define FRAME_death220 327 +#define FRAME_death221 328 +#define FRAME_death222 329 +#define FRAME_death223 330 +#define FRAME_death224 331 +#define FRAME_death225 332 +#define FRAME_death226 333 +#define FRAME_death227 334 +#define FRAME_death228 335 +#define FRAME_death229 336 +#define FRAME_death230 337 +#define FRAME_death231 338 +#define FRAME_death232 339 +#define FRAME_death233 340 +#define FRAME_death234 341 +#define FRAME_death235 342 +#define FRAME_death301 343 +#define FRAME_death302 344 +#define FRAME_death303 345 +#define FRAME_death304 346 +#define FRAME_death305 347 +#define FRAME_death306 348 +#define FRAME_death307 349 +#define FRAME_death308 350 +#define FRAME_death309 351 +#define FRAME_death310 352 +#define FRAME_death311 353 +#define FRAME_death312 354 +#define FRAME_death313 355 +#define FRAME_death314 356 +#define FRAME_death315 357 +#define FRAME_death316 358 +#define FRAME_death317 359 +#define FRAME_death318 360 +#define FRAME_death319 361 +#define FRAME_death320 362 +#define FRAME_death321 363 +#define FRAME_death322 364 +#define FRAME_death323 365 +#define FRAME_death324 366 +#define FRAME_death325 367 +#define FRAME_death326 368 +#define FRAME_death327 369 +#define FRAME_death328 370 +#define FRAME_death329 371 +#define FRAME_death330 372 +#define FRAME_death331 373 +#define FRAME_death332 374 +#define FRAME_death333 375 +#define FRAME_death334 376 +#define FRAME_death335 377 +#define FRAME_death336 378 +#define FRAME_death337 379 +#define FRAME_death338 380 +#define FRAME_death339 381 +#define FRAME_death340 382 +#define FRAME_death341 383 +#define FRAME_death342 384 +#define FRAME_death343 385 +#define FRAME_death344 386 +#define FRAME_death345 387 +#define FRAME_death401 388 +#define FRAME_death402 389 +#define FRAME_death403 390 +#define FRAME_death404 391 +#define FRAME_death405 392 +#define FRAME_death406 393 +#define FRAME_death407 394 +#define FRAME_death408 395 +#define FRAME_death409 396 +#define FRAME_death410 397 +#define FRAME_death411 398 +#define FRAME_death412 399 +#define FRAME_death413 400 +#define FRAME_death414 401 +#define FRAME_death415 402 +#define FRAME_death416 403 +#define FRAME_death417 404 +#define FRAME_death418 405 +#define FRAME_death419 406 +#define FRAME_death420 407 +#define FRAME_death421 408 +#define FRAME_death422 409 +#define FRAME_death423 410 +#define FRAME_death424 411 +#define FRAME_death425 412 +#define FRAME_death426 413 +#define FRAME_death427 414 +#define FRAME_death428 415 +#define FRAME_death429 416 +#define FRAME_death430 417 +#define FRAME_death431 418 +#define FRAME_death432 419 +#define FRAME_death433 420 +#define FRAME_death434 421 +#define FRAME_death435 422 +#define FRAME_death436 423 +#define FRAME_death437 424 +#define FRAME_death438 425 +#define FRAME_death439 426 +#define FRAME_death440 427 +#define FRAME_death441 428 +#define FRAME_death442 429 +#define FRAME_death443 430 +#define FRAME_death444 431 +#define FRAME_death445 432 +#define FRAME_death446 433 +#define FRAME_death447 434 +#define FRAME_death448 435 +#define FRAME_death449 436 +#define FRAME_death450 437 +#define FRAME_death451 438 +#define FRAME_death452 439 +#define FRAME_death453 440 +#define FRAME_death501 441 +#define FRAME_death502 442 +#define FRAME_death503 443 +#define FRAME_death504 444 +#define FRAME_death505 445 +#define FRAME_death506 446 +#define FRAME_death507 447 +#define FRAME_death508 448 +#define FRAME_death509 449 +#define FRAME_death510 450 +#define FRAME_death511 451 +#define FRAME_death512 452 +#define FRAME_death513 453 +#define FRAME_death514 454 +#define FRAME_death515 455 +#define FRAME_death516 456 +#define FRAME_death517 457 +#define FRAME_death518 458 +#define FRAME_death519 459 +#define FRAME_death520 460 +#define FRAME_death521 461 +#define FRAME_death522 462 +#define FRAME_death523 463 +#define FRAME_death524 464 +#define FRAME_death601 465 +#define FRAME_death602 466 +#define FRAME_death603 467 +#define FRAME_death604 468 +#define FRAME_death605 469 +#define FRAME_death606 470 +#define FRAME_death607 471 +#define FRAME_death608 472 +#define FRAME_death609 473 +#define FRAME_death610 474 + +#define MODEL_SCALE 1.200000 diff --git a/src/game/monster/stalker/stalker.c b/src/game/monster/stalker/stalker.c new file mode 100644 index 000000000..6a2804599 --- /dev/null +++ b/src/game/monster/stalker/stalker.c @@ -0,0 +1,1515 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Stalker. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "stalker.h" +#include + +static int sound_pain; +static int sound_die; +static int sound_sight; +static int sound_punch_hit1; +static int sound_punch_hit2; +static int sound_idle; + +int stalker_do_pounce(edict_t *self, vec3_t dest); +void stalker_stand(edict_t *self); +void stalker_run(edict_t *self); +void stalker_walk(edict_t *self); +void stalker_jump(edict_t *self); +void stalker_dodge_jump(edict_t *self); +void stalker_swing_check_l(edict_t *self); +void stalker_swing_check_r(edict_t *self); +void stalker_swing_attack(edict_t *self); +void stalker_jump_straightup(edict_t *self); +void stalker_jump_wait_land(edict_t *self); +void stalker_false_death(edict_t *self); +void stalker_false_death_start(edict_t *self); + +#define STALKER_ON_CEILING(ent) (ent->gravityVector[2] > 0 ? 1 : 0) + +#define PI 3.14159 +#define RAD2DEG(x) (x * (float)180.0 / (float)PI) +#define DEG2RAD(x) (x * (float)PI / (float)180.0) +#define FAUX_GRAVITY 800.0 + +extern qboolean SV_PointCloseEnough(edict_t *ent, vec3_t goal, float dist); +extern void drawbbox(edict_t *self); + +static qboolean +stalker_ok_to_transition(edict_t *self) +{ + trace_t trace; + vec3_t pt, start; + float max_dist; + float margin; + float end_height; + + if (!self) + { + return false; + } + + if (STALKER_ON_CEILING(self)) + { + max_dist = -384; + margin = self->mins[2] - 8; + } + else + { + /* her stalkers are just better */ + if (self->monsterinfo.aiflags & AI_SPAWNED_WIDOW) + { + max_dist = 256; + } + else + { + max_dist = 180; + } + + margin = self->maxs[2] + 8; + } + + VectorCopy(self->s.origin, pt); + pt[2] += max_dist; + trace = gi.trace(self->s.origin, self->mins, self->maxs, + pt, self, MASK_MONSTERSOLID); + + if ((trace.fraction == 1.0) || + !(trace.contents & CONTENTS_SOLID) || + (trace.ent != world)) + { + if (STALKER_ON_CEILING(self)) + { + if (trace.plane.normal[2] < 0.9) + { + return false; + } + } + else + { + if (trace.plane.normal[2] > -0.9) + { + return false; + } + } + } + + end_height = trace.endpos[2]; + + /* check the four corners, tracing only to the + endpoint of the center trace (vertically). */ + pt[0] = self->absmin[0]; + pt[1] = self->absmin[1]; + pt[2] = trace.endpos[2] + margin; /* give a little margin of error to allow slight inclines */ + VectorCopy(pt, start); + start[2] = self->s.origin[2]; + trace = gi.trace(start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); + + if ((trace.fraction == 1.0) || !(trace.contents & CONTENTS_SOLID) || + (trace.ent != world)) + { + return false; + } + + if (fabsf(end_height + margin - trace.endpos[2]) > 8) + { + return false; + } + + pt[0] = self->absmax[0]; + pt[1] = self->absmin[1]; + VectorCopy(pt, start); + start[2] = self->s.origin[2]; + trace = gi.trace(start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); + + if ((trace.fraction == 1.0) || !(trace.contents & CONTENTS_SOLID) || + (trace.ent != world)) + { + return false; + } + + if (fabsf(end_height + margin - trace.endpos[2]) > 8) + { + return false; + } + + pt[0] = self->absmax[0]; + pt[1] = self->absmax[1]; + VectorCopy(pt, start); + start[2] = self->s.origin[2]; + trace = gi.trace(start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); + + if ((trace.fraction == 1.0) || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) + { + return false; + } + + if (fabsf(end_height + margin - trace.endpos[2]) > 8) + { + return false; + } + + pt[0] = self->absmin[0]; + pt[1] = self->absmax[1]; + VectorCopy(pt, start); + start[2] = self->s.origin[2]; + trace = gi.trace(start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID); + + if ((trace.fraction == 1.0) || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world)) + { + return false; + } + + if (fabsf(end_height + margin - trace.endpos[2]) > 8) + { + return false; + } + + return true; +} + +void +stalker_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0); +} + +void +stalker_idle_noise(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_idle, 0.5, ATTN_IDLE, 0); +} + +static mframe_t stalker_frames_idle[] = { + {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, stalker_idle_noise}, + {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 stalker_move_idle = { + FRAME_idle01, + FRAME_idle21, + stalker_frames_idle, + stalker_stand +}; + +static mframe_t stalker_frames_idle2[] = { + {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 stalker_move_idle2 = { + FRAME_idle201, + FRAME_idle213, + stalker_frames_idle2, + stalker_stand +}; + +void +stalker_idle(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.35) + { + self->monsterinfo.currentmove = &stalker_move_idle; + } + else + { + self->monsterinfo.currentmove = &stalker_move_idle2; + } +} + +static mframe_t stalker_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, stalker_idle_noise}, + {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 stalker_move_stand = { + FRAME_idle01, + FRAME_idle21, + stalker_frames_stand, + stalker_stand +}; + +void +stalker_stand(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.25) + { + self->monsterinfo.currentmove = &stalker_move_stand; + } + else + { + self->monsterinfo.currentmove = &stalker_move_idle2; + } +} + +static mframe_t stalker_frames_run[] = { + {ai_run, 13, NULL}, + {ai_run, 17, NULL}, + {ai_run, 21, NULL}, + {ai_run, 18, NULL} +}; + +mmove_t stalker_move_run = { + FRAME_run01, + FRAME_run04, + stalker_frames_run, + NULL +}; + +void +stalker_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &stalker_move_stand; + } + else + { + self->monsterinfo.currentmove = &stalker_move_run; + } +} + +static mframe_t stalker_frames_walk[] = { + {ai_walk, 4, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 5, NULL}, + + {ai_walk, 4, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 4, NULL} +}; + +mmove_t stalker_move_walk = { + FRAME_walk01, + FRAME_walk08, + stalker_frames_walk, + stalker_walk +}; + +void +stalker_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &stalker_move_walk; +} + +static mframe_t stalker_frames_reactivate[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t stalker_move_false_death_end = { + FRAME_reactive01, + FRAME_reactive04, + stalker_frames_reactivate, + stalker_run +}; + +void +stalker_reactivate(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_STAND_GROUND; + self->monsterinfo.currentmove = &stalker_move_false_death_end; +} + +void +stalker_heal(edict_t *self) +{ + if (!self) + { + return; + } + + if (skill->value == SKILL_HARD) + { + self->health += 2; + } + else if (skill->value == SKILL_HARDPLUS) + { + self->health += 3; + } + else + { + self->health++; + } + + if (self->health > (self->max_health / 2)) + { + self->s.skinnum = 0; + } + + if (self->health >= self->max_health) + { + self->health = self->max_health; + stalker_reactivate(self); + } +} + +static mframe_t stalker_frames_false_death[] = { + {ai_move, 0, stalker_heal}, + {ai_move, 0, stalker_heal}, + {ai_move, 0, stalker_heal}, + {ai_move, 0, stalker_heal}, + {ai_move, 0, stalker_heal}, + + {ai_move, 0, stalker_heal}, + {ai_move, 0, stalker_heal}, + {ai_move, 0, stalker_heal}, + {ai_move, 0, stalker_heal}, + {ai_move, 0, stalker_heal} +}; + +mmove_t stalker_move_false_death = { + FRAME_twitch01, + FRAME_twitch10, + stalker_frames_false_death, + stalker_false_death +}; + +void +stalker_false_death(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &stalker_move_false_death; +} + +static mframe_t stalker_frames_false_death_start[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, +}; + +mmove_t stalker_move_false_death_start = { + FRAME_death01, + FRAME_death09, + stalker_frames_false_death_start, + stalker_false_death +}; + +void +stalker_false_death_start(edict_t *self) +{ + if (!self) + { + return; + } + + self->s.angles[2] = 0; + VectorSet(self->gravityVector, 0, 0, -1); + + self->monsterinfo.aiflags |= AI_STAND_GROUND; + self->monsterinfo.currentmove = &stalker_move_false_death_start; +} + +static mframe_t stalker_frames_pain[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t stalker_move_pain = { + FRAME_pain01, + FRAME_pain04, + stalker_frames_pain, + stalker_run +}; + +void +stalker_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage) +{ + if (!self) + { + return; + } + + if (self->deadflag == DEAD_DEAD) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (self->groundentity == NULL) + { + return; + } + + /* if we're reactivating or false dying, ignore the pain. */ + if ((self->monsterinfo.currentmove == &stalker_move_false_death_end) || + (self->monsterinfo.currentmove == &stalker_move_false_death_start)) + { + return; + } + + if (self->monsterinfo.currentmove == &stalker_move_false_death) + { + stalker_reactivate(self); + return; + } + + if ((self->health > 0) && (self->health < (self->max_health / 4))) + { + if (random() < (0.2 * skill->value)) + { + if (!STALKER_ON_CEILING(self) || stalker_ok_to_transition(self)) + { + stalker_false_death_start(self); + return; + } + } + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 3; + + if (damage > 10) /* don't react unless the damage was significant */ + { + /* stalker should dodge jump periodically to help avoid damage. */ + if (self->groundentity && (random() < 0.5)) + { + stalker_dodge_jump(self); + } + else + { + self->monsterinfo.currentmove = &stalker_move_pain; + } + + gi.sound(self, CHAN_WEAPON, sound_pain, 1, ATTN_NORM, 0); + } +} + +void +stalker_shoot_attack(edict_t *self) +{ + vec3_t offset, start, f, r, dir; + vec3_t end; + float time, dist; + trace_t trace; + + if (!self) + { + return; + } + + if (!has_valid_enemy(self)) + { + return; + } + + if (self->groundentity && (random() < 0.33)) + { + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + dist = VectorLength(dir); + + if ((dist > 256) || (random() < 0.5)) + { + stalker_do_pounce(self, self->enemy->s.origin); + } + else + { + stalker_jump_straightup(self); + } + } + + AngleVectors(self->s.angles, f, r, NULL); + VectorSet(offset, 24, 0, 6); + G_ProjectSource(self->s.origin, offset, f, r, start); + + VectorSubtract(self->enemy->s.origin, start, dir); + + if (random() < (0.20 + 0.1 * skill->value)) + { + dist = VectorLength(dir); + time = dist / 1000; + VectorMA(self->enemy->s.origin, time, self->enemy->velocity, end); + VectorSubtract(end, start, dir); + } + else + { + VectorCopy(self->enemy->s.origin, end); + } + + trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT); + + if ((trace.ent == self->enemy) || (trace.ent == world)) + { + monster_fire_blaster2(self, start, dir, 15, 800, MZ2_STALKER_BLASTER, EF_BLASTER); + } +} + +void +stalker_shoot_attack2(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < (0.4 + (0.1 * (float)skill->value))) + { + stalker_shoot_attack(self); + } +} + +static mframe_t stalker_frames_shoot[] = { + {ai_charge, 13, NULL}, + {ai_charge, 17, stalker_shoot_attack}, + {ai_charge, 21, NULL}, + {ai_charge, 18, stalker_shoot_attack2} +}; + +mmove_t stalker_move_shoot = { + FRAME_run01, + FRAME_run04, + stalker_frames_shoot, + stalker_run +}; + +void +stalker_attack_ranged(edict_t *self) +{ + if (!self) + { + return; + } + + if (!has_valid_enemy(self)) + { + return; + } + + /* circle strafe stuff */ + if (random() > (1.0 - (0.5 / (float)(skill->value)))) + { + self->monsterinfo.attack_state = AS_STRAIGHT; + } + else + { + if (random() <= 0.5) /* switch directions */ + { + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + } + + self->monsterinfo.attack_state = AS_SLIDING; + } + + self->monsterinfo.currentmove = &stalker_move_shoot; +} + +void +stalker_swing_attack(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, MELEE_DISTANCE, 0, 0); + + if (fire_hit(self, aim, (5 + (rand() % 5)), 50)) + { + if (self->s.frame < FRAME_attack08) + { + gi.sound(self, CHAN_WEAPON, sound_punch_hit2, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, sound_punch_hit1, 1, ATTN_NORM, 0); + } + } +} + +static mframe_t stalker_frames_swing_l[] = { + {ai_charge, 2, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 6, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 5, stalker_swing_attack}, + {ai_charge, 5, NULL}, + {ai_charge, 5, NULL}, + {ai_charge, 5, NULL} /* stalker_swing_check_l */ +}; + +mmove_t stalker_move_swing_l = { + FRAME_attack01, + FRAME_attack08, + stalker_frames_swing_l, + stalker_run +}; + +static mframe_t stalker_frames_swing_r[] = { + {ai_charge, 4, NULL}, + {ai_charge, 6, NULL}, + {ai_charge, 6, stalker_swing_attack}, + {ai_charge, 10, NULL}, + {ai_charge, 5, NULL} /* stalker_swing_check_r */ +}; + +mmove_t stalker_move_swing_r = { + FRAME_attack11, + FRAME_attack15, + stalker_frames_swing_r, + stalker_run +}; + +void +stalker_attack_melee(edict_t *self) +{ + if (!self) + { + return; + } + + if (!has_valid_enemy(self)) + { + return; + } + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &stalker_move_swing_l; + } + else + { + self->monsterinfo.currentmove = &stalker_move_swing_r; + } +} + +static void +calcJumpAngle(vec3_t start, vec3_t end, float velocity, vec3_t angles) +{ + float distV, distH; + float one, cosU; + float l, U; + vec3_t dist; + + VectorSubtract(end, start, dist); + distH = (float)sqrt(dist[0] * dist[0] + dist[1] * dist[1]); + distV = dist[2]; + + if (distV < 0) + { + distV = 0 - distV; + } + + if (distV) + { + l = (float)sqrt(distH * distH + distV * distV); + U = (float)atan(distV / distH); + + if (dist[2] > 0) + { + U = (float)0.0 - U; + } + + angles[2] = 0.0; + + cosU = (float)cos(U); + one = l * FAUX_GRAVITY * (cosU * cosU); + one = one / (velocity * velocity); + one = one - (float)sin(U); + angles[0] = (float)asin(one); + + if (isnan(angles[0])) + { + angles[2] = 1.0; + } + + angles[1] = (float)PI - angles[0]; + + if (isnan(angles[1])) + { + angles[2] = 1.0; + } + + angles[0] = RAD2DEG((angles[0] - U) / 2.0); + angles[1] = RAD2DEG((angles[1] - U) / 2.0); + } + else + { + l = (float)sqrt(distH * distH + distV * distV); + + angles[2] = 0.0; + + one = l * FAUX_GRAVITY; + one = one / (velocity * velocity); + angles[0] = (float)asin(one); + + if (isnan(angles[0])) + { + angles[2] = 1.0; + } + + angles[1] = (float)PI - angles[0]; + + if (isnan(angles[1])) + { + angles[2] = 1.0; + } + + angles[0] = RAD2DEG((angles[0]) / 2.0); + angles[1] = RAD2DEG((angles[1]) / 2.0); + } +} + +int +stalker_check_lz(edict_t *self, edict_t *target, vec3_t dest) +{ + vec3_t jumpLZ; + + if (!self || !target) + { + return 0; + } + + if ((gi.pointcontents(dest) & MASK_WATER) || (target->waterlevel)) + { + return false; + } + + if (!target->groundentity) + { + return false; + } + + /* check under the player's four corners + if they're not solid, bail. */ + jumpLZ[0] = self->enemy->mins[0]; + jumpLZ[1] = self->enemy->mins[1]; + jumpLZ[2] = self->enemy->mins[2] - 0.25; + + if (!(gi.pointcontents(jumpLZ) & MASK_SOLID)) + { + return false; + } + + jumpLZ[0] = self->enemy->maxs[0]; + jumpLZ[1] = self->enemy->mins[1]; + + if (!(gi.pointcontents(jumpLZ) & MASK_SOLID)) + { + return false; + } + + jumpLZ[0] = self->enemy->maxs[0]; + jumpLZ[1] = self->enemy->maxs[1]; + + if (!(gi.pointcontents(jumpLZ) & MASK_SOLID)) + { + return false; + } + + jumpLZ[0] = self->enemy->mins[0]; + jumpLZ[1] = self->enemy->maxs[1]; + + if (!(gi.pointcontents(jumpLZ) & MASK_SOLID)) + { + return false; + } + + return true; +} + +int +stalker_do_pounce(edict_t *self, vec3_t dest) +{ + vec3_t forward, right; + vec3_t dist; + vec_t length; + vec3_t jumpAngles; + vec3_t jumpLZ; + float velocity = 400.1; + trace_t trace; + int preferHighJump; + + if (!self) + { + return 0; + } + + /* don't pounce when we're on the ceiling */ + if (STALKER_ON_CEILING(self)) + { + return false; + } + + if (!stalker_check_lz(self, self->enemy, dest)) + { + return false; + } + + VectorSubtract(dest, self->s.origin, dist); + + /* make sure we're pointing in that direction 15deg margin of error. */ + vectoangles2(dist, jumpAngles); + + if (fabsf(jumpAngles[YAW] - self->s.angles[YAW]) > 45) + { + return false; /* not facing the player... */ + } + + self->ideal_yaw = jumpAngles[YAW]; + M_ChangeYaw(self); + + length = VectorLength(dist); + + if (length > 450) + { + return false; /* can't jump that far... */ + } + + VectorCopy(dest, jumpLZ); + + preferHighJump = 0; + + /* if we're having to jump up a distance, jump a little too high to compensate. */ + if (dist[2] >= 32.0) + { + preferHighJump = 1; + jumpLZ[2] += 32; + } + + trace = gi.trace(self->s.origin, vec3_origin, vec3_origin, dest, + self, MASK_MONSTERSOLID); + + if ((trace.fraction < 1) && (trace.ent != self->enemy)) + { + preferHighJump = 1; + } + + /* find a valid angle/velocity combination */ + while (velocity <= 800) + { + calcJumpAngle(self->s.origin, jumpLZ, velocity, jumpAngles); + + if ((!isnan(jumpAngles[0])) || (!isnan(jumpAngles[1]))) + { + break; + } + + velocity += 200; + } + + if (!preferHighJump && (!isnan(jumpAngles[0]))) + { + AngleVectors(self->s.angles, forward, right, NULL); + VectorNormalize(forward); + + VectorScale(forward, velocity * cos(DEG2RAD(jumpAngles[0])), self->velocity); + self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[0])) + (0.5 * sv_gravity->value * FRAMETIME); + return 1; + } + + if (!isnan(jumpAngles[1])) + { + AngleVectors(self->s.angles, forward, right, NULL); + VectorNormalize(forward); + + VectorScale(forward, velocity * cos(DEG2RAD(jumpAngles[1])), self->velocity); + self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[1])) + (0.5 * sv_gravity->value * FRAMETIME); + return 1; + } + + return 0; +} + +void +stalker_jump_straightup(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->deadflag == DEAD_DEAD) + { + return; + } + + if (STALKER_ON_CEILING(self)) + { + if (stalker_ok_to_transition(self)) + { + self->gravityVector[2] = -1; + self->s.angles[2] += 180.0; + + if (self->s.angles[2] > 360.0) + { + self->s.angles[2] -= 360.0; + } + + self->groundentity = NULL; + } + } + else if (self->groundentity) /* make sure we're standing on SOMETHING... */ + { + self->velocity[0] += ((random() * 10) - 5); + self->velocity[1] += ((random() * 10) - 5); + self->velocity[2] += -400 * self->gravityVector[2]; + + if (stalker_ok_to_transition(self)) + { + self->gravityVector[2] = 1; + self->s.angles[2] = 180.0; + self->groundentity = NULL; + } + } +} + +static mframe_t stalker_frames_jump_straightup[] = { + {ai_move, 1, stalker_jump_straightup}, + {ai_move, 1, stalker_jump_wait_land}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL} +}; + +mmove_t stalker_move_jump_straightup = { + FRAME_jump04, + FRAME_jump07, + stalker_frames_jump_straightup, + stalker_run +}; + +void +stalker_dodge_jump(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &stalker_move_jump_straightup; +} + +static mframe_t stalker_frames_dodge_run[] = { + {ai_run, 13, NULL}, + {ai_run, 17, NULL}, + {ai_run, 21, NULL}, + {ai_run, 18, monster_done_dodge} +}; + +mmove_t stalker_move_dodge_run = { + FRAME_run01, + FRAME_run04, + stalker_frames_dodge_run, + NULL +}; + +void +stalker_dodge(edict_t *self, edict_t *attacker, float eta, trace_t *tr /* unused */) +{ + if (!self || !attacker) + { + return; + } + + if (!self->groundentity || (self->health <= 0)) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + return; + } + + if ((eta < 0.1) || (eta > 5)) + { + return; + } + + /* this will override the foundtarget call of stalker_run */ + stalker_dodge_jump(self); +} + +void +stalker_jump_down(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + monster_jump_start(self); + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 100, forward, self->velocity); + VectorMA(self->velocity, 300, up, self->velocity); +} + +void +stalker_jump_up(edict_t *self) +{ + vec3_t forward, up; + + if (!self) + { + return; + } + + monster_jump_start(self); + + AngleVectors(self->s.angles, forward, NULL, up); + VectorMA(self->velocity, 200, forward, self->velocity); + VectorMA(self->velocity, 450, up, self->velocity); +} + +void +stalker_jump_wait_land(edict_t *self) +{ + if (!self) + { + return; + } + + if ((random() < (0.3 + (0.1 * (float)(skill->value)))) && + (level.time >= self->monsterinfo.attack_finished)) + { + self->monsterinfo.attack_finished = level.time + 0.3; + stalker_shoot_attack(self); + } + + if (self->groundentity == NULL) + { + self->gravity = 1.3; + self->monsterinfo.nextframe = self->s.frame; + + if (monster_jump_finished(self)) + { + self->gravity = 1; + self->monsterinfo.nextframe = self->s.frame + 1; + } + } + else + { + self->gravity = 1; + self->monsterinfo.nextframe = self->s.frame + 1; + } +} + +static mframe_t stalker_frames_jump_up[] = { + {ai_move, -8, NULL}, + {ai_move, -8, NULL}, + {ai_move, -8, NULL}, + {ai_move, -8, NULL}, + + {ai_move, 0, stalker_jump_up}, + {ai_move, 0, stalker_jump_wait_land}, + {ai_move, 0, NULL} +}; + +mmove_t stalker_move_jump_up = { + FRAME_jump01, + FRAME_jump07, + stalker_frames_jump_up, + stalker_run +}; + +static mframe_t stalker_frames_jump_down[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, stalker_jump_down}, + {ai_move, 0, stalker_jump_wait_land}, + {ai_move, 0, NULL} +}; + +mmove_t stalker_move_jump_down = { + FRAME_jump01, + FRAME_jump07, + stalker_frames_jump_down, + stalker_run +}; + +void +stalker_jump(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->enemy) + { + return; + } + + if (self->enemy->absmin[2] >= self->absmin[2]) + { + self->monsterinfo.currentmove = &stalker_move_jump_up; + } + else + { + self->monsterinfo.currentmove = &stalker_move_jump_down; + } +} + +qboolean +stalker_blocked(edict_t *self, float dist) +{ + qboolean onCeiling; + + if (!self) + { + return false; + } + + if (!has_valid_enemy(self)) + { + return false; + } + + onCeiling = false; + + if (self->gravityVector[2] > 0) + { + onCeiling = true; + } + + if (!onCeiling) + { + if (visible(self, self->enemy)) + { + stalker_do_pounce(self, self->enemy->s.origin); + return true; + } + + if (blocked_checkjump(self, dist, 256, 68)) + { + stalker_jump(self); + return true; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + } + else + { + if (stalker_ok_to_transition(self)) + { + self->gravityVector[2] = -1; + self->s.angles[2] += 180.0; + + if (self->s.angles[2] > 360.0) + { + self->s.angles[2] -= 360.0; + } + + self->groundentity = NULL; + + return true; + } + } + + return false; +} + +void +stalker_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -28, -28, -18); + VectorSet(self->maxs, 28, 28, -4); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +static mframe_t stalker_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, -5, NULL}, + {ai_move, -10, NULL}, + {ai_move, -20, NULL}, + + {ai_move, -10, NULL}, + {ai_move, -10, NULL}, + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + + {ai_move, 0, NULL} +}; + +mmove_t stalker_move_death = { + FRAME_death01, + FRAME_death09, + stalker_frames_death, + stalker_dead +}; + +void +stalker_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage, vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + /* dude bit it, make him fall! */ + self->movetype = MOVETYPE_TOSS; + self->s.angles[2] = 0; + VectorSet(self->gravityVector, 0, 0, -1); + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 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, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &stalker_move_death; +} + +/* + * QUAKED monster_stalker (1 .5 0) (-28 -28 -18) (28 28 18) Ambush Trigger_Spawn Sight OnRoof + * Spider Monster + * + * ONROOF - Monster starts sticking to the roof. + */ +void +SP_monster_stalker(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain = gi.soundindex("stalker/pain.wav"); + sound_die = gi.soundindex("stalker/death.wav"); + sound_sight = gi.soundindex("stalker/sight.wav"); + sound_punch_hit1 = gi.soundindex("stalker/melee1.wav"); + sound_punch_hit2 = gi.soundindex("stalker/melee2.wav"); + sound_idle = gi.soundindex("stalker/idle.wav"); + + gi.modelindex("models/proj/laser2/tris.md2"); + + self->s.modelindex = gi.modelindex("models/monsters/stalker/tris.md2"); + VectorSet(self->mins, -28, -28, -18); + VectorSet(self->maxs, 28, 28, 18); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->health = 250; + self->gib_health = -50; + self->mass = 250; + self->viewheight = 15; + + self->pain = stalker_pain; + self->die = stalker_die; + + self->monsterinfo.stand = stalker_stand; + self->monsterinfo.walk = stalker_walk; + self->monsterinfo.run = stalker_run; + self->monsterinfo.attack = stalker_attack_ranged; + self->monsterinfo.sight = stalker_sight; + self->monsterinfo.idle = stalker_idle; + self->monsterinfo.dodge = stalker_dodge; + self->monsterinfo.blocked = stalker_blocked; + self->monsterinfo.melee = stalker_attack_melee; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &stalker_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + self->monsterinfo.aiflags |= AI_WALK_WALLS; + + if (self->spawnflags & 8) + { + self->s.angles[2] = 180; + self->gravityVector[2] = 1; + } + + walkmonster_start(self); +} diff --git a/src/game/monster/stalker/stalker.h b/src/game/monster/stalker/stalker.h new file mode 100644 index 000000000..f68d45a17 --- /dev/null +++ b/src/game/monster/stalker/stalker.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Stalker animations. + * + * ======================================================================= + */ + +#define FRAME_idle01 0 +#define FRAME_idle02 1 +#define FRAME_idle03 2 +#define FRAME_idle04 3 +#define FRAME_idle05 4 +#define FRAME_idle06 5 +#define FRAME_idle07 6 +#define FRAME_idle08 7 +#define FRAME_idle09 8 +#define FRAME_idle10 9 +#define FRAME_idle11 10 +#define FRAME_idle12 11 +#define FRAME_idle13 12 +#define FRAME_idle14 13 +#define FRAME_idle15 14 +#define FRAME_idle16 15 +#define FRAME_idle17 16 +#define FRAME_idle18 17 +#define FRAME_idle19 18 +#define FRAME_idle20 19 +#define FRAME_idle21 20 +#define FRAME_idle201 21 +#define FRAME_idle202 22 +#define FRAME_idle203 23 +#define FRAME_idle204 24 +#define FRAME_idle205 25 +#define FRAME_idle206 26 +#define FRAME_idle207 27 +#define FRAME_idle208 28 +#define FRAME_idle209 29 +#define FRAME_idle210 30 +#define FRAME_idle211 31 +#define FRAME_idle212 32 +#define FRAME_idle213 33 +#define FRAME_walk01 34 +#define FRAME_walk02 35 +#define FRAME_walk03 36 +#define FRAME_walk04 37 +#define FRAME_walk05 38 +#define FRAME_walk06 39 +#define FRAME_walk07 40 +#define FRAME_walk08 41 +#define FRAME_jump01 42 +#define FRAME_jump02 43 +#define FRAME_jump03 44 +#define FRAME_jump04 45 +#define FRAME_jump05 46 +#define FRAME_jump06 47 +#define FRAME_jump07 48 +#define FRAME_run01 49 +#define FRAME_run02 50 +#define FRAME_run03 51 +#define FRAME_run04 52 +#define FRAME_attack01 53 +#define FRAME_attack02 54 +#define FRAME_attack03 55 +#define FRAME_attack04 56 +#define FRAME_attack05 57 +#define FRAME_attack06 58 +#define FRAME_attack07 59 +#define FRAME_attack08 60 +#define FRAME_attack11 61 +#define FRAME_attack12 62 +#define FRAME_attack13 63 +#define FRAME_attack14 64 +#define FRAME_attack15 65 +#define FRAME_pain01 66 +#define FRAME_pain02 67 +#define FRAME_pain03 68 +#define FRAME_pain04 69 +#define FRAME_death01 70 +#define FRAME_death02 71 +#define FRAME_death03 72 +#define FRAME_death04 73 +#define FRAME_death05 74 +#define FRAME_death06 75 +#define FRAME_death07 76 +#define FRAME_death08 77 +#define FRAME_death09 78 +#define FRAME_twitch01 79 +#define FRAME_twitch02 80 +#define FRAME_twitch03 81 +#define FRAME_twitch04 82 +#define FRAME_twitch05 83 +#define FRAME_twitch06 84 +#define FRAME_twitch07 85 +#define FRAME_twitch08 86 +#define FRAME_twitch09 87 +#define FRAME_twitch10 88 +#define FRAME_reactive01 89 +#define FRAME_reactive02 90 +#define FRAME_reactive03 91 +#define FRAME_reactive04 92 +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/supertank/supertank.c b/src/game/monster/supertank/supertank.c new file mode 100644 index 000000000..5f1fb6391 --- /dev/null +++ b/src/game/monster/supertank/supertank.c @@ -0,0 +1,953 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Supertank aka "Boss1". This enhanced version features a nice + * powershield. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "supertank.h" + +qboolean visible(edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; +static int sound_search2; + +static int tread_sound; + +void BossExplode(edict_t *self); +void supertank_dead(edict_t *self); +void supertankRocket(edict_t *self); +void supertankMachineGun(edict_t *self); +void supertank_reattack1(edict_t *self); + +void +TreadSound(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0); +} + +void +supertank_search(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0); + } +} + +static mframe_t supertank_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}, + {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 supertank_move_stand = +{ + FRAME_stand_1, + FRAME_stand_60, + supertank_frames_stand, + NULL +}; + +void +supertank_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &supertank_move_stand; +} + +static mframe_t supertank_frames_run[] = { + {ai_run, 12, TreadSound}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL}, + {ai_run, 12, NULL} +}; + +mmove_t supertank_move_run = +{ + FRAME_forwrd_1, + FRAME_forwrd_18, + supertank_frames_run, + NULL +}; + +static mframe_t supertank_frames_forward[] = { + {ai_walk, 4, TreadSound}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL} +}; + +mmove_t supertank_move_forward = +{ + FRAME_forwrd_1, + FRAME_forwrd_18, + supertank_frames_forward, + NULL +}; + +void +supertank_forward(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &supertank_move_forward; +} + +void +supertank_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &supertank_move_forward; +} + +void +supertank_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &supertank_move_stand; + } + else + { + self->monsterinfo.currentmove = &supertank_move_run; + } +} + +static mframe_t supertank_frames_turn_right[] = { + {ai_move, 0, TreadSound}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t supertank_move_turn_right = +{ + FRAME_right_1, + FRAME_right_18, + supertank_frames_turn_right, + supertank_run +}; + +static mframe_t supertank_frames_turn_left[] = { + {ai_move, 0, TreadSound}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t supertank_move_turn_left = +{ + FRAME_left_1, + FRAME_left_18, + supertank_frames_turn_left, + supertank_run +}; + +static mframe_t supertank_frames_pain3[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t supertank_move_pain3 = +{ + FRAME_pain3_9, + FRAME_pain3_12, + supertank_frames_pain3, + supertank_run +}; + +static mframe_t supertank_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t supertank_move_pain2 = +{ + FRAME_pain2_5, + FRAME_pain2_8, + supertank_frames_pain2, + supertank_run +}; + +static mframe_t supertank_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t supertank_move_pain1 = +{ + FRAME_pain1_1, + FRAME_pain1_4, + supertank_frames_pain1, + supertank_run +}; + +static mframe_t supertank_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, BossExplode} +}; + +mmove_t supertank_move_death = +{ + FRAME_death_1, + FRAME_death_24, + supertank_frames_death1, + supertank_dead +}; + +static mframe_t supertank_frames_backward[] = { + {ai_walk, 0, TreadSound}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL} +}; + +mmove_t supertank_move_backward = +{ + FRAME_backwd_1, + FRAME_backwd_18, + supertank_frames_backward, + NULL +}; + +static mframe_t supertank_frames_attack4[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t supertank_move_attack4 = +{ + FRAME_attak4_1, + FRAME_attak4_6, + supertank_frames_attack4, + supertank_run +}; + +static mframe_t supertank_frames_attack3[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t supertank_move_attack3 = +{ + FRAME_attak3_1, + FRAME_attak3_27, + supertank_frames_attack3, + supertank_run +}; + +static mframe_t supertank_frames_attack2[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, supertankRocket}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, supertankRocket}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, supertankRocket}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t supertank_move_attack2 = +{ + FRAME_attak2_1, + FRAME_attak2_27, + supertank_frames_attack2, + supertank_run +}; + +static mframe_t supertank_frames_attack1[] = { + {ai_charge, 0, supertankMachineGun}, + {ai_charge, 0, supertankMachineGun}, + {ai_charge, 0, supertankMachineGun}, + {ai_charge, 0, supertankMachineGun}, + {ai_charge, 0, supertankMachineGun}, + {ai_charge, 0, supertankMachineGun}, +}; + +mmove_t supertank_move_attack1 = +{ + FRAME_attak1_1, + FRAME_attak1_6, + supertank_frames_attack1, + supertank_reattack1 +}; + +static mframe_t supertank_frames_end_attack1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t supertank_move_end_attack1 = +{ + FRAME_attak1_7, + FRAME_attak1_20, + supertank_frames_end_attack1, + supertank_run +}; + +void +supertank_reattack1(edict_t *self) +{ + if (!self) + { + return; + } + + if (visible(self, self->enemy)) + { + if (random() < 0.9) + { + self->monsterinfo.currentmove = &supertank_move_attack1; + } + else + { + self->monsterinfo.currentmove = &supertank_move_end_attack1; + } + } + else + { + self->monsterinfo.currentmove = &supertank_move_end_attack1; + } +} + +void +supertank_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + /* Lessen the chance of him going into his pain frames */ + if (damage <= 25) + { + if (random() < 0.2) + { + return; + } + } + + /* Don't go into pain if he's firing his rockets */ + if (skill->value >= SKILL_HARD) + { + if ((self->s.frame >= FRAME_attak2_1) && + (self->s.frame <= FRAME_attak2_14)) + { + return; + } + } + + self->pain_debounce_time = level.time + 3; + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (damage <= 10) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &supertank_move_pain1; + } + else if (damage <= 25) + { + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &supertank_move_pain2; + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &supertank_move_pain3; + } +} + +void +supertankRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + + if (!self || !self->enemy || !self->enemy->inuse) + { + return; + } + + if (self->s.frame == FRAME_attak2_8) + { + flash_number = MZ2_SUPERTANK_ROCKET_1; + } + else if (self->s.frame == FRAME_attak2_11) + { + flash_number = MZ2_SUPERTANK_ROCKET_2; + } + else + { + flash_number = MZ2_SUPERTANK_ROCKET_3; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + + monster_fire_rocket(self, start, dir, 50, 500, flash_number); +} + +void +supertankMachineGun(edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + if (!self) + { + return; + } + + if (!self->enemy || !self->enemy->inuse) + { + return; + } + + flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + + (self->s.frame - FRAME_attak1_1); + + dir[0] = 0; + dir[1] = self->s.angles[1]; + dir[2] = 0; + + AngleVectors(dir, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + if (self->enemy) + { + VectorCopy(self->enemy->s.origin, vec); + VectorMA(vec, 0, self->enemy->velocity, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, forward); + VectorNormalize(forward); + } + + monster_fire_bullet(self, start, forward, 6, 4, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + flash_number); +} + +void +supertank_attack(edict_t *self) +{ + vec3_t vec; + float range; + + if (!self) + { + return; + } + + VectorSubtract(self->enemy->s.origin, self->s.origin, vec); + range = VectorLength(vec); + + if (range <= 160) + { + self->monsterinfo.currentmove = &supertank_move_attack1; + } + else + { + /* fire rockets more often at distance */ + if (random() < 0.3) + { + self->monsterinfo.currentmove = &supertank_move_attack1; + } + else + { + self->monsterinfo.currentmove = &supertank_move_attack2; + } + } +} + +void +supertank_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -60, -60, 0); + VectorSet(self->maxs, 60, 60, 72); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +void +BossExplode(edict_t *self) +{ + vec3_t org; + int n; + + if (!self) + { + return; + } + + self->think = BossExplode; + VectorCopy(self->s.origin, org); + org[2] += 24 + (randk() & 15); + + switch (self->count++) + { + case 0: + org[0] -= 24; + org[1] -= 24; + break; + case 1: + org[0] += 24; + org[1] += 24; + break; + case 2: + org[0] += 24; + org[1] -= 24; + break; + case 3: + org[0] -= 24; + org[1] += 24; + break; + case 4: + org[0] -= 48; + org[1] -= 48; + break; + case 5: + org[0] += 48; + org[1] += 48; + break; + case 6: + org[0] -= 48; + org[1] += 48; + break; + case 7: + org[0] += 48; + org[1] -= 48; + break; + case 8: + self->s.sound = 0; + + for (n = 0; n < 4; n++) + { + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", + 500, GIB_ORGANIC); + } + + for (n = 0; n < 8; n++) + { + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", + 500, GIB_METALLIC); + } + + ThrowGib(self, "models/objects/gibs/chest/tris.md2", + 500, GIB_ORGANIC); + ThrowHead(self, "models/objects/gibs/gear/tris.md2", + 500, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_PVS); + + self->nextthink = level.time + 0.1; +} + +void +supertank_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage /* unused */, + vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.currentmove = &supertank_move_death; +} + +qboolean +supertank_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + return false; +} + +/* + * QUAKED monster_supertank (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight Powershield + */ +void +SP_monster_supertank(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("bosstank/btkpain1.wav"); + sound_pain2 = gi.soundindex("bosstank/btkpain2.wav"); + sound_pain3 = gi.soundindex("bosstank/btkpain3.wav"); + sound_death = gi.soundindex("bosstank/btkdeth1.wav"); + sound_search1 = gi.soundindex("bosstank/btkunqv1.wav"); + sound_search2 = gi.soundindex("bosstank/btkunqv2.wav"); + + tread_sound = gi.soundindex("bosstank/btkengn1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/boss1/tris.md2"); + VectorSet(self->mins, -64, -64, 0); + VectorSet(self->maxs, 64, 64, 112); + + self->health = 1500; + self->gib_health = -500; + self->mass = 800; + + self->pain = supertank_pain; + self->die = supertank_die; + self->monsterinfo.stand = supertank_stand; + self->monsterinfo.walk = supertank_walk; + self->monsterinfo.run = supertank_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = supertank_attack; + self->monsterinfo.search = supertank_search; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = NULL; + self->monsterinfo.blocked = supertank_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &supertank_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + if (self->spawnflags & 8) + { + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 400; + } + + walkmonster_start(self); + + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; +} diff --git a/src/game/monster/supertank/supertank.h b/src/game/monster/supertank/supertank.h new file mode 100644 index 000000000..2578d3bf0 --- /dev/null +++ b/src/game/monster/supertank/supertank.h @@ -0,0 +1,283 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Supertank aka "Boss1" animations. + * + * ======================================================================= + */ + +#define FRAME_attak1_1 0 +#define FRAME_attak1_2 1 +#define FRAME_attak1_3 2 +#define FRAME_attak1_4 3 +#define FRAME_attak1_5 4 +#define FRAME_attak1_6 5 +#define FRAME_attak1_7 6 +#define FRAME_attak1_8 7 +#define FRAME_attak1_9 8 +#define FRAME_attak1_10 9 +#define FRAME_attak1_11 10 +#define FRAME_attak1_12 11 +#define FRAME_attak1_13 12 +#define FRAME_attak1_14 13 +#define FRAME_attak1_15 14 +#define FRAME_attak1_16 15 +#define FRAME_attak1_17 16 +#define FRAME_attak1_18 17 +#define FRAME_attak1_19 18 +#define FRAME_attak1_20 19 +#define FRAME_attak2_1 20 +#define FRAME_attak2_2 21 +#define FRAME_attak2_3 22 +#define FRAME_attak2_4 23 +#define FRAME_attak2_5 24 +#define FRAME_attak2_6 25 +#define FRAME_attak2_7 26 +#define FRAME_attak2_8 27 +#define FRAME_attak2_9 28 +#define FRAME_attak2_10 29 +#define FRAME_attak2_11 30 +#define FRAME_attak2_12 31 +#define FRAME_attak2_13 32 +#define FRAME_attak2_14 33 +#define FRAME_attak2_15 34 +#define FRAME_attak2_16 35 +#define FRAME_attak2_17 36 +#define FRAME_attak2_18 37 +#define FRAME_attak2_19 38 +#define FRAME_attak2_20 39 +#define FRAME_attak2_21 40 +#define FRAME_attak2_22 41 +#define FRAME_attak2_23 42 +#define FRAME_attak2_24 43 +#define FRAME_attak2_25 44 +#define FRAME_attak2_26 45 +#define FRAME_attak2_27 46 +#define FRAME_attak3_1 47 +#define FRAME_attak3_2 48 +#define FRAME_attak3_3 49 +#define FRAME_attak3_4 50 +#define FRAME_attak3_5 51 +#define FRAME_attak3_6 52 +#define FRAME_attak3_7 53 +#define FRAME_attak3_8 54 +#define FRAME_attak3_9 55 +#define FRAME_attak3_10 56 +#define FRAME_attak3_11 57 +#define FRAME_attak3_12 58 +#define FRAME_attak3_13 59 +#define FRAME_attak3_14 60 +#define FRAME_attak3_15 61 +#define FRAME_attak3_16 62 +#define FRAME_attak3_17 63 +#define FRAME_attak3_18 64 +#define FRAME_attak3_19 65 +#define FRAME_attak3_20 66 +#define FRAME_attak3_21 67 +#define FRAME_attak3_22 68 +#define FRAME_attak3_23 69 +#define FRAME_attak3_24 70 +#define FRAME_attak3_25 71 +#define FRAME_attak3_26 72 +#define FRAME_attak3_27 73 +#define FRAME_attak4_1 74 +#define FRAME_attak4_2 75 +#define FRAME_attak4_3 76 +#define FRAME_attak4_4 77 +#define FRAME_attak4_5 78 +#define FRAME_attak4_6 79 +#define FRAME_backwd_1 80 +#define FRAME_backwd_2 81 +#define FRAME_backwd_3 82 +#define FRAME_backwd_4 83 +#define FRAME_backwd_5 84 +#define FRAME_backwd_6 85 +#define FRAME_backwd_7 86 +#define FRAME_backwd_8 87 +#define FRAME_backwd_9 88 +#define FRAME_backwd_10 89 +#define FRAME_backwd_11 90 +#define FRAME_backwd_12 91 +#define FRAME_backwd_13 92 +#define FRAME_backwd_14 93 +#define FRAME_backwd_15 94 +#define FRAME_backwd_16 95 +#define FRAME_backwd_17 96 +#define FRAME_backwd_18 97 +#define FRAME_death_1 98 +#define FRAME_death_2 99 +#define FRAME_death_3 100 +#define FRAME_death_4 101 +#define FRAME_death_5 102 +#define FRAME_death_6 103 +#define FRAME_death_7 104 +#define FRAME_death_8 105 +#define FRAME_death_9 106 +#define FRAME_death_10 107 +#define FRAME_death_11 108 +#define FRAME_death_12 109 +#define FRAME_death_13 110 +#define FRAME_death_14 111 +#define FRAME_death_15 112 +#define FRAME_death_16 113 +#define FRAME_death_17 114 +#define FRAME_death_18 115 +#define FRAME_death_19 116 +#define FRAME_death_20 117 +#define FRAME_death_21 118 +#define FRAME_death_22 119 +#define FRAME_death_23 120 +#define FRAME_death_24 121 +#define FRAME_death_31 122 +#define FRAME_death_32 123 +#define FRAME_death_33 124 +#define FRAME_death_45 125 +#define FRAME_death_46 126 +#define FRAME_death_47 127 +#define FRAME_forwrd_1 128 +#define FRAME_forwrd_2 129 +#define FRAME_forwrd_3 130 +#define FRAME_forwrd_4 131 +#define FRAME_forwrd_5 132 +#define FRAME_forwrd_6 133 +#define FRAME_forwrd_7 134 +#define FRAME_forwrd_8 135 +#define FRAME_forwrd_9 136 +#define FRAME_forwrd_10 137 +#define FRAME_forwrd_11 138 +#define FRAME_forwrd_12 139 +#define FRAME_forwrd_13 140 +#define FRAME_forwrd_14 141 +#define FRAME_forwrd_15 142 +#define FRAME_forwrd_16 143 +#define FRAME_forwrd_17 144 +#define FRAME_forwrd_18 145 +#define FRAME_left_1 146 +#define FRAME_left_2 147 +#define FRAME_left_3 148 +#define FRAME_left_4 149 +#define FRAME_left_5 150 +#define FRAME_left_6 151 +#define FRAME_left_7 152 +#define FRAME_left_8 153 +#define FRAME_left_9 154 +#define FRAME_left_10 155 +#define FRAME_left_11 156 +#define FRAME_left_12 157 +#define FRAME_left_13 158 +#define FRAME_left_14 159 +#define FRAME_left_15 160 +#define FRAME_left_16 161 +#define FRAME_left_17 162 +#define FRAME_left_18 163 +#define FRAME_pain1_1 164 +#define FRAME_pain1_2 165 +#define FRAME_pain1_3 166 +#define FRAME_pain1_4 167 +#define FRAME_pain2_5 168 +#define FRAME_pain2_6 169 +#define FRAME_pain2_7 170 +#define FRAME_pain2_8 171 +#define FRAME_pain3_9 172 +#define FRAME_pain3_10 173 +#define FRAME_pain3_11 174 +#define FRAME_pain3_12 175 +#define FRAME_right_1 176 +#define FRAME_right_2 177 +#define FRAME_right_3 178 +#define FRAME_right_4 179 +#define FRAME_right_5 180 +#define FRAME_right_6 181 +#define FRAME_right_7 182 +#define FRAME_right_8 183 +#define FRAME_right_9 184 +#define FRAME_right_10 185 +#define FRAME_right_11 186 +#define FRAME_right_12 187 +#define FRAME_right_13 188 +#define FRAME_right_14 189 +#define FRAME_right_15 190 +#define FRAME_right_16 191 +#define FRAME_right_17 192 +#define FRAME_right_18 193 +#define FRAME_stand_1 194 +#define FRAME_stand_2 195 +#define FRAME_stand_3 196 +#define FRAME_stand_4 197 +#define FRAME_stand_5 198 +#define FRAME_stand_6 199 +#define FRAME_stand_7 200 +#define FRAME_stand_8 201 +#define FRAME_stand_9 202 +#define FRAME_stand_10 203 +#define FRAME_stand_11 204 +#define FRAME_stand_12 205 +#define FRAME_stand_13 206 +#define FRAME_stand_14 207 +#define FRAME_stand_15 208 +#define FRAME_stand_16 209 +#define FRAME_stand_17 210 +#define FRAME_stand_18 211 +#define FRAME_stand_19 212 +#define FRAME_stand_20 213 +#define FRAME_stand_21 214 +#define FRAME_stand_22 215 +#define FRAME_stand_23 216 +#define FRAME_stand_24 217 +#define FRAME_stand_25 218 +#define FRAME_stand_26 219 +#define FRAME_stand_27 220 +#define FRAME_stand_28 221 +#define FRAME_stand_29 222 +#define FRAME_stand_30 223 +#define FRAME_stand_31 224 +#define FRAME_stand_32 225 +#define FRAME_stand_33 226 +#define FRAME_stand_34 227 +#define FRAME_stand_35 228 +#define FRAME_stand_36 229 +#define FRAME_stand_37 230 +#define FRAME_stand_38 231 +#define FRAME_stand_39 232 +#define FRAME_stand_40 233 +#define FRAME_stand_41 234 +#define FRAME_stand_42 235 +#define FRAME_stand_43 236 +#define FRAME_stand_44 237 +#define FRAME_stand_45 238 +#define FRAME_stand_46 239 +#define FRAME_stand_47 240 +#define FRAME_stand_48 241 +#define FRAME_stand_49 242 +#define FRAME_stand_50 243 +#define FRAME_stand_51 244 +#define FRAME_stand_52 245 +#define FRAME_stand_53 246 +#define FRAME_stand_54 247 +#define FRAME_stand_55 248 +#define FRAME_stand_56 249 +#define FRAME_stand_57 250 +#define FRAME_stand_58 251 +#define FRAME_stand_59 252 +#define FRAME_stand_60 253 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/tank/tank.c b/src/game/monster/tank/tank.c new file mode 100644 index 000000000..1cb11a87c --- /dev/null +++ b/src/game/monster/tank/tank.c @@ -0,0 +1,1329 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Tank and Tank Commander. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "tank.h" + +void tank_refire_rocket(edict_t *self); +void tank_doattack_rocket(edict_t *self); +void tank_reattack_blaster(edict_t *self); +void tank_walk(edict_t *self); +void tank_run(edict_t *self); +void Use_Boss3(edict_t * ent, edict_t * other, edict_t * activator); + +static int sound_thud; +static int sound_pain; +static int sound_idle; +static int sound_die; +static int sound_step; +static int sound_sight; +static int sound_windup; +static int sound_strike; + +void +tank_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +tank_footstep(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); +} + +void +tank_thud(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0); +} + +void +tank_windup(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0); +} + +void +tank_idle(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); +} + +static mframe_t tank_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} +}; + +mmove_t tank_move_stand = +{ + FRAME_stand01, + FRAME_stand30, + tank_frames_stand, + NULL +}; + +void +tank_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &tank_move_stand; +} + +static mframe_t tank_frames_start_walk[] = { + {ai_walk, 0, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 11, tank_footstep} +}; + +mmove_t tank_move_start_walk = +{ + FRAME_walk01, + FRAME_walk04, + tank_frames_start_walk, + tank_walk +}; + +static mframe_t tank_frames_walk[] = { + {ai_walk, 4, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 3, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 4, tank_footstep}, + {ai_walk, 3, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 6, tank_footstep} +}; + +mmove_t tank_move_walk = +{ + FRAME_walk05, + FRAME_walk20, + tank_frames_walk, + NULL +}; + +static mframe_t tank_frames_stop_walk[] = { + {ai_walk, 3, NULL}, + {ai_walk, 3, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 4, tank_footstep} +}; + +mmove_t tank_move_stop_walk = +{ + FRAME_walk21, + FRAME_walk25, + tank_frames_stop_walk, + tank_stand +}; + +void +tank_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &tank_move_walk; +} + +static mframe_t tank_frames_start_run[] = { + {ai_run, 0, NULL}, + {ai_run, 6, NULL}, + {ai_run, 6, NULL}, + {ai_run, 11, tank_footstep} +}; + +mmove_t tank_move_start_run = +{ + FRAME_walk01, + FRAME_walk04, + tank_frames_start_run, + tank_run +}; + +static mframe_t tank_frames_run[] = { + {ai_run, 4, NULL}, + {ai_run, 5, NULL}, + {ai_run, 3, NULL}, + {ai_run, 2, NULL}, + {ai_run, 5, NULL}, + {ai_run, 5, NULL}, + {ai_run, 4, NULL}, + {ai_run, 4, tank_footstep}, + {ai_run, 3, NULL}, + {ai_run, 5, NULL}, + {ai_run, 4, NULL}, + {ai_run, 5, NULL}, + {ai_run, 7, NULL}, + {ai_run, 7, NULL}, + {ai_run, 6, NULL}, + {ai_run, 6, tank_footstep} +}; + +mmove_t tank_move_run = +{ + FRAME_walk05, + FRAME_walk20, + tank_frames_run, + NULL +}; + +static mframe_t tank_frames_stop_run[] = { + {ai_run, 3, NULL}, + {ai_run, 3, NULL}, + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + {ai_run, 4, tank_footstep} +}; + +mmove_t tank_move_stop_run = +{ + FRAME_walk21, + FRAME_walk25, + tank_frames_stop_run, + tank_walk +}; + +void +tank_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->enemy && self->enemy->client) + { + self->monsterinfo.aiflags |= AI_BRUTAL; + } + else + { + self->monsterinfo.aiflags &= ~AI_BRUTAL; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &tank_move_stand; + return; + } + + if ((self->monsterinfo.currentmove == &tank_move_walk) || + (self->monsterinfo.currentmove == &tank_move_start_run)) + { + self->monsterinfo.currentmove = &tank_move_run; + } + else + { + self->monsterinfo.currentmove = &tank_move_start_run; + } +} + +static mframe_t tank_frames_pain1[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t tank_move_pain1 = +{ + FRAME_pain101, + FRAME_pain104, + tank_frames_pain1, + tank_run +}; + +static mframe_t tank_frames_pain2[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t tank_move_pain2 = +{ + FRAME_pain201, + FRAME_pain205, + tank_frames_pain2, + tank_run +}; + +static mframe_t tank_frames_pain3[] = { + {ai_move, -7, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 3, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, tank_footstep} +}; + +mmove_t tank_move_pain3 = +{ + FRAME_pain301, + FRAME_pain316, + tank_frames_pain3, + tank_run +}; + +void +tank_pain(edict_t *self, edict_t *other /* other */, + float kick /* other */, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum |= 1; + } + + if (damage <= 10) + { + return; + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + if (damage <= 30) + { + if (random() > 0.2) + { + return; + } + } + + /* If hard or nightmare, don't go into pain while attacking */ + if (skill->value >= SKILL_HARD) + { + if ((self->s.frame >= FRAME_attak301) && + (self->s.frame <= FRAME_attak330)) + { + return; + } + + if ((self->s.frame >= FRAME_attak101) && + (self->s.frame <= FRAME_attak116)) + { + return; + } + } + + self->pain_debounce_time = level.time + 3; + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if (damage <= 30) + { + self->monsterinfo.currentmove = &tank_move_pain1; + } + else if (damage <= 60) + { + self->monsterinfo.currentmove = &tank_move_pain2; + } + else + { + self->monsterinfo.currentmove = &tank_move_pain3; + } +} + +void +TankBlaster(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t end; + vec3_t dir; + int flash_number; + + if (!self) + { + return; + } + + if (self->s.frame == FRAME_attak110) + { + flash_number = MZ2_TANK_BLASTER_1; + } + else if (self->s.frame == FRAME_attak113) + { + flash_number = MZ2_TANK_BLASTER_2; + } + else + { + flash_number = MZ2_TANK_BLASTER_3; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, dir); + + monster_fire_blaster(self, start, dir, 30, 800, flash_number, EF_BLASTER); +} + +void +TankStrike(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0); +} + +void +TankRocket(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + int flash_number; + trace_t trace; + int rocketSpeed; + vec3_t target; + qboolean blindfire = false; + + if (!self || !self->enemy || !self->enemy->inuse) + { + return; + } + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + blindfire = true; + } + else + { + blindfire = false; + } + + if (self->s.frame == FRAME_attak324) + { + flash_number = MZ2_TANK_ROCKET_1; + } + else if (self->s.frame == FRAME_attak327) + { + flash_number = MZ2_TANK_ROCKET_2; + } + else + { + flash_number = MZ2_TANK_ROCKET_3; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + rocketSpeed = 500 + (100 * skill->value); + + if (blindfire) + { + VectorCopy (self->monsterinfo.blind_fire_target, target); + } + else + { + VectorCopy (self->enemy->s.origin, target); + } + + if (blindfire) + { + VectorCopy(target, vec); + VectorSubtract(vec, start, dir); + } + else if(random() < 0.66 || (start[2] < self->enemy->absmin[2])) + { + // Don't shoot at the feed if enemy is above. + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, dir); + } + else + { + // Shoot at the feed. + VectorCopy(self->enemy->s.origin, vec); + vec[2] = self->enemy->absmin[2]; + VectorSubtract(vec, start, dir); + } + + // Lead target: 20, 35, 50, 65 chance of leading. + if ((!blindfire) && ((random() < (0.2 + ((3 - skill->value) * 0.15))))) + { + float dist; + float time; + + dist = VectorLength(dir); + time = dist/rocketSpeed; + VectorMA(vec, time, self->enemy->velocity, vec); + VectorSubtract(vec, start, dir); + } + + VectorNormalize(dir); + + if (blindfire) + { + /* blindfire has different fail criteria for the trace */ + if (!blind_rocket_ok(self, start, right, target, 20.0f, dir)) + { + return; + } + } + else + { + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + + if (((trace.ent != self->enemy) && (trace.ent != world)) || + ((trace.fraction <= 0.5f) && !trace.ent->client)) + { + return; + } + } + + monster_fire_rocket(self, start, dir, 50, rocketSpeed, flash_number); +} + +void +TankMachineGun(edict_t *self) +{ + vec3_t dir; + vec3_t vec; + vec3_t start; + vec3_t forward, right; + int flash_number; + + if (!self || !self->enemy || !self->enemy->inuse) + { + return; + } + + flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406); + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], + forward, right, start); + + if (self->enemy) + { + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, vec); + vectoangles(vec, vec); + dir[0] = vec[0]; + } + else + { + dir[0] = 0; + } + + if (self->s.frame <= FRAME_attak415) + { + dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411); + } + else + { + dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419); + } + + dir[2] = 0; + + AngleVectors(dir, forward, NULL, NULL); + + monster_fire_bullet(self, start, forward, 20, 4, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + flash_number); +} + +static mframe_t tank_frames_attack_blast[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, -1, NULL}, + {ai_charge, -2, NULL}, + {ai_charge, -1, NULL}, + {ai_charge, -1, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, TankBlaster}, /* 10 */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, TankBlaster}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, TankBlaster} /* 16 */ +}; + +mmove_t tank_move_attack_blast = +{ + FRAME_attak101, + FRAME_attak116, + tank_frames_attack_blast, + tank_reattack_blaster +}; + +static mframe_t tank_frames_reattack_blast[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, TankBlaster}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, TankBlaster} /* 16 */ +}; + +mmove_t tank_move_reattack_blast = +{ + FRAME_attak111, + FRAME_attak116, + tank_frames_reattack_blast, + tank_reattack_blaster +}; + +static mframe_t tank_frames_attack_post_blast[] = { + {ai_move, 0, NULL}, /* 17 */ + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL}, + {ai_move, -2, tank_footstep} /* 22 */ +}; + +mmove_t tank_move_attack_post_blast = +{ + FRAME_attak117, + FRAME_attak122, + tank_frames_attack_post_blast, + tank_run +}; + +void +tank_reattack_blaster(edict_t *self) +{ + if (!self) + { + return; + } + + if (skill->value >= SKILL_HARD) + { + if (visible(self, self->enemy)) + { + if (self->enemy->health > 0) + { + if (random() <= 0.6) + { + self->monsterinfo.currentmove = &tank_move_reattack_blast; + return; + } + } + } + } + + self->monsterinfo.currentmove = &tank_move_attack_post_blast; +} + +void +tank_poststrike(edict_t *self) +{ + if (!self) + { + return; + } + + self->enemy = NULL; + tank_run(self); +} + +static mframe_t tank_frames_attack_strike[] = { + {ai_move, 3, NULL}, + {ai_move, 2, NULL}, + {ai_move, 2, NULL}, + {ai_move, 1, NULL}, + {ai_move, 6, NULL}, + {ai_move, 7, NULL}, + {ai_move, 9, tank_footstep}, + {ai_move, 2, NULL}, + {ai_move, 1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 2, tank_footstep}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -2, NULL}, + {ai_move, -2, NULL}, + {ai_move, 0, tank_windup}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, TankStrike}, + {ai_move, 0, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, -3, NULL}, + {ai_move, -10, NULL}, + {ai_move, -10, NULL}, + {ai_move, -2, NULL}, + {ai_move, -3, NULL}, + {ai_move, -2, tank_footstep} +}; + +mmove_t tank_move_attack_strike = +{ + FRAME_attak201, + FRAME_attak238, + tank_frames_attack_strike, + tank_poststrike +}; + +static mframe_t tank_frames_attack_pre_rocket[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, /* 10 */ + + {ai_charge, 0, NULL}, + {ai_charge, 1, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 7, NULL}, + {ai_charge, 7, NULL}, + {ai_charge, 7, tank_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, /* 20 */ + + {ai_charge, -3, NULL} +}; + +mmove_t tank_move_attack_pre_rocket = +{ + FRAME_attak301, + FRAME_attak321, + tank_frames_attack_pre_rocket, + tank_doattack_rocket +}; + +static mframe_t tank_frames_attack_fire_rocket[] = { + {ai_charge, -3, NULL}, /* Loop Start 22 */ + {ai_charge, 0, NULL}, + {ai_charge, 0, TankRocket}, /* 24 */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, TankRocket}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, -1, TankRocket} /* 30 Loop End */ +}; + +mmove_t tank_move_attack_fire_rocket = +{ + FRAME_attak322, + FRAME_attak330, + tank_frames_attack_fire_rocket, + tank_refire_rocket +}; + +static mframe_t tank_frames_attack_post_rocket[] = { + {ai_charge, 0, NULL}, /* 31 */ + {ai_charge, -1, NULL}, + {ai_charge, -1, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 3, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, /* 40 */ + + {ai_charge, 0, NULL}, + {ai_charge, -9, NULL}, + {ai_charge, -8, NULL}, + {ai_charge, -7, NULL}, + {ai_charge, -1, NULL}, + {ai_charge, -1, tank_footstep}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, /* 50 */ + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t tank_move_attack_post_rocket = +{ + FRAME_attak331, + FRAME_attak353, + tank_frames_attack_post_rocket, + tank_run +}; + +static mframe_t tank_frames_attack_chain[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {NULL, 0, TankMachineGun}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t tank_move_attack_chain = +{ + FRAME_attak401, + FRAME_attak429, + tank_frames_attack_chain, + tank_run +}; + +void +tank_refire_rocket(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &tank_move_attack_post_rocket; + return; + } + + /* Only on hard or nightmare */ + if (skill->value >= SKILL_HARD) + { + if (self->enemy->health > 0) + { + if (visible(self, self->enemy)) + { + if (random() <= 0.4) + { + self->monsterinfo.currentmove = + &tank_move_attack_fire_rocket; + return; + } + } + } + } + + self->monsterinfo.currentmove = &tank_move_attack_post_rocket; +} + +void +tank_doattack_rocket(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &tank_move_attack_fire_rocket; +} + +void +tank_attack(edict_t *self) +{ + vec3_t vec; + float range; + float r; + float chance; + + if (!self || !self->enemy || !self->enemy->inuse) + { + return; + } + + if (self->enemy->health < 0) + { + self->monsterinfo.currentmove = &tank_move_attack_strike; + self->monsterinfo.aiflags &= ~AI_BRUTAL; + return; + } + + if (self->monsterinfo.attack_state == AS_BLIND) + { + if (self->monsterinfo.blind_fire_delay < 1.0) + { + chance = 1.0; + } + else if (self->monsterinfo.blind_fire_delay < 7.5) + { + chance = 0.4; + } + else + { + chance = 0.1; + } + + r = random(); + + self->monsterinfo.blind_fire_delay += 3.2 + 2.0 + random() * 3.0; + + // Don't shoot at the origin. + if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin)) + { + return; + } + + // Don't shoot if the dice say not to. + if (r > chance) + { + return; + } + + // turn on manual steering to signal both manual steering and blindfire + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &tank_move_attack_fire_rocket; + self->monsterinfo.attack_finished = level.time + 3.0 + 2*random(); + self->pain_debounce_time = level.time + 5.0; // no pain for a while + return; + } + + VectorSubtract(self->enemy->s.origin, self->s.origin, vec); + range = VectorLength(vec); + + r = random(); + + if (range <= 125) + { + if (r < 0.4) + { + self->monsterinfo.currentmove = &tank_move_attack_chain; + } + else + { + self->monsterinfo.currentmove = &tank_move_attack_blast; + } + } + else if (range <= 250) + { + if (r < 0.5) + { + self->monsterinfo.currentmove = &tank_move_attack_chain; + } + else + { + self->monsterinfo.currentmove = &tank_move_attack_blast; + } + } + else + { + if (r < 0.33) + { + self->monsterinfo.currentmove = &tank_move_attack_chain; + } + else if (r < 0.66) + { + self->monsterinfo.currentmove = &tank_move_attack_pre_rocket; + self->pain_debounce_time = level.time + 5.0; /* no pain for a while */ + } + else + { + self->monsterinfo.currentmove = &tank_move_attack_blast; + } + } +} + +void +tank_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -16, -16, -16); + VectorSet(self->maxs, 16, 16, -0); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +static mframe_t tank_frames_death1[] = { + {ai_move, -7, NULL}, + {ai_move, -2, NULL}, + {ai_move, -2, NULL}, + {ai_move, 1, NULL}, + {ai_move, 3, NULL}, + {ai_move, 6, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -3, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -4, NULL}, + {ai_move, -6, NULL}, + {ai_move, -4, NULL}, + {ai_move, -5, NULL}, + {ai_move, -7, NULL}, + {ai_move, -15, tank_thud}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t tank_move_death = +{ + FRAME_death101, + FRAME_death132, + tank_frames_death1, + tank_dead +}; + +void +tank_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /* unused */) +{ + int n; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + for (n = 0; n < 1 /*4*/; n++) + { + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", + damage, GIB_ORGANIC); + } + + for (n = 0; n < 4; n++) + { + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", + damage, GIB_METALLIC); + } + + ThrowGib(self, "models/objects/gibs/chest/tris.md2", + damage, GIB_ORGANIC); + ThrowHead(self, "models/objects/gibs/gear/tris.md2", + damage, GIB_METALLIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + { + return; + } + + /* regular death */ + gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + + self->monsterinfo.currentmove = &tank_move_death; +} + +qboolean +tank_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + return false; +} + +void +tank_stand_think(edict_t * self) +{ + if (!self) + { + return; + } + + if (self->s.frame == FRAME_stand30) + { + self->s.frame = FRAME_stand01; + } + else + { + self->s.frame++; + } + + self->nextthink = level.time + FRAMETIME; +} + +/* + * QUAKED monster_tank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight + */ + +/* + * QUAKED monster_tank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight + */ + +/*QUAKED monster_tank_stand (1 .5 0) (-32 -32 0) (32 32 90) + +Just stands and cycles in one place until targeted, then teleports away. +N64 edition! +*/ + +void +SP_monster_tank(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2"); + VectorSet(self->mins, -32, -32, -16); + VectorSet(self->maxs, 32, 32, 72); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_pain = gi.soundindex("tank/tnkpain2.wav"); + sound_thud = gi.soundindex("tank/tnkdeth2.wav"); + sound_idle = gi.soundindex("tank/tnkidle1.wav"); + sound_die = gi.soundindex("tank/death.wav"); + sound_step = gi.soundindex("tank/step.wav"); + sound_windup = gi.soundindex("tank/tnkatck4.wav"); + sound_strike = gi.soundindex("tank/tnkatck5.wav"); + sound_sight = gi.soundindex("tank/sight1.wav"); + + gi.soundindex("tank/tnkatck1.wav"); + gi.soundindex("tank/tnkatk2a.wav"); + gi.soundindex("tank/tnkatk2b.wav"); + gi.soundindex("tank/tnkatk2c.wav"); + gi.soundindex("tank/tnkatk2d.wav"); + gi.soundindex("tank/tnkatk2e.wav"); + gi.soundindex("tank/tnkatck3.wav"); + + if (strcmp(self->classname, "monster_tank_commander") == 0) + { + self->health = 1000; + self->gib_health = -225; + } + else + { + self->health = 750; + self->gib_health = -200; + } + + self->mass = 500; + + self->pain = tank_pain; + self->die = tank_die; + self->monsterinfo.stand = tank_stand; + self->monsterinfo.walk = tank_walk; + self->monsterinfo.run = tank_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = tank_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = tank_sight; + self->monsterinfo.idle = tank_idle; + self->monsterinfo.blocked = tank_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &tank_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + if (strcmp(self->classname, "monster_tank_stand") == 0) + { + self->monsterinfo.scale = MODEL_SCALE * 1.5f; + self->use = Use_Boss3; + self->think = tank_stand_think; + self->nextthink = level.time + FRAMETIME; + } + else + { + walkmonster_start(self); + } + + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + self->monsterinfo.blindfire = true; + + if (strcmp(self->classname, "monster_tank_commander") == 0) + { + self->s.skinnum = 2; + } +} diff --git a/src/game/monster/tank/tank.h b/src/game/monster/tank/tank.h new file mode 100644 index 000000000..bf70e1903 --- /dev/null +++ b/src/game/monster/tank/tank.h @@ -0,0 +1,323 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Tank and Tank Commander animations. + * + * ======================================================================= + */ + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_walk01 30 +#define FRAME_walk02 31 +#define FRAME_walk03 32 +#define FRAME_walk04 33 +#define FRAME_walk05 34 +#define FRAME_walk06 35 +#define FRAME_walk07 36 +#define FRAME_walk08 37 +#define FRAME_walk09 38 +#define FRAME_walk10 39 +#define FRAME_walk11 40 +#define FRAME_walk12 41 +#define FRAME_walk13 42 +#define FRAME_walk14 43 +#define FRAME_walk15 44 +#define FRAME_walk16 45 +#define FRAME_walk17 46 +#define FRAME_walk18 47 +#define FRAME_walk19 48 +#define FRAME_walk20 49 +#define FRAME_walk21 50 +#define FRAME_walk22 51 +#define FRAME_walk23 52 +#define FRAME_walk24 53 +#define FRAME_walk25 54 +#define FRAME_attak101 55 +#define FRAME_attak102 56 +#define FRAME_attak103 57 +#define FRAME_attak104 58 +#define FRAME_attak105 59 +#define FRAME_attak106 60 +#define FRAME_attak107 61 +#define FRAME_attak108 62 +#define FRAME_attak109 63 +#define FRAME_attak110 64 +#define FRAME_attak111 65 +#define FRAME_attak112 66 +#define FRAME_attak113 67 +#define FRAME_attak114 68 +#define FRAME_attak115 69 +#define FRAME_attak116 70 +#define FRAME_attak117 71 +#define FRAME_attak118 72 +#define FRAME_attak119 73 +#define FRAME_attak120 74 +#define FRAME_attak121 75 +#define FRAME_attak122 76 +#define FRAME_attak201 77 +#define FRAME_attak202 78 +#define FRAME_attak203 79 +#define FRAME_attak204 80 +#define FRAME_attak205 81 +#define FRAME_attak206 82 +#define FRAME_attak207 83 +#define FRAME_attak208 84 +#define FRAME_attak209 85 +#define FRAME_attak210 86 +#define FRAME_attak211 87 +#define FRAME_attak212 88 +#define FRAME_attak213 89 +#define FRAME_attak214 90 +#define FRAME_attak215 91 +#define FRAME_attak216 92 +#define FRAME_attak217 93 +#define FRAME_attak218 94 +#define FRAME_attak219 95 +#define FRAME_attak220 96 +#define FRAME_attak221 97 +#define FRAME_attak222 98 +#define FRAME_attak223 99 +#define FRAME_attak224 100 +#define FRAME_attak225 101 +#define FRAME_attak226 102 +#define FRAME_attak227 103 +#define FRAME_attak228 104 +#define FRAME_attak229 105 +#define FRAME_attak230 106 +#define FRAME_attak231 107 +#define FRAME_attak232 108 +#define FRAME_attak233 109 +#define FRAME_attak234 110 +#define FRAME_attak235 111 +#define FRAME_attak236 112 +#define FRAME_attak237 113 +#define FRAME_attak238 114 +#define FRAME_attak301 115 +#define FRAME_attak302 116 +#define FRAME_attak303 117 +#define FRAME_attak304 118 +#define FRAME_attak305 119 +#define FRAME_attak306 120 +#define FRAME_attak307 121 +#define FRAME_attak308 122 +#define FRAME_attak309 123 +#define FRAME_attak310 124 +#define FRAME_attak311 125 +#define FRAME_attak312 126 +#define FRAME_attak313 127 +#define FRAME_attak314 128 +#define FRAME_attak315 129 +#define FRAME_attak316 130 +#define FRAME_attak317 131 +#define FRAME_attak318 132 +#define FRAME_attak319 133 +#define FRAME_attak320 134 +#define FRAME_attak321 135 +#define FRAME_attak322 136 +#define FRAME_attak323 137 +#define FRAME_attak324 138 +#define FRAME_attak325 139 +#define FRAME_attak326 140 +#define FRAME_attak327 141 +#define FRAME_attak328 142 +#define FRAME_attak329 143 +#define FRAME_attak330 144 +#define FRAME_attak331 145 +#define FRAME_attak332 146 +#define FRAME_attak333 147 +#define FRAME_attak334 148 +#define FRAME_attak335 149 +#define FRAME_attak336 150 +#define FRAME_attak337 151 +#define FRAME_attak338 152 +#define FRAME_attak339 153 +#define FRAME_attak340 154 +#define FRAME_attak341 155 +#define FRAME_attak342 156 +#define FRAME_attak343 157 +#define FRAME_attak344 158 +#define FRAME_attak345 159 +#define FRAME_attak346 160 +#define FRAME_attak347 161 +#define FRAME_attak348 162 +#define FRAME_attak349 163 +#define FRAME_attak350 164 +#define FRAME_attak351 165 +#define FRAME_attak352 166 +#define FRAME_attak353 167 +#define FRAME_attak401 168 +#define FRAME_attak402 169 +#define FRAME_attak403 170 +#define FRAME_attak404 171 +#define FRAME_attak405 172 +#define FRAME_attak406 173 +#define FRAME_attak407 174 +#define FRAME_attak408 175 +#define FRAME_attak409 176 +#define FRAME_attak410 177 +#define FRAME_attak411 178 +#define FRAME_attak412 179 +#define FRAME_attak413 180 +#define FRAME_attak414 181 +#define FRAME_attak415 182 +#define FRAME_attak416 183 +#define FRAME_attak417 184 +#define FRAME_attak418 185 +#define FRAME_attak419 186 +#define FRAME_attak420 187 +#define FRAME_attak421 188 +#define FRAME_attak422 189 +#define FRAME_attak423 190 +#define FRAME_attak424 191 +#define FRAME_attak425 192 +#define FRAME_attak426 193 +#define FRAME_attak427 194 +#define FRAME_attak428 195 +#define FRAME_attak429 196 +#define FRAME_pain101 197 +#define FRAME_pain102 198 +#define FRAME_pain103 199 +#define FRAME_pain104 200 +#define FRAME_pain201 201 +#define FRAME_pain202 202 +#define FRAME_pain203 203 +#define FRAME_pain204 204 +#define FRAME_pain205 205 +#define FRAME_pain301 206 +#define FRAME_pain302 207 +#define FRAME_pain303 208 +#define FRAME_pain304 209 +#define FRAME_pain305 210 +#define FRAME_pain306 211 +#define FRAME_pain307 212 +#define FRAME_pain308 213 +#define FRAME_pain309 214 +#define FRAME_pain310 215 +#define FRAME_pain311 216 +#define FRAME_pain312 217 +#define FRAME_pain313 218 +#define FRAME_pain314 219 +#define FRAME_pain315 220 +#define FRAME_pain316 221 +#define FRAME_death101 222 +#define FRAME_death102 223 +#define FRAME_death103 224 +#define FRAME_death104 225 +#define FRAME_death105 226 +#define FRAME_death106 227 +#define FRAME_death107 228 +#define FRAME_death108 229 +#define FRAME_death109 230 +#define FRAME_death110 231 +#define FRAME_death111 232 +#define FRAME_death112 233 +#define FRAME_death113 234 +#define FRAME_death114 235 +#define FRAME_death115 236 +#define FRAME_death116 237 +#define FRAME_death117 238 +#define FRAME_death118 239 +#define FRAME_death119 240 +#define FRAME_death120 241 +#define FRAME_death121 242 +#define FRAME_death122 243 +#define FRAME_death123 244 +#define FRAME_death124 245 +#define FRAME_death125 246 +#define FRAME_death126 247 +#define FRAME_death127 248 +#define FRAME_death128 249 +#define FRAME_death129 250 +#define FRAME_death130 251 +#define FRAME_death131 252 +#define FRAME_death132 253 +#define FRAME_recln101 254 +#define FRAME_recln102 255 +#define FRAME_recln103 256 +#define FRAME_recln104 257 +#define FRAME_recln105 258 +#define FRAME_recln106 259 +#define FRAME_recln107 260 +#define FRAME_recln108 261 +#define FRAME_recln109 262 +#define FRAME_recln110 263 +#define FRAME_recln111 264 +#define FRAME_recln112 265 +#define FRAME_recln113 266 +#define FRAME_recln114 267 +#define FRAME_recln115 268 +#define FRAME_recln116 269 +#define FRAME_recln117 270 +#define FRAME_recln118 271 +#define FRAME_recln119 272 +#define FRAME_recln120 273 +#define FRAME_recln121 274 +#define FRAME_recln122 275 +#define FRAME_recln123 276 +#define FRAME_recln124 277 +#define FRAME_recln125 278 +#define FRAME_recln126 279 +#define FRAME_recln127 280 +#define FRAME_recln128 281 +#define FRAME_recln129 282 +#define FRAME_recln130 283 +#define FRAME_recln131 284 +#define FRAME_recln132 285 +#define FRAME_recln133 286 +#define FRAME_recln134 287 +#define FRAME_recln135 288 +#define FRAME_recln136 289 +#define FRAME_recln137 290 +#define FRAME_recln138 291 +#define FRAME_recln139 292 +#define FRAME_recln140 293 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/tarbaby/tarbaby.c b/src/game/monster/tarbaby/tarbaby.c new file mode 100644 index 000000000..f40aed5f0 --- /dev/null +++ b/src/game/monster/tarbaby/tarbaby.c @@ -0,0 +1,285 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "tarbaby.h" + +static int sound_death; +static int sound_hit; +static int sound_land; +static int sound_sight; + +void tarbaby_rejump(edict_t *self); + +static void +tarbaby_unbounce(edict_t *self) +{ + self->movetype = MOVETYPE_STEP; +} + +// Stand +static mframe_t tarbaby_frames_stand [] = +{ + {ai_stand, 0, tarbaby_unbounce} +}; +mmove_t tarbaby_move_stand = +{ + FRAME_walk1, + FRAME_walk1, + tarbaby_frames_stand, + NULL +}; + +void +tarbaby_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &tarbaby_move_stand; +} + +// Run +static mframe_t tarbaby_frames_run [] = +{ + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + {ai_run, 2, NULL}, + + {ai_run, 2, NULL} +}; +mmove_t tarbaby_move_run = +{ + FRAME_run1, + FRAME_run25, + tarbaby_frames_run, + NULL +}; + +void +tarbaby_run(edict_t *self) +{ + self->monsterinfo.currentmove = &tarbaby_move_run; +} + +// Sight +void +tarbaby_sight(edict_t *self, edict_t *other /* unused */) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +tarbaby_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->takedamage) + { + if (VectorLength(self->velocity) > 400) + { + int damage = 10 + 10 * random(); + + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, damage, 0, 0, 0); + gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + } + } + else + gi.sound(self, CHAN_WEAPON, sound_land, 1, ATTN_NORM, 0); + self->touch = NULL; + + if (!M_CheckBottom(self)) + { + if (self->groundentity) + { + self->monsterinfo.currentmove = &tarbaby_move_run; + self->movetype = MOVETYPE_STEP; + } + return; + } +} + +static void +tarbaby_jump_step(edict_t *self) +{ + vec3_t forward; + + self->movetype = MOVETYPE_BOUNCE; + self->touch = tarbaby_touch; + + AngleVectors (self->s.angles, forward, NULL, NULL); + self->s.origin[2] += 1; + VectorScale(forward, 600, self->velocity); + self->velocity[2] = 200 + (random() * 150); + self->groundentity = NULL; +} + +// Fly +static mframe_t tarbaby_frames_fly [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t tarbaby_move_fly = +{ + FRAME_fly1, + FRAME_fly4, + tarbaby_frames_fly, + tarbaby_rejump +}; + +void +tarbaby_fly(edict_t *self) +{ + self->monsterinfo.currentmove = &tarbaby_move_fly; +} + +// Jump +static mframe_t tarbaby_frames_jump [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, tarbaby_jump_step}, + {ai_charge, 0, NULL} +}; +mmove_t tarbaby_move_jump = +{ + FRAME_jump1, + FRAME_jump6, + tarbaby_frames_jump, + tarbaby_fly +}; + +void +tarbaby_rejump(edict_t *self) +{ + self->monsterinfo.currentmove = &tarbaby_move_jump; + self->monsterinfo.nextframe = 54; +} + +// Attack +void +tarbaby_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &tarbaby_move_jump; +} + +void +tarbaby_explode(edict_t *self) +{ + T_RadiusDamage(self, self, 120, NULL, 160, 0); + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BFG_BIGEXPLOSION); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PVS); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_ROCKET_EXPLOSION); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PVS); + + G_FreeEdict(self); +} + +// Death +void +tarbaby_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, vec3_t point /* unused */) +{ + if (self->deadflag == DEAD_DEAD) + return; + self->s.frame = FRAME_exp; + self->deadflag = DEAD_DEAD; + self->think = tarbaby_explode; + self->nextthink = level.time + 0.1; +} + +// Pain +void +tarbaby_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + +} + +/* + * QUAKED monster_tarbaby (1 .5 0) (-16, -16, -24) (16, 16, 40) Ambush Trigger_Spawn Sight + */ +void +SP_monster_tarbaby(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/tarbaby/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 40); + self->health = 80; + + sound_death = gi.soundindex("tarbaby/death1.wav"); + sound_hit = gi.soundindex("tarbaby/hit1.wav"); + sound_land = gi.soundindex("tarbaby/land1.wav"); + sound_sight = gi.soundindex("tarbaby/sight1.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = 0; + self->mass = 80; + + self->monsterinfo.stand = tarbaby_stand; + self->monsterinfo.walk = tarbaby_run; + self->monsterinfo.run = tarbaby_run; + self->monsterinfo.attack = tarbaby_attack; + self->monsterinfo.sight = tarbaby_sight; + + self->die = tarbaby_die; + self->pain = tarbaby_pain; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/tarbaby/tarbaby.h b/src/game/monster/tarbaby/tarbaby.h new file mode 100644 index 000000000..6e05a8585 --- /dev/null +++ b/src/game/monster/tarbaby/tarbaby.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Tarbaby animations. + * + * ======================================================================= + */ + +#define FRAME_walk1 0 +#define FRAME_walk2 1 +#define FRAME_walk3 2 +#define FRAME_walk4 3 +#define FRAME_walk5 4 +#define FRAME_walk6 5 +#define FRAME_walk7 6 +#define FRAME_walk8 7 +#define FRAME_walk9 8 +#define FRAME_walk10 9 +#define FRAME_walk11 10 +#define FRAME_walk12 11 +#define FRAME_walk13 12 +#define FRAME_walk14 13 +#define FRAME_walk15 14 +#define FRAME_walk16 15 +#define FRAME_walk17 16 +#define FRAME_walk18 17 +#define FRAME_walk19 18 +#define FRAME_walk20 19 +#define FRAME_walk21 20 +#define FRAME_walk22 21 +#define FRAME_walk23 22 +#define FRAME_walk24 23 +#define FRAME_walk25 24 +#define FRAME_run1 25 +#define FRAME_run2 26 +#define FRAME_run3 27 +#define FRAME_run4 28 +#define FRAME_run5 29 +#define FRAME_run6 30 +#define FRAME_run7 31 +#define FRAME_run8 32 +#define FRAME_run9 33 +#define FRAME_run10 34 +#define FRAME_run11 35 +#define FRAME_run12 36 +#define FRAME_run13 37 +#define FRAME_run14 38 +#define FRAME_run15 39 +#define FRAME_run16 40 +#define FRAME_run17 41 +#define FRAME_run18 42 +#define FRAME_run19 43 +#define FRAME_run20 44 +#define FRAME_run21 45 +#define FRAME_run22 46 +#define FRAME_run23 47 +#define FRAME_run24 48 +#define FRAME_run25 49 +#define FRAME_jump1 50 +#define FRAME_jump2 51 +#define FRAME_jump3 52 +#define FRAME_jump4 53 +#define FRAME_jump5 54 +#define FRAME_jump6 55 +#define FRAME_fly1 56 +#define FRAME_fly2 57 +#define FRAME_fly3 58 +#define FRAME_fly4 59 +#define FRAME_exp 60 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/turret/turret.c b/src/game/monster/turret/turret.c new file mode 100644 index 000000000..be73a233d --- /dev/null +++ b/src/game/monster/turret/turret.c @@ -0,0 +1,1286 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Wall mounted turrets. + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "turret.h" + +#define SPAWN_BLASTER 0x0008 +#define SPAWN_MACHINEGUN 0x0010 +#define SPAWN_ROCKET 0x0020 +#define SPAWN_HEATBEAM 0x0040 +#define SPAWN_WEAPONCHOICE 0x0078 +#define SPAWN_INSTANT_WEAPON 0x0050 +#define SPAWN_WALL_UNIT 0x0080 + +#define TURRET_BULLET_DAMAGE 4 +#define TURRET_HEAT_DAMAGE 4 + +extern qboolean FindTarget(edict_t *self); + +void turret_run(edict_t *self); +void TurretAim(edict_t *self); +void turret_sight(edict_t *self, edict_t *other); +void turret_search(edict_t *self); +void turret_stand(edict_t *self); +void turret_wake(edict_t *self); +void turret_ready_gun(edict_t *self); +void turret_run(edict_t *self); +void turret_attack(edict_t *self); + +extern void Move_Calc(edict_t *ent, vec3_t dest, void (*func)(edict_t *)); + +mmove_t turret_move_fire; +mmove_t turret_move_fire_blind; + +void +TurretAim(edict_t *self) +{ + vec3_t end, dir; + vec3_t ang; + float move, idealPitch, idealYaw, current, speed; + int orientation; + + if (!self) + { + return; + } + + if (!self->enemy || (self->enemy == world)) + { + if (!FindTarget(self)) + { + return; + } + } + + /* if turret is still in inactive mode, ready the gun, but don't aim */ + if (self->s.frame < FRAME_active01) + { + turret_ready_gun(self); + return; + } + + /* if turret is still readying, don't aim. */ + if (self->s.frame < FRAME_run01) + { + return; + } + + /* blindfire aiming here */ + if (self->monsterinfo.currentmove == &turret_move_fire_blind) + { + VectorCopy(self->monsterinfo.blind_fire_target, end); + + if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2]) + { + end[2] += self->enemy->viewheight + 10; + } + else + { + end[2] += self->enemy->mins[2] - 10; + } + } + else + { + VectorCopy(self->enemy->s.origin, end); + + if (self->enemy->client) + { + end[2] += self->enemy->viewheight; + } + } + + VectorSubtract(end, self->s.origin, dir); + vectoangles2(dir, ang); + + idealPitch = ang[PITCH]; + idealYaw = ang[YAW]; + + orientation = self->offset[1]; + + switch (orientation) + { + case -1: /* up pitch: 0 to 90 */ + + if (idealPitch < -90) + { + idealPitch += 360; + } + + if (idealPitch > -5) + { + idealPitch = -5; + } + + break; + case -2: /* down pitch: -180 to -360 */ + + if (idealPitch > -90) + { + idealPitch -= 360; + } + + if (idealPitch < -355) + { + idealPitch = -355; + } + else if (idealPitch > -185) + { + idealPitch = -185; + } + + break; + case 0: /* +X pitch: 0 to -90, -270 to -360 (or 0 to 90) */ + + if (idealPitch < -180) + { + idealPitch += 360; + } + + if (idealPitch > 85) + { + idealPitch = 85; + } + else if (idealPitch < -85) + { + idealPitch = -85; + } + + if (idealYaw > 180) + { + idealYaw -= 360; + } + + if (idealYaw > 85) + { + idealYaw = 85; + } + else if (idealYaw < -85) + { + idealYaw = -85; + } + + break; + case 90: /* +Y pitch: 0 to 90, -270 to -360 (or 0 to 90) */ + + if (idealPitch < -180) + { + idealPitch += 360; + } + + if (idealPitch > 85) + { + idealPitch = 85; + } + else if (idealPitch < -85) + { + idealPitch = -85; + } + + if (idealYaw > 270) + { + idealYaw -= 360; + } + + if (idealYaw > 175) + { + idealYaw = 175; + } + else if (idealYaw < 5) + { + idealYaw = 5; + } + + break; + case 180: /* -X pitch: 0 to 90, -270 to -360 (or 0 to 90) */ + + if (idealPitch < -180) + { + idealPitch += 360; + } + + if (idealPitch > 85) + { + idealPitch = 85; + } + else if (idealPitch < -85) + { + idealPitch = -85; + } + + if (idealYaw > 265) + { + idealYaw = 265; + } + else if (idealYaw < 95) + { + idealYaw = 95; + } + + break; + case 270: /* -Y pitch: 0 to 90, -270 to -360 (or 0 to 90) */ + + if (idealPitch < -180) + { + idealPitch += 360; + } + + if (idealPitch > 85) + { + idealPitch = 85; + } + else if (idealPitch < -85) + { + idealPitch = -85; + } + + if (idealYaw < 90) + { + idealYaw += 360; + } + + if (idealYaw > 355) + { + idealYaw = 355; + } + else if (idealYaw < 185) + { + idealYaw = 185; + } + + break; + } + + current = self->s.angles[PITCH]; + speed = self->yaw_speed; + + if (idealPitch != current) + { + move = idealPitch - current; + + while (move >= 360) + { + move -= 360; + } + + if (move >= 90) + { + move = move - 360; + } + + while (move <= -360) + { + move += 360; + } + + if (move <= -90) + { + move = move + 360; + } + + if (move > 0) + { + if (move > speed) + { + move = speed; + } + } + else + { + if (move < -speed) + { + move = -speed; + } + } + + self->s.angles[PITCH] = anglemod(current + move); + } + + current = self->s.angles[YAW]; + speed = self->yaw_speed; + + if (idealYaw != current) + { + move = idealYaw - current; + + if (move >= 180) + { + move = move - 360; + } + + if (move <= -180) + { + move = move + 360; + } + + if (move > 0) + { + if (move > speed) + { + move = speed; + } + } + else + { + if (move < -speed) + { + move = -speed; + } + } + + self->s.angles[YAW] = anglemod(current + move); + } +} + +void +turret_sight(edict_t *self, edict_t *other) +{ +} + +void +turret_search(edict_t *self) +{ +} + +static mframe_t turret_frames_stand[] = { + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL} +}; + +mmove_t turret_move_stand = { + FRAME_stand01, + FRAME_stand02, + turret_frames_stand, + NULL +}; + +void +turret_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &turret_move_stand; +} + +static mframe_t turret_frames_ready_gun[] = { + {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 turret_move_ready_gun = { + FRAME_active01, + FRAME_run01, + turret_frames_ready_gun, + turret_run +}; + +void +turret_ready_gun(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &turret_move_ready_gun; +} + +static mframe_t turret_frames_seek[] = { + {ai_walk, 0, TurretAim}, + {ai_walk, 0, TurretAim} +}; + +mmove_t turret_move_seek = { + FRAME_run01, + FRAME_run02, + turret_frames_seek, + NULL +}; + +void +turret_walk(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.frame < FRAME_run01) + { + turret_ready_gun(self); + } + else + { + self->monsterinfo.currentmove = &turret_move_seek; + } +} + +static mframe_t turret_frames_run[] = { + {ai_run, 0, TurretAim}, + {ai_run, 0, TurretAim} +}; + +mmove_t turret_move_run = { + FRAME_run01, + FRAME_run02, + turret_frames_run, + turret_run +}; + +void +turret_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.frame < FRAME_run01) + { + turret_ready_gun(self); + } + else + { + self->monsterinfo.currentmove = &turret_move_run; + } +} + +void +TurretFire(edict_t *self) +{ + vec3_t forward; + vec3_t start, end, dir; + float time, dist, chance; + trace_t trace; + int rocketSpeed = 0; + + if (!self) + { + return; + } + + TurretAim(self); + + if (!self->enemy || !self->enemy->inuse) + { + return; + } + + VectorSubtract(self->enemy->s.origin, self->s.origin, dir); + VectorNormalize(dir); + AngleVectors(self->s.angles, forward, NULL, NULL); + chance = DotProduct(dir, forward); + + if (chance < 0.98) + { + return; + } + + /* rockets fire less often than the others do. */ + if (self->spawnflags & SPAWN_ROCKET) + { + rocketSpeed = 550; + + if (skill->value == SKILL_HARD) + { + rocketSpeed += 200 * random(); + } + else if (skill->value == SKILL_HARDPLUS) + { + rocketSpeed += 100 + (200 * random()); + } + } + else if (self->spawnflags & SPAWN_BLASTER) + { + if (skill->value == SKILL_EASY) + { + rocketSpeed = 600; + } + else if (skill->value == SKILL_MEDIUM) + { + rocketSpeed = 800; + } + else + { + rocketSpeed = 1000; + } + } + + if (visible(self, self->enemy)) + { + VectorCopy(self->s.origin, start); + VectorCopy(self->enemy->s.origin, end); + + /* aim for the head. */ + if (self->enemy->client) + { + end[2] += self->enemy->viewheight; + } + else + { + end[2] += 22; + } + + VectorSubtract(end, start, dir); + dist = VectorLength(dir); + + /* check for predictive fire if distance less than 512 */ + if (!(self->spawnflags & SPAWN_INSTANT_WEAPON) && (dist < 512)) + { + chance = random(); + + /* ramp chance. easy - 50%, avg - 60%, hard - 70%, nightmare - 80% */ + chance += (3 - skill->value) * 0.1; + + if (chance < 0.8) + { + /* lead the target.... */ + time = dist / 1000; + VectorMA(end, time, self->enemy->velocity, end); + VectorSubtract(end, start, dir); + } + } + + VectorNormalize(dir); + trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT); + + if ((trace.ent == self->enemy) || (trace.ent == world)) + { + if (self->spawnflags & SPAWN_BLASTER) + { + monster_fire_blaster(self, start, dir, 20, rocketSpeed, + MZ2_TURRET_BLASTER, EF_BLASTER); + } + else if (self->spawnflags & SPAWN_MACHINEGUN) + { + monster_fire_bullet(self, start, dir, TURRET_BULLET_DAMAGE, + 0, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + MZ2_TURRET_MACHINEGUN); + } + else if (self->spawnflags & SPAWN_ROCKET) + { + if (dist * trace.fraction > 72) + { + monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET); + } + } + } + } +} + +void +TurretFireBlind(edict_t *self) +{ + vec3_t forward; + vec3_t start, end, dir; + float chance; + int rocketSpeed = 0; + + if (!self) + { + return; + } + + TurretAim(self); + + if (!self->enemy || !self->enemy->inuse) + { + return; + } + + VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, dir); + VectorNormalize(dir); + AngleVectors(self->s.angles, forward, NULL, NULL); + chance = DotProduct(dir, forward); + + if (chance < 0.98) + { + return; + } + + if (self->spawnflags & SPAWN_ROCKET) + { + rocketSpeed = 550; + + if (skill->value == SKILL_HARD) + { + rocketSpeed += 200 * random(); + } + else if (skill->value == SKILL_HARDPLUS) + { + rocketSpeed += 100 + (200 * random()); + } + } + + VectorCopy(self->s.origin, start); + VectorCopy(self->monsterinfo.blind_fire_target, end); + + if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2]) + { + end[2] += self->enemy->viewheight + 10; + } + else + { + end[2] += self->enemy->mins[2] - 10; + } + + VectorSubtract(end, start, dir); + VectorNormalize(dir); + + if (self->spawnflags & SPAWN_BLASTER) + { + monster_fire_blaster(self, start, dir, 20, 1000, + MZ2_TURRET_BLASTER, EF_BLASTER); + } + else if (self->spawnflags & SPAWN_ROCKET) + { + monster_fire_rocket(self, start, dir, 50, rocketSpeed, + MZ2_TURRET_ROCKET); + } +} + +static mframe_t turret_frames_fire[] = { + {ai_run, 0, TurretFire}, + {ai_run, 0, TurretAim}, + {ai_run, 0, TurretAim}, + {ai_run, 0, TurretAim} +}; + +mmove_t turret_move_fire = { + FRAME_pow01, + FRAME_pow04, + turret_frames_fire, + turret_run +}; + +/* the blind frames need to aim first */ +static mframe_t turret_frames_fire_blind[] = { + {ai_run, 0, TurretAim}, + {ai_run, 0, TurretAim}, + {ai_run, 0, TurretAim}, + {ai_run, 0, TurretFireBlind} +}; + +mmove_t turret_move_fire_blind = { + FRAME_pow01, + FRAME_pow04, + turret_frames_fire_blind, + turret_run +}; + +void +turret_attack(edict_t *self) +{ + float r, chance; + + if (!self) + { + return; + } + + if (self->s.frame < FRAME_run01) + { + turret_ready_gun(self); + } + else if (self->monsterinfo.attack_state != AS_BLIND) + { + self->monsterinfo.nextframe = FRAME_pow01; + self->monsterinfo.currentmove = &turret_move_fire; + } + else + { + /* setup shot probabilities */ + if (self->monsterinfo.blind_fire_delay < 1.0) + { + chance = 1.0; + } + else if (self->monsterinfo.blind_fire_delay < 7.5) + { + chance = 0.4; + } + else + { + chance = 0.1; + } + + r = random(); + + /* minimum of 3 seconds, plus 0-4, after the shots are done - total time should be max less than 7.5 */ + self->monsterinfo.blind_fire_delay += 0.4 + 3.0 + random() * 4.0; + + /* don't shoot at the origin */ + if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin)) + { + return; + } + + /* don't shoot if the dice say not to */ + if (r > chance) + { + return; + } + + self->monsterinfo.nextframe = FRAME_pow01; + self->monsterinfo.currentmove = &turret_move_fire_blind; + } +} + +void +turret_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + return; +} + +void +turret_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage /* unused */, vec3_t point /* unused */) +{ + vec3_t forward; + vec3_t start; + edict_t *base; + + if (!self) + { + return; + } + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PLAIN_EXPLOSION); + gi.WritePosition(self->s.origin); + gi.multicast(self->s.origin, MULTICAST_PHS); + + AngleVectors(self->s.angles, forward, NULL, NULL); + VectorMA(self->s.origin, 1, forward, start); + + ThrowDebris(self, "models/objects/debris1/tris.md2", 1, start); + ThrowDebris(self, "models/objects/debris1/tris.md2", 2, start); + ThrowDebris(self, "models/objects/debris1/tris.md2", 1, start); + ThrowDebris(self, "models/objects/debris1/tris.md2", 2, start); + + if (self->teamchain) + { + base = self->teamchain; + base->solid = SOLID_BBOX; + base->takedamage = DAMAGE_NO; + base->movetype = MOVETYPE_NONE; + gi.linkentity(base); + } + + if (self->target) + { + if (self->enemy && self->enemy->inuse) + { + G_UseTargets(self, self->enemy); + } + else + { + G_UseTargets(self, self); + } + } + + G_FreeEdict(self); +} + +void +turret_wall_spawn(edict_t *turret) +{ + edict_t *ent; + int angle; + + if (!turret) + { + return; + } + + ent = G_Spawn(); + VectorCopy(turret->s.origin, ent->s.origin); + VectorCopy(turret->s.angles, ent->s.angles); + + angle = ent->s.angles[1]; + + if (ent->s.angles[0] == 90) + { + angle = -1; + } + else if (ent->s.angles[0] == 270) + { + angle = -2; + } + + switch (angle) + { + case -1: + VectorSet(ent->mins, -16, -16, -8); + VectorSet(ent->maxs, 16, 16, 0); + break; + case -2: + VectorSet(ent->mins, -16, -16, 0); + VectorSet(ent->maxs, 16, 16, 8); + break; + case 0: + VectorSet(ent->mins, -8, -16, -16); + VectorSet(ent->maxs, 0, 16, 16); + break; + case 90: + VectorSet(ent->mins, -16, -8, -16); + VectorSet(ent->maxs, 16, 0, 16); + break; + case 180: + VectorSet(ent->mins, 0, -16, -16); + VectorSet(ent->maxs, 8, 16, 16); + break; + case 270: + VectorSet(ent->mins, -16, 0, -16); + VectorSet(ent->maxs, 16, 8, 16); + break; + } + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + + ent->teammaster = turret; + turret->teammaster = turret; + turret->teamchain = ent; + ent->teamchain = NULL; + ent->flags |= FL_TEAMSLAVE; + ent->owner = turret; + + ent->s.modelindex = gi.modelindex("models/monsters/turretbase/tris.md2"); + + gi.linkentity(ent); +} + +void +turret_wake(edict_t *self) +{ + if (!self) + { + return; + } + + /* the wall section will call this when it stops moving. */ + if (self->flags & FL_TEAMSLAVE) + { + return; + } + + self->monsterinfo.stand = turret_stand; + self->monsterinfo.walk = turret_walk; + self->monsterinfo.run = turret_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = turret_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = turret_sight; + self->monsterinfo.search = turret_search; + self->monsterinfo.currentmove = &turret_move_stand; + + self->takedamage = DAMAGE_AIM; + self->movetype = MOVETYPE_NONE; + self->monsterinfo.aiflags |= AI_DO_NOT_COUNT; + + gi.linkentity(self); + + stationarymonster_start(self); + + if (self->think) + { + self->think(self); + } + + if (self->spawnflags & SPAWN_MACHINEGUN) + { + self->s.skinnum = 1; + } + else if (self->spawnflags & SPAWN_ROCKET) + { + self->s.skinnum = 2; + } + + /* but we do want the death to count */ + self->monsterinfo.aiflags &= ~AI_DO_NOT_COUNT; +} + +void +turret_activate(edict_t *self, edict_t *other, edict_t *activator) +{ + vec3_t endpos; + vec3_t forward; + edict_t *base; + + if (self->movetype == MOVETYPE_PUSH) + { + return; + } + + self->movetype = MOVETYPE_PUSH; + + if (!self->speed) + { + self->speed = 15; + } + + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->speed; + self->moveinfo.decel = self->speed; + + if (self->s.angles[0] == 270) + { + VectorSet(forward, 0, 0, 1); + } + else if (self->s.angles[0] == 90) + { + VectorSet(forward, 0, 0, -1); + } + else if (self->s.angles[1] == 0) + { + VectorSet(forward, 1, 0, 0); + } + else if (self->s.angles[1] == 90) + { + VectorSet(forward, 0, 1, 0); + } + else if (self->s.angles[1] == 180) + { + VectorSet(forward, -1, 0, 0); + } + else if (self->s.angles[1] == 270) + { + VectorSet(forward, 0, -1, 0); + } + else + { + VectorClear(forward); + } + + /* start up the turret */ + VectorMA(self->s.origin, 32, forward, endpos); + Move_Calc(self, endpos, turret_wake); + + base = self->teamchain; + + if (base) + { + base->movetype = MOVETYPE_PUSH; + base->speed = self->speed; + base->moveinfo.speed = base->speed; + base->moveinfo.accel = base->speed; + base->moveinfo.decel = base->speed; + + /* start up the wall section */ + VectorMA(self->teamchain->s.origin, 32, forward, endpos); + Move_Calc(self->teamchain, endpos, turret_wake); + } + + gi.sound(self, CHAN_VOICE, gi.soundindex("world/dr_short.wav"), 1, ATTN_NORM, 0); +} + +/* checkattack .. ignore range, just attack if available */ +qboolean +turret_checkattack(edict_t *self) +{ + vec3_t spot1, spot2; + float chance, nexttime; + trace_t tr; + + if (!self) + { + return false; + } + + if (self->enemy->health > 0) + { + /* see if any entities are in the way of the shot */ + VectorCopy(self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy(self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace(spot1, NULL, NULL, spot2, self, + CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA | + CONTENTS_WINDOW); + + /* do we have a clear shot? */ + if (tr.ent != self->enemy) + { + /* we want them to go ahead and shoot at info_notnulls if they can. */ + if ((self->enemy->solid != SOLID_NOT) || (tr.fraction < 1.0)) + { + /* if we can't see our target, and we're not blocked by a monster, go into blind fire if available */ + if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy))) + { + if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 10.0)) + { + if (level.time < self->monsterinfo.attack_finished) + { + return false; + } + + if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay)) + { + /* wait for our time */ + return false; + } + else + { + /* make sure we're not going to shoot something we don't want to shoot */ + tr = gi.trace(spot1, NULL, NULL, self->monsterinfo.blind_fire_target, + self, CONTENTS_MONSTER); + + if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy))) + { + return false; + } + + self->monsterinfo.attack_state = AS_BLIND; + self->monsterinfo.attack_finished = level.time + 0.5 + 2 * random(); + return true; + } + } + } + + return false; + } + } + } + + if (level.time < self->monsterinfo.attack_finished) + { + return false; + } + + if (self->spawnflags & SPAWN_ROCKET) + { + chance = 0.10; + nexttime = (1.8 - (0.2 * skill->value)); + } + else if (self->spawnflags & SPAWN_BLASTER) + { + chance = 0.35; + nexttime = (1.2 - (0.2 * skill->value)); + } + else + { + chance = 0.50; + nexttime = (0.8 - (0.1 * skill->value)); + } + + if (skill->value == SKILL_EASY) + { + chance *= 0.5; + } + else if (skill->value > SKILL_MEDIUM) + { + chance *= 2; + } + + if (((random() < chance) && (visible(self, self->enemy))) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + nexttime; + return true; + } + + self->monsterinfo.attack_state = AS_STRAIGHT; + + return false; +} + +/*QUAKED monster_turret (1 .5 0) (-16 -16 -16) (16 16 16) Ambush Trigger_Spawn Sight Blaster MachineGun Rocket Heatbeam WallUnit + * + * The automated defense turret that mounts on walls. + * Check the weapon you want it to use: blaster, machinegun, rocket, heatbeam. + * Default weapon is blaster. + * When activated, wall units move 32 units in the direction they're facing. + */ +void +SP_monster_turret(edict_t *self) +{ + int angle; + + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + /* pre-caches */ + gi.soundindex("world/dr_short.wav"); + gi.modelindex("models/objects/debris1/tris.md2"); + + self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2"); + + VectorSet(self->mins, -12, -12, -12); + VectorSet(self->maxs, 12, 12, 12); + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + + self->health = 240; + self->gib_health = -100; + self->mass = 250; + self->yaw_speed = 45; + + self->flags |= FL_MECHANICAL; + + self->pain = turret_pain; + self->die = turret_die; + + /* map designer didn't specify weapon type. set it now. */ + if (!(self->spawnflags & SPAWN_WEAPONCHOICE)) + { + self->spawnflags |= SPAWN_BLASTER; + } + + if (self->spawnflags & SPAWN_HEATBEAM) + { + self->spawnflags &= ~SPAWN_HEATBEAM; + self->spawnflags |= SPAWN_BLASTER; + } + + if (!(self->spawnflags & SPAWN_WALL_UNIT)) + { + self->monsterinfo.stand = turret_stand; + self->monsterinfo.walk = turret_walk; + self->monsterinfo.run = turret_run; + self->monsterinfo.dodge = NULL; + self->monsterinfo.attack = turret_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = turret_sight; + self->monsterinfo.search = turret_search; + self->monsterinfo.currentmove = &turret_move_stand; + } + + /* PMM */ + self->monsterinfo.checkattack = turret_checkattack; + + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->monsterinfo.scale = MODEL_SCALE; + self->gravity = 0; + + VectorCopy(self->s.angles, self->offset); + angle = (int)self->s.angles[1]; + + switch (angle) + { + case -1: /* up */ + self->s.angles[0] = 270; + self->s.angles[1] = 0; + self->s.origin[2] += 2; + break; + case -2: /* down */ + self->s.angles[0] = 90; + self->s.angles[1] = 0; + self->s.origin[2] -= 2; + break; + case 0: + self->s.origin[0] += 2; + break; + case 90: + self->s.origin[1] += 2; + break; + case 180: + self->s.origin[0] -= 2; + break; + case 270: + self->s.origin[1] -= 2; + break; + default: + break; + } + + gi.linkentity(self); + + if (self->spawnflags & SPAWN_WALL_UNIT) + { + if (!self->targetname) + { + G_FreeEdict(self); + return; + } + + self->takedamage = DAMAGE_NO; + self->use = turret_activate; + turret_wall_spawn(self); + + if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && + (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT))) + { + level.total_monsters++; + } + } + else + { + stationarymonster_start(self); + } + + if (self->spawnflags & SPAWN_MACHINEGUN) + { + gi.soundindex("infantry/infatck1.wav"); + self->s.skinnum = 1; + } + else if (self->spawnflags & SPAWN_ROCKET) + { + gi.soundindex("weapons/rockfly.wav"); + gi.modelindex("models/objects/rocket/tris.md2"); + gi.soundindex("chick/chkatck2.wav"); + self->s.skinnum = 2; + } + else + { + if (!(self->spawnflags & SPAWN_BLASTER)) + { + self->spawnflags |= SPAWN_BLASTER; + } + + gi.modelindex("models/objects/laser/tris.md2"); + gi.soundindex("misc/lasfly.wav"); + gi.soundindex("soldier/solatck2.wav"); + } + + /* turrets don't get mad at monsters, and visa versa */ + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + /* blindfire */ + if (self->spawnflags & (SPAWN_ROCKET | SPAWN_BLASTER)) + { + self->monsterinfo.blindfire = true; + } +} diff --git a/src/game/monster/turret/turret.h b/src/game/monster/turret/turret.h new file mode 100644 index 000000000..54979a2a6 --- /dev/null +++ b/src/game/monster/turret/turret.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Turret animations. + * + * ======================================================================= + */ + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_active01 2 +#define FRAME_active02 3 +#define FRAME_active03 4 +#define FRAME_active04 5 +#define FRAME_active05 6 +#define FRAME_active06 7 +#define FRAME_run01 8 +#define FRAME_run02 9 +#define FRAME_pow01 10 +#define FRAME_pow02 11 +#define FRAME_pow03 12 +#define FRAME_pow04 13 +#define FRAME_death01 14 +#define FRAME_death02 15 +#define MODEL_SCALE 3.500000 diff --git a/src/game/monster/widow/widow.c b/src/game/monster/widow/widow.c new file mode 100644 index 000000000..aae91d655 --- /dev/null +++ b/src/game/monster/widow/widow.c @@ -0,0 +1,1920 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Black Window (stage 1). + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "widow.h" + +#define NUM_STALKERS_SPAWNED 6 /* max # of stalkers she can spawn */ + +#define RAIL_TIME 3 +#define BLASTER_TIME 2 +#define BLASTER2_DAMAGE 10 +#define WIDOW_RAIL_DAMAGE 50 +#define VARIANCE 15.0 + +void BossExplode(edict_t *self); +qboolean infront(edict_t *self, edict_t *other); + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_search1; +static int sound_rail; + +static unsigned long shotsfired; + +static vec3_t spawnpoints[] = { + {30, 100, 16}, + {30, -100, 16} +}; + +static vec3_t beameffects[] = { + {12.58, -43.71, 68.88}, + {3.43, 58.72, 68.41} +}; + +static float sweep_angles[] = { + 32.0, 26.0, 20.0, 10.0, 0.0, -6.5, -13.0, -27.0, -41.0 +}; + +vec3_t stalker_mins = {-28, -28, -18}; +vec3_t stalker_maxs = {28, 28, 18}; + +mmove_t widow_move_attack_post_blaster; +mmove_t widow_move_attack_post_blaster_r; +mmove_t widow_move_attack_post_blaster_l; +mmove_t widow_move_attack_blaster; +mmove_t widow_move_attack_rail; +mmove_t widow_move_attack_rail_l; +mmove_t widow_move_attack_rail_r; + +unsigned int widow_damage_multiplier; + +void widow_run(edict_t *self); +void widow_stand(edict_t *self); +void widow_dead(edict_t *self); +void widow_attack(edict_t *self); +void widow_attack_blaster(edict_t *self); +void widow_reattack_blaster(edict_t *self); +void widow_die(edict_t *self, edict_t *inflictor, edict_t *attacker, + int damage, vec3_t point); + +void widow_start_spawn(edict_t *self); +void widow_done_spawn(edict_t *self); +void widow_spawn_check(edict_t *self); +void widow_prep_spawn(edict_t *self); +void widow_attack_rail(edict_t *self); + +void widow_start_run_5(edict_t *self); +void widow_start_run_10(edict_t *self); +void widow_start_run_12(edict_t *self); + +void WidowCalcSlots(edict_t *self); + +void drawbbox(edict_t *self); + +void +widow_search(edict_t *self) +{ +} + +void +widow_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + self->monsterinfo.pausetime = 0; +} + +float +target_angle(edict_t *self) +{ + vec3_t target; + float enemy_yaw; + + if (!self) + { + return 0.0; + } + + VectorSubtract(self->s.origin, self->enemy->s.origin, target); + enemy_yaw = self->s.angles[YAW] - vectoyaw2(target); + + if (enemy_yaw < 0) + { + enemy_yaw += 360.0; + } + + enemy_yaw -= 180.0; + + return enemy_yaw; +} + +static int +WidowTorso(edict_t *self) +{ + float enemy_yaw; + + if (!self) + { + return 0; + } + + enemy_yaw = target_angle(self); + + if (enemy_yaw >= 105) + { + self->monsterinfo.currentmove = &widow_move_attack_post_blaster_r; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return 0; + } + + if (enemy_yaw <= -75.0) + { + self->monsterinfo.currentmove = &widow_move_attack_post_blaster_l; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return 0; + } + + if (enemy_yaw >= 95) + { + return FRAME_fired03; + } + else if (enemy_yaw >= 85) + { + return FRAME_fired04; + } + else if (enemy_yaw >= 75) + { + return FRAME_fired05; + } + else if (enemy_yaw >= 65) + { + return FRAME_fired06; + } + else if (enemy_yaw >= 55) + { + return FRAME_fired07; + } + else if (enemy_yaw >= 45) + { + return FRAME_fired08; + } + else if (enemy_yaw >= 35) + { + return FRAME_fired09; + } + else if (enemy_yaw >= 25) + { + return FRAME_fired10; + } + else if (enemy_yaw >= 15) + { + return FRAME_fired11; + } + else if (enemy_yaw >= 5) + { + return FRAME_fired12; + } + else if (enemy_yaw >= -5) + { + return FRAME_fired13; + } + else if (enemy_yaw >= -15) + { + return FRAME_fired14; + } + else if (enemy_yaw >= -25) + { + return FRAME_fired15; + } + else if (enemy_yaw >= -35) + { + return FRAME_fired16; + } + else if (enemy_yaw >= -45) + { + return FRAME_fired17; + } + else if (enemy_yaw >= -55) + { + return FRAME_fired18; + } + else if (enemy_yaw >= -65) + { + return FRAME_fired19; + } + else if (enemy_yaw >= -75) + { + return FRAME_fired20; + } + + return 1; +} + +void +WidowBlaster(edict_t *self) +{ + vec3_t forward, right, target, vec, targ_angles; + vec3_t start; + int flashnum; + int effect; + + if (!self) + { + return; + } + + if (!self->enemy) + { + return; + } + + shotsfired++; + + if (!(shotsfired % 4)) + { + effect = EF_BLASTER; + } + else + { + effect = 0; + } + + AngleVectors(self->s.angles, forward, right, NULL); + + if ((self->s.frame >= FRAME_spawn05) && (self->s.frame <= FRAME_spawn13)) + { + /* sweep */ + flashnum = MZ2_WIDOW_BLASTER_SWEEP1 + self->s.frame - FRAME_spawn05; + G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, + right, start); + VectorSubtract(self->enemy->s.origin, start, target); + vectoangles2(target, targ_angles); + + VectorCopy(self->s.angles, vec); + + vec[PITCH] += targ_angles[PITCH]; + vec[YAW] -= sweep_angles[flashnum - MZ2_WIDOW_BLASTER_SWEEP1]; + + AngleVectors(vec, forward, NULL, NULL); + monster_fire_blaster2(self, start, forward, BLASTER2_DAMAGE * widow_damage_multiplier, + 1000, flashnum, effect); + } + else if ((self->s.frame >= FRAME_fired02a) && (self->s.frame <= FRAME_fired20)) + { + vec3_t angles; + float aim_angle, target_angle; + float error; + + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + + self->monsterinfo.nextframe = WidowTorso(self); + + if (!self->monsterinfo.nextframe) + { + self->monsterinfo.nextframe = self->s.frame; + } + + if (self->s.frame == FRAME_fired02a) + { + flashnum = MZ2_WIDOW_BLASTER_0; + } + else + { + flashnum = MZ2_WIDOW_BLASTER_100 + self->s.frame - FRAME_fired03; + } + + G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], + forward, right, start); + + PredictAim(self->enemy, start, 1000, true, ((random() * 0.1) - 0.05), forward, NULL); + + /* clamp it to within 10 degrees of the aiming angle (where she's facing) */ + vectoangles2(forward, angles); + aim_angle = 100 - (10 * (flashnum - MZ2_WIDOW_BLASTER_100)); + + if (aim_angle <= 0) + { + aim_angle += 360; + } + + target_angle = self->s.angles[YAW] - angles[YAW]; + + if (target_angle <= 0) + { + target_angle += 360; + } + + error = aim_angle - target_angle; + + /* positive error is to entity's left, aka positive direction in + engine unfortunately, I decided that for the aim_angle, positive + was right. *sigh* */ + if (error > VARIANCE) + { + angles[YAW] = (self->s.angles[YAW] - aim_angle) + VARIANCE; + AngleVectors(angles, forward, NULL, NULL); + } + else if (error < -VARIANCE) + { + angles[YAW] = (self->s.angles[YAW] - aim_angle) - VARIANCE; + AngleVectors(angles, forward, NULL, NULL); + } + + monster_fire_blaster2(self, start, forward, BLASTER2_DAMAGE * widow_damage_multiplier, + 1000, flashnum, effect); + } + else if ((self->s.frame >= FRAME_run01) && (self->s.frame <= FRAME_run08)) + { + flashnum = MZ2_WIDOW_RUN_1 + self->s.frame - FRAME_run01; + G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], + forward, right, start); + + VectorSubtract(self->enemy->s.origin, start, target); + target[2] += self->enemy->viewheight; + + monster_fire_blaster2(self, start, target, BLASTER2_DAMAGE * widow_damage_multiplier, + 1000, flashnum, effect); + } +} + +void +WidowSpawn(edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + edict_t *ent, *designated_enemy; + int i; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + + for (i = 0; i < 2; i++) + { + VectorCopy(spawnpoints[i], offset); + + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, + 64)) + { + ent = CreateGroundMonster(spawnpoint, self->s.angles, stalker_mins, stalker_maxs, + "monster_stalker", 256); + + if (!ent) + { + continue; + } + + self->monsterinfo.monster_used++; + ent->monsterinfo.commander = self; + ent->nextthink = level.time; + ent->think(ent); + + ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW | AI_DO_NOT_COUNT | AI_IGNORE_SHOTS; + + designated_enemy = self->enemy; + + if ((designated_enemy->inuse) && (designated_enemy->health > 0)) + { + ent->enemy = designated_enemy; + FoundTarget(ent); + ent->monsterinfo.attack(ent); + } + } + } +} + +void +widow_spawn_check(edict_t *self) +{ + if (!self) + { + return; + } + + WidowBlaster(self); + WidowSpawn(self); +} + +void +widow_ready_spawn(edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + int i; + + if (!self) + { + return; + } + + WidowBlaster(self); + AngleVectors(self->s.angles, f, r, u); + + for (i = 0; i < 2; i++) + { + VectorCopy(spawnpoints[i], offset); + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, + 64)) + { + SpawnGrow_Spawn(spawnpoint, 1); + } + } +} + +void +widow_step(edict_t *self) +{ + gi.sound(self, CHAN_BODY, gi.soundindex("widow/bwstep3.wav"), 1, ATTN_NORM, 0); +} + +static mframe_t widow_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} +}; + +mmove_t widow_move_stand = { + FRAME_idle01, + FRAME_idle11, + widow_frames_stand, + NULL +}; + +static mframe_t widow_frames_walk[] = { + /* auto generated numbers */ + {ai_walk, 2.79, widow_step}, + {ai_walk, 2.77, NULL}, + {ai_walk, 3.53, NULL}, + {ai_walk, 3.97, NULL}, + {ai_walk, 4.13, NULL}, /* 5 */ + {ai_walk, 4.09, NULL}, + {ai_walk, 3.84, NULL}, + {ai_walk, 3.62, widow_step}, + {ai_walk, 3.29, NULL}, + {ai_walk, 6.08, NULL}, /* 10 */ + {ai_walk, 6.94, NULL}, + {ai_walk, 5.73, NULL}, + {ai_walk, 2.85, NULL} +}; + +mmove_t widow_move_walk = { + FRAME_walk01, + FRAME_walk13, + widow_frames_walk, + NULL +}; + +static mframe_t widow_frames_run[] = { + {ai_run, 2.79, widow_step}, + {ai_run, 2.77, NULL}, + {ai_run, 3.53, NULL}, + {ai_run, 3.97, NULL}, + {ai_run, 4.13, NULL}, /* 5 */ + {ai_run, 4.09, NULL}, + {ai_run, 3.84, NULL}, + {ai_run, 3.62, widow_step}, + {ai_run, 3.29, NULL}, + {ai_run, 6.08, NULL}, /* 10 */ + {ai_run, 6.94, NULL}, + {ai_run, 5.73, NULL}, + {ai_run, 2.85, NULL} +}; + +mmove_t widow_move_run = { + FRAME_walk01, + FRAME_walk13, + widow_frames_run, + NULL +}; + +void +widow_stepshoot(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_BODY, gi.soundindex("widow/bwstep2.wav"), 1, ATTN_NORM, 0); + WidowBlaster(self); +} + +static mframe_t widow_frames_run_attack[] = { + {ai_charge, 13, widow_stepshoot}, + {ai_charge, 11.72, WidowBlaster}, + {ai_charge, 18.04, WidowBlaster}, + {ai_charge, 14.58, WidowBlaster}, + {ai_charge, 13, widow_stepshoot}, /* 5 */ + {ai_charge, 12.12, WidowBlaster}, + {ai_charge, 19.63, WidowBlaster}, + {ai_charge, 11.37, WidowBlaster} +}; + +mmove_t widow_move_run_attack = { + FRAME_run01, + FRAME_run08, + widow_frames_run_attack, + widow_run +}; + +/* These three allow specific entry into the run sequence */ + +void +widow_start_run_5(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &widow_move_run; + self->monsterinfo.nextframe = FRAME_walk05; +} + +void +widow_start_run_10(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &widow_move_run; + self->monsterinfo.nextframe = FRAME_walk10; +} + +void +widow_start_run_12(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &widow_move_run; + self->monsterinfo.nextframe = FRAME_walk12; +} + +static mframe_t widow_frames_attack_pre_blaster[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, widow_attack_blaster} +}; + +mmove_t widow_move_attack_pre_blaster = { + FRAME_fired01, + FRAME_fired02a, + widow_frames_attack_pre_blaster, + NULL +}; + +static mframe_t widow_frames_attack_blaster[] = { + {ai_charge, 0, widow_reattack_blaster}, /* straight ahead */ + {ai_charge, 0, widow_reattack_blaster}, /* 100 degrees right */ + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, /* 50 degrees right */ + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, /* straight */ + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster}, /* 50 degrees left */ + {ai_charge, 0, widow_reattack_blaster}, + {ai_charge, 0, widow_reattack_blaster} /* 70 degrees left */ +}; + +mmove_t widow_move_attack_blaster = { + FRAME_fired02a, + FRAME_fired20, + widow_frames_attack_blaster, + NULL +}; + +static mframe_t widow_frames_attack_post_blaster[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t widow_move_attack_post_blaster = { + FRAME_fired21, + FRAME_fired22, + widow_frames_attack_post_blaster, + widow_run +}; + +static mframe_t widow_frames_attack_post_blaster_r[] = { + {ai_charge, -2, NULL}, + {ai_charge, -10, NULL}, + {ai_charge, -2, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, widow_start_run_12} +}; + +mmove_t widow_move_attack_post_blaster_r = { + FRAME_transa01, + FRAME_transa05, + widow_frames_attack_post_blaster_r, + NULL +}; + +static mframe_t widow_frames_attack_post_blaster_l[] = { + {ai_charge, 0, NULL}, + {ai_charge, 14, NULL}, + {ai_charge, -2, NULL}, + {ai_charge, 10, NULL}, + {ai_charge, 10, widow_start_run_12} +}; + +mmove_t widow_move_attack_post_blaster_l = { + FRAME_transb01, + FRAME_transb05, + widow_frames_attack_post_blaster_l, + NULL +}; + +void +WidowRail(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + int flash = 0; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + + if (self->monsterinfo.currentmove == &widow_move_attack_rail) + { + flash = MZ2_WIDOW_RAIL; + } + else if (self->monsterinfo.currentmove == &widow_move_attack_rail_l) + { + flash = MZ2_WIDOW_RAIL_LEFT; + } + else if (self->monsterinfo.currentmove == &widow_move_attack_rail_r) + { + flash = MZ2_WIDOW_RAIL_RIGHT; + } + + G_ProjectSource(self->s.origin, monster_flash_offset[flash], forward, + right, start); + + /* calc direction to where we targeted */ + VectorSubtract(self->pos1, start, dir); + VectorNormalize(dir); + + monster_fire_railgun(self, start, dir, WIDOW_RAIL_DAMAGE * widow_damage_multiplier, + 100, flash); + self->timestamp = level.time + RAIL_TIME; +} + +void +WidowSaveLoc(edict_t *self) +{ + if (!self) + { + return; + } + + VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */ + self->pos1[2] += self->enemy->viewheight; +} + +void +widow_start_rail(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; +} + +void +widow_rail_done(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; +} + +static mframe_t widow_frames_attack_pre_rail[] = { + {ai_charge, 0, widow_start_rail}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, widow_attack_rail} +}; + +mmove_t widow_move_attack_pre_rail = { + FRAME_transc01, + FRAME_transc04, + widow_frames_attack_pre_rail, + NULL +}; + +static mframe_t widow_frames_attack_rail[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, WidowSaveLoc}, + {ai_charge, -10, WidowRail}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, widow_rail_done} +}; + +mmove_t widow_move_attack_rail = { + FRAME_firea01, + FRAME_firea09, + widow_frames_attack_rail, + widow_run +}; + +static mframe_t widow_frames_attack_rail_r[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, WidowSaveLoc}, + {ai_charge, -10, WidowRail}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, widow_rail_done} +}; +mmove_t widow_move_attack_rail_r = { + FRAME_fireb01, + FRAME_fireb09, + widow_frames_attack_rail_r, + widow_run +}; + +static mframe_t widow_frames_attack_rail_l[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, WidowSaveLoc}, + {ai_charge, -10, WidowRail}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, widow_rail_done} +}; + +mmove_t widow_move_attack_rail_l = { + FRAME_firec01, + FRAME_firec09, + widow_frames_attack_rail_l, + widow_run +}; + +void +widow_attack_rail(edict_t *self) +{ + float enemy_angle; + + if (!self) + { + return; + } + + enemy_angle = target_angle(self); + + if (enemy_angle < -15) + { + self->monsterinfo.currentmove = &widow_move_attack_rail_l; + } + else if (enemy_angle > 15) + { + self->monsterinfo.currentmove = &widow_move_attack_rail_r; + } + else + { + self->monsterinfo.currentmove = &widow_move_attack_rail; + } +} + +void +widow_start_spawn(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; +} + +void +widow_done_spawn(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; +} + +static mframe_t widow_frames_spawn[] = { + {ai_charge, 0, NULL}, /* 1 */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, widow_start_spawn}, + {ai_charge, 0, NULL}, /* 5 */ + {ai_charge, 0, WidowBlaster}, /* 6 */ + {ai_charge, 0, widow_ready_spawn}, /* 7 */ + {ai_charge, 0, WidowBlaster}, + {ai_charge, 0, WidowBlaster}, /* 9 */ + {ai_charge, 0, widow_spawn_check}, + {ai_charge, 0, WidowBlaster}, /* 11 */ + {ai_charge, 0, WidowBlaster}, + {ai_charge, 0, WidowBlaster}, /* 13 */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, widow_done_spawn} +}; + +mmove_t widow_move_spawn = { + FRAME_spawn01, + FRAME_spawn18, + widow_frames_spawn, + widow_run +}; + +static mframe_t widow_frames_pain_heavy[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t widow_move_pain_heavy = { + FRAME_pain01, + FRAME_pain13, + widow_frames_pain_heavy, + widow_run +}; + +static mframe_t widow_frames_pain_light[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t widow_move_pain_light = { + FRAME_pain201, + FRAME_pain203, + widow_frames_pain_light, + widow_run +}; + +void +spawn_out_start(edict_t *self) +{ + vec3_t startpoint, f, r, u; + + if (!self) + { + return; + } + + self->wait = level.time + 2.0; + + AngleVectors(self->s.angles, f, r, u); + + G_ProjectSource2(self->s.origin, beameffects[0], f, r, u, startpoint); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWBEAMOUT); + gi.WriteShort(20001); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL); + + G_ProjectSource2(self->s.origin, beameffects[1], f, r, u, startpoint); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWBEAMOUT); + gi.WriteShort(20002); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL); + + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/bwidowbeamout.wav"), 1, ATTN_NORM, 0); +} + +void +spawn_out_do(edict_t *self) +{ + vec3_t startpoint, f, r, u; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, beameffects[0], f, r, u, startpoint); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWSPLASH); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL); + + G_ProjectSource2(self->s.origin, beameffects[1], f, r, u, startpoint); + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WIDOWSPLASH); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_ALL); + + VectorCopy(self->s.origin, startpoint); + startpoint[2] += 36; + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BOSSTPORT); + gi.WritePosition(startpoint); + gi.multicast(startpoint, MULTICAST_PVS); + + Widowlegs_Spawn(self->s.origin, self->s.angles); + + G_FreeEdict(self); +} + +static mframe_t widow_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 5 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, spawn_out_start}, /* 10 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 15 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 20 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 25 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 30 */ + {ai_move, 0, spawn_out_do} +}; + +mmove_t widow_move_death = { + FRAME_death01, + FRAME_death31, + widow_frames_death, + NULL +}; + +void +widow_attack_kick(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + VectorSet(aim, 100, 0, 4); + + if (self->enemy->groundentity) + { + fire_hit(self, aim, (50 + (rand() % 6)), 500); + } + else /* not as much kick if they're in the air .. makes it harder to land on her head */ + { + fire_hit(self, aim, (50 + (rand() % 6)), 250); + } +} + +static mframe_t widow_frames_attack_kick[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, widow_attack_kick}, + {ai_move, 0, NULL}, /* 5 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t widow_move_attack_kick = { + FRAME_kick01, + FRAME_kick08, + widow_frames_attack_kick, + widow_run +}; + +void +widow_stand(edict_t *self) +{ + if (!self) + { + return; + } + + gi.sound(self, CHAN_WEAPON, gi.soundindex("widow/laugh.wav"), 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_stand; +} + +void +widow_run(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &widow_move_stand; + } + else + { + self->monsterinfo.currentmove = &widow_move_run; + } +} + +void +widow_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &widow_move_walk; +} + +void +widow_attack(edict_t *self) +{ + float luck; + qboolean rail_frames = false, blaster_frames = false, blocked = false, anger = false; + + if (!self) + { + return; + } + + self->movetarget = NULL; + + if (self->monsterinfo.aiflags & AI_BLOCKED) + { + blocked = true; + self->monsterinfo.aiflags &= ~AI_BLOCKED; + } + + if (self->monsterinfo.aiflags & AI_TARGET_ANGER) + { + anger = true; + self->monsterinfo.aiflags &= ~AI_TARGET_ANGER; + } + + if ((!self->enemy) || (!self->enemy->inuse)) + { + return; + } + + if (self->bad_area) + { + if ((random() < 0.1) || (level.time < self->timestamp)) + { + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + } + else + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + + return; + } + + if ((self->s.frame == FRAME_walk13) || + ((self->s.frame >= FRAME_walk01) && (self->s.frame <= FRAME_walk03))) + { + rail_frames = true; + } + + if ((self->s.frame >= FRAME_walk09) && (self->s.frame <= FRAME_walk12)) + { + blaster_frames = true; + } + + WidowCalcSlots(self); + + /* if we can't see the target, spawn stuff regardless of frame */ + if ((self->monsterinfo.attack_state == AS_BLIND) && (SELF_SLOTS_LEFT >= 2)) + { + self->monsterinfo.currentmove = &widow_move_spawn; + return; + } + + /* accept bias towards spawning regardless of frame */ + if (blocked && (SELF_SLOTS_LEFT >= 2)) + { + self->monsterinfo.currentmove = &widow_move_spawn; + return; + } + + if ((realrange(self, self->enemy) > 300) && (!anger) && (random() < 0.5) && + (!blocked)) + { + self->monsterinfo.currentmove = &widow_move_run_attack; + return; + } + + if (blaster_frames) + { + if (SELF_SLOTS_LEFT >= 2) + { + self->monsterinfo.currentmove = &widow_move_spawn; + return; + } + else if (self->monsterinfo.pausetime + BLASTER_TIME <= level.time) + { + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + return; + } + } + + if (rail_frames) + { + if (!(level.time < self->timestamp)) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + } + + if ((rail_frames) || (blaster_frames)) + { + return; + } + + luck = random(); + + if (SELF_SLOTS_LEFT >= 2) + { + if ((luck <= 0.40) && (self->monsterinfo.pausetime + BLASTER_TIME <= level.time)) + { + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + } + else if ((luck <= 0.7) && !(level.time < self->timestamp)) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + else + { + self->monsterinfo.currentmove = &widow_move_spawn; + } + } + else + { + if (level.time < self->timestamp) + { + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + } + else if ((luck <= 0.50) || (level.time + BLASTER_TIME >= self->monsterinfo.pausetime)) + { + gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0); + self->monsterinfo.currentmove = &widow_move_attack_pre_rail; + } + else /* holdout to blaster */ + { + self->monsterinfo.currentmove = &widow_move_attack_pre_blaster; + } + } +} + +void +widow_attack_blaster(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.pausetime = level.time + 1.0 + (2.0 * random()); + self->monsterinfo.currentmove = &widow_move_attack_blaster; + self->monsterinfo.nextframe = WidowTorso(self); +} + +void +widow_reattack_blaster(edict_t *self) +{ + if (!self) + { + return; + } + + WidowBlaster(self); + + if ((self->monsterinfo.currentmove == &widow_move_attack_post_blaster_l) || + (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_r)) + { + return; + } + + /* if we're not done with the attack, don't leave the sequence */ + if (self->monsterinfo.pausetime >= level.time) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &widow_move_attack_post_blaster; +} + +void +widow_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + if (self->monsterinfo.pausetime == 100000000) + { + self->monsterinfo.pausetime = 0; + } + + self->pain_debounce_time = level.time + 5; + + if (damage < 15) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + } + else if (damage < 75) + { + if ((skill->value < SKILL_HARDPLUS) && (random() < (0.6 - (0.2 * ((float)skill->value))))) + { + self->monsterinfo.currentmove = &widow_move_pain_light; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + } + + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + } + else + { + if ((skill->value < SKILL_HARDPLUS) && (random() < (0.75 - (0.1 * ((float)skill->value))))) + { + self->monsterinfo.currentmove = &widow_move_pain_heavy; + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + } + + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + } +} + +void +widow_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -56, -56, 0); + VectorSet(self->maxs, 56, 56, 80); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +void +widow_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage /* unused */, vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + self->monsterinfo.quad_framenum = 0; + self->monsterinfo.double_framenum = 0; + self->monsterinfo.invincible_framenum = 0; + self->monsterinfo.currentmove = &widow_move_death; +} + +void +widow_melee(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &widow_move_attack_kick; +} + +void +WidowGoinQuad(edict_t *self, float framenum) +{ + if (!self) + { + return; + } + + self->monsterinfo.quad_framenum = framenum; + widow_damage_multiplier = 4; +} + +void +WidowDouble(edict_t *self, float framenum) +{ + if (!self) + { + return; + } + + self->monsterinfo.double_framenum = framenum; + widow_damage_multiplier = 2; +} + +void +WidowPent(edict_t *self, float framenum) +{ + if (!self) + { + return; + } + + self->monsterinfo.invincible_framenum = framenum; +} + +void +WidowPowerArmor(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + + /* I don't like this, but it works */ + if (self->monsterinfo.power_armor_power <= 0) + { + self->monsterinfo.power_armor_power += 250 * skill->value; + } +} + +void +WidowRespondPowerup(edict_t *self, edict_t *other) +{ + if (!self || !other) + { + return; + } + + if (other->s.effects & EF_QUAD) + { + if (skill->value == SKILL_MEDIUM) + { + WidowDouble(self, other->client->quad_framenum); + } + else if (skill->value == SKILL_HARD) + { + WidowGoinQuad(self, other->client->quad_framenum); + } + else if (skill->value == SKILL_HARDPLUS) + { + WidowGoinQuad(self, other->client->quad_framenum); + WidowPowerArmor(self); + } + } + else if (other->s.effects & EF_DOUBLE) + { + if (skill->value == SKILL_HARD) + { + WidowDouble(self, other->client->double_framenum); + } + else if (skill->value == SKILL_HARDPLUS) + { + WidowDouble(self, other->client->double_framenum); + WidowPowerArmor(self); + } + } + else + { + widow_damage_multiplier = 1; + } + + if (other->s.effects & EF_PENT) + { + if (skill->value == SKILL_MEDIUM) + { + WidowPowerArmor(self); + } + else if (skill->value == SKILL_HARD) + { + WidowPent(self, other->client->invincible_framenum); + } + else if (skill->value == SKILL_HARDPLUS) + { + WidowPent(self, other->client->invincible_framenum); + WidowPowerArmor(self); + } + } +} + +void +WidowPowerups(edict_t *self) +{ + int player; + edict_t *ent; + + if (!self) + { + return; + } + + if (!(coop && coop->value)) + { + WidowRespondPowerup(self, self->enemy); + } + else + { + /* in coop, check for pents, then quads, then doubles */ + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + + if (!ent->inuse) + { + continue; + } + + if (!ent->client) + { + continue; + } + + if (ent->s.effects & EF_PENT) + { + WidowRespondPowerup(self, ent); + return; + } + } + + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + + if (!ent->inuse) + { + continue; + } + + if (!ent->client) + { + continue; + } + + if (ent->s.effects & EF_QUAD) + { + WidowRespondPowerup(self, ent); + return; + } + } + + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + + if (!ent->inuse) + { + continue; + } + + if (!ent->client) + { + continue; + } + + if (ent->s.effects & EF_DOUBLE) + { + WidowRespondPowerup(self, ent); + return; + } + } + } +} + +qboolean +Widow_CheckAttack(edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance = 0; + trace_t tr; + int enemy_range; + float enemy_yaw; + float real_enemy_range; + + if (!self) + { + return false; + } + + if (!self->enemy) + { + return false; + } + + WidowPowerups(self); + + if (self->monsterinfo.currentmove == &widow_move_run) + { + /* if we're in run, make sure we're in a good frame for attacking before doing anything else */ + switch (self->s.frame) + { + case FRAME_walk04: + case FRAME_walk05: + case FRAME_walk06: + case FRAME_walk07: + case FRAME_walk08: + case FRAME_walk12: + { + return false; + } + default: + break; + } + } + + /* give a LARGE bias to spawning things when we have room + use AI_BLOCKED as a signal to attack to spawn */ + if ((random() < 0.8) && (SELF_SLOTS_LEFT >= 2) && + (realrange(self, self->enemy) > 150)) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (self->enemy->health > 0) + { + /* see if any entities are in the way of the shot */ + VectorCopy(self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy(self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace(spot1, NULL, NULL, spot2, self, + CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | + CONTENTS_LAVA); + + /* do we have a clear shot? */ + if (tr.ent != self->enemy) + { + /* go ahead and spawn stuff if we're mad a a client */ + if (self->enemy->client && (SELF_SLOTS_LEFT >= 2)) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + /* PGM - we want them to go ahead and shoot at info_notnulls if they can. */ + if ((self->enemy->solid != SOLID_NOT) || (tr.fraction < 1.0)) + { + return false; + } + } + } + + enemy_range = range(self, self->enemy); + VectorSubtract(self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw2(temp); + + self->ideal_yaw = enemy_yaw; + + real_enemy_range = realrange(self, self->enemy); + + if (real_enemy_range <= (MELEE_DISTANCE + 20)) + { + /* don't always melee in easy mode */ + if ((skill->value == SKILL_EASY) && (rand() & 3)) + { + return false; + } + + if (self->monsterinfo.melee) + { + self->monsterinfo.attack_state = AS_MELEE; + } + else + { + self->monsterinfo.attack_state = AS_MISSILE; + } + + return true; + } + + if (level.time < self->monsterinfo.attack_finished) + { + return false; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.8; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.7; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.6; + } + else if (enemy_range == RANGE_FAR) + { + chance = 0.5; + } + + /* go ahead and shoot every time if it's a info_notnull */ + if ((random() < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return false; +} + +qboolean +widow_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + if (self->monsterinfo.currentmove == &widow_move_run_attack) + { + self->monsterinfo.aiflags |= AI_TARGET_ANGER; + + if (self->monsterinfo.checkattack(self)) + { + self->monsterinfo.attack(self); + } + else + { + self->monsterinfo.run(self); + } + + return true; + } + + return false; +} + +void +WidowCalcSlots(edict_t *self) +{ + if (!self) + { + return; + } + + switch ((int)skill->value) + { + case SKILL_EASY: + case SKILL_MEDIUM: + self->monsterinfo.monster_slots = 3; + break; + case SKILL_HARD: + self->monsterinfo.monster_slots = 4; + break; + case SKILL_HARDPLUS: + self->monsterinfo.monster_slots = 6; + break; + default: + self->monsterinfo.monster_slots = 3; + break; + } + + if (coop->value) + { + self->monsterinfo.monster_slots = Q_min( + 6, self->monsterinfo.monster_slots + ((skill->value) * (CountPlayers() - 1))); + } +} + +static void +WidowPrecache(void) +{ + /* cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs */ + gi.soundindex("stalker/pain.wav"); + gi.soundindex("stalker/death.wav"); + gi.soundindex("stalker/sight.wav"); + gi.soundindex("stalker/melee1.wav"); + gi.soundindex("stalker/melee2.wav"); + gi.soundindex("stalker/idle.wav"); + + gi.soundindex("tank/tnkatck3.wav"); + gi.modelindex("models/proj/laser2/tris.md2"); + + gi.modelindex("models/monsters/stalker/tris.md2"); + gi.modelindex("models/items/spawngro2/tris.md2"); + gi.modelindex("models/objects/gibs/sm_metal/tris.md2"); + gi.modelindex("models/objects/gibs/gear/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib1/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib2/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib3/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib4/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib1/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib3/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib4/tris.md2"); + gi.modelindex("models/monsters/legs/tris.md2"); + gi.soundindex("misc/bwidowbeamout.wav"); + + gi.soundindex("misc/bigtele.wav"); + gi.soundindex("widow/bwstep3.wav"); + gi.soundindex("widow/bwstep2.wav"); +} + +/* + * QUAKED monster_widow (1 .5 0) (-40 -40 0) (40 40 144) Ambush Trigger_Spawn Sight + */ +void +SP_monster_widow(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("widow/bw1pain1.wav"); + sound_pain2 = gi.soundindex("widow/bw1pain2.wav"); + sound_pain3 = gi.soundindex("widow/bw1pain3.wav"); + sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav"); + sound_rail = gi.soundindex("gladiator/railgun.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/blackwidow/tris.md2"); + VectorSet(self->mins, -40, -40, 0); + VectorSet(self->maxs, 40, 40, 144); + + self->health = 2000 + 1000 * (skill->value); + + if (coop->value) + { + self->health += 500 * (skill->value); + } + + self->gib_health = -5000; + self->mass = 1500; + + if (skill->value == SKILL_HARDPLUS) + { + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 500; + } + + self->yaw_speed = 30; + + self->flags |= FL_IMMUNE_LASER; + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + self->pain = widow_pain; + self->die = widow_die; + + self->monsterinfo.melee = widow_melee; + self->monsterinfo.stand = widow_stand; + self->monsterinfo.walk = widow_walk; + self->monsterinfo.run = widow_run; + self->monsterinfo.attack = widow_attack; + self->monsterinfo.search = widow_search; + self->monsterinfo.checkattack = Widow_CheckAttack; + self->monsterinfo.sight = widow_sight; + + self->monsterinfo.blocked = widow_blocked; + + gi.linkentity(self); + + self->monsterinfo.currentmove = &widow_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + WidowPrecache(); + WidowCalcSlots(self); + widow_damage_multiplier = 1; + + walkmonster_start(self); +} diff --git a/src/game/monster/widow/widow.h b/src/game/monster/widow/widow.h new file mode 100644 index 000000000..c14460a01 --- /dev/null +++ b/src/game/monster/widow/widow.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Black Widow (stage 1) animations. + * + * ======================================================================= + */ + +#define FRAME_idle01 0 +#define FRAME_idle02 1 +#define FRAME_idle03 2 +#define FRAME_idle04 3 +#define FRAME_idle05 4 +#define FRAME_idle06 5 +#define FRAME_idle07 6 +#define FRAME_idle08 7 +#define FRAME_idle09 8 +#define FRAME_idle10 9 +#define FRAME_idle11 10 +#define FRAME_walk01 11 +#define FRAME_walk02 12 +#define FRAME_walk03 13 +#define FRAME_walk04 14 +#define FRAME_walk05 15 +#define FRAME_walk06 16 +#define FRAME_walk07 17 +#define FRAME_walk08 18 +#define FRAME_walk09 19 +#define FRAME_walk10 20 +#define FRAME_walk11 21 +#define FRAME_walk12 22 +#define FRAME_walk13 23 +#define FRAME_run01 24 +#define FRAME_run02 25 +#define FRAME_run03 26 +#define FRAME_run04 27 +#define FRAME_run05 28 +#define FRAME_run06 29 +#define FRAME_run07 30 +#define FRAME_run08 31 +#define FRAME_firea01 32 +#define FRAME_firea02 33 +#define FRAME_firea03 34 +#define FRAME_firea04 35 +#define FRAME_firea05 36 +#define FRAME_firea06 37 +#define FRAME_firea07 38 +#define FRAME_firea08 39 +#define FRAME_firea09 40 +#define FRAME_fireb01 41 +#define FRAME_fireb02 42 +#define FRAME_fireb03 43 +#define FRAME_fireb04 44 +#define FRAME_fireb05 45 +#define FRAME_fireb06 46 +#define FRAME_fireb07 47 +#define FRAME_fireb08 48 +#define FRAME_fireb09 49 +#define FRAME_firec01 50 +#define FRAME_firec02 51 +#define FRAME_firec03 52 +#define FRAME_firec04 53 +#define FRAME_firec05 54 +#define FRAME_firec06 55 +#define FRAME_firec07 56 +#define FRAME_firec08 57 +#define FRAME_firec09 58 +#define FRAME_fired01 59 +#define FRAME_fired02 60 +#define FRAME_fired02a 61 +#define FRAME_fired03 62 +#define FRAME_fired04 63 +#define FRAME_fired05 64 +#define FRAME_fired06 65 +#define FRAME_fired07 66 +#define FRAME_fired08 67 +#define FRAME_fired09 68 +#define FRAME_fired10 69 +#define FRAME_fired11 70 +#define FRAME_fired12 71 +#define FRAME_fired13 72 +#define FRAME_fired14 73 +#define FRAME_fired15 74 +#define FRAME_fired16 75 +#define FRAME_fired17 76 +#define FRAME_fired18 77 +#define FRAME_fired19 78 +#define FRAME_fired20 79 +#define FRAME_fired21 80 +#define FRAME_fired22 81 +#define FRAME_spawn01 82 +#define FRAME_spawn02 83 +#define FRAME_spawn03 84 +#define FRAME_spawn04 85 +#define FRAME_spawn05 86 +#define FRAME_spawn06 87 +#define FRAME_spawn07 88 +#define FRAME_spawn08 89 +#define FRAME_spawn09 90 +#define FRAME_spawn10 91 +#define FRAME_spawn11 92 +#define FRAME_spawn12 93 +#define FRAME_spawn13 94 +#define FRAME_spawn14 95 +#define FRAME_spawn15 96 +#define FRAME_spawn16 97 +#define FRAME_spawn17 98 +#define FRAME_spawn18 99 +#define FRAME_pain01 100 +#define FRAME_pain02 101 +#define FRAME_pain03 102 +#define FRAME_pain04 103 +#define FRAME_pain05 104 +#define FRAME_pain06 105 +#define FRAME_pain07 106 +#define FRAME_pain08 107 +#define FRAME_pain09 108 +#define FRAME_pain10 109 +#define FRAME_pain11 110 +#define FRAME_pain12 111 +#define FRAME_pain13 112 +#define FRAME_pain201 113 +#define FRAME_pain202 114 +#define FRAME_pain203 115 +#define FRAME_transa01 116 +#define FRAME_transa02 117 +#define FRAME_transa03 118 +#define FRAME_transa04 119 +#define FRAME_transa05 120 +#define FRAME_transb01 121 +#define FRAME_transb02 122 +#define FRAME_transb03 123 +#define FRAME_transb04 124 +#define FRAME_transb05 125 +#define FRAME_transc01 126 +#define FRAME_transc02 127 +#define FRAME_transc03 128 +#define FRAME_transc04 129 +#define FRAME_death01 130 +#define FRAME_death02 131 +#define FRAME_death03 132 +#define FRAME_death04 133 +#define FRAME_death05 134 +#define FRAME_death06 135 +#define FRAME_death07 136 +#define FRAME_death08 137 +#define FRAME_death09 138 +#define FRAME_death10 139 +#define FRAME_death11 140 +#define FRAME_death12 141 +#define FRAME_death13 142 +#define FRAME_death14 143 +#define FRAME_death15 144 +#define FRAME_death16 145 +#define FRAME_death17 146 +#define FRAME_death18 147 +#define FRAME_death19 148 +#define FRAME_death20 149 +#define FRAME_death21 150 +#define FRAME_death22 151 +#define FRAME_death23 152 +#define FRAME_death24 153 +#define FRAME_death25 154 +#define FRAME_death26 155 +#define FRAME_death27 156 +#define FRAME_death28 157 +#define FRAME_death29 158 +#define FRAME_death30 159 +#define FRAME_death31 160 +#define FRAME_kick01 161 +#define FRAME_kick02 162 +#define FRAME_kick03 163 +#define FRAME_kick04 164 +#define FRAME_kick05 165 +#define FRAME_kick06 166 +#define FRAME_kick07 167 +#define FRAME_kick08 168 +#define MODEL_SCALE 2.000000 diff --git a/src/game/monster/widow/widow2.c b/src/game/monster/widow/widow2.c new file mode 100644 index 000000000..7d528d17c --- /dev/null +++ b/src/game/monster/widow/widow2.c @@ -0,0 +1,2291 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Black Window (stage 2). + * + * ======================================================================= + */ + +#include "../../header/local.h" +#include "widow2.h" + +#define NUM_STALKERS_SPAWNED 6 /* max # of stalkers she can spawn */ +#define DISRUPT_TIME 3 + +static int sound_pain1; +static int sound_pain2; +static int sound_pain3; +static int sound_death; +static int sound_search1; +static int sound_tentacles_retract; + +static vec3_t spawnpoints[] = { + {30, 135, 0}, + {30, -135, 0} +}; + +static float sweep_angles[] = { + -40.0, -32.0, -24.0, -16.0, -8.0, 0.0, 8.0, 16.0, 24.0, 32.0, 40.0 +}; + +extern vec3_t stalker_mins, stalker_maxs; + +qboolean infront(edict_t *self, edict_t *other); +void WidowCalcSlots(edict_t *self); +void WidowPowerups(edict_t *self); + +void widow2_run(edict_t *self); +void widow2_stand(edict_t *self); +void widow2_dead(edict_t *self); +void widow2_attack(edict_t *self); +void widow2_attack_beam(edict_t *self); +void widow2_reattack_beam(edict_t *self); +void widow2_die(edict_t *self, edict_t *inflictor, edict_t *attacker, + int damage, vec3_t point); +void widow_start_spawn(edict_t *self); +void widow_done_spawn(edict_t *self); +void widow2_spawn_check(edict_t *self); +void widow2_prep_spawn(edict_t *self); +void Widow2SaveBeamTarget(edict_t *self); +void widow2_start_searching(edict_t *self); +void widow2_keep_searching(edict_t *self); +void widow2_finaldeath(edict_t *self); + +void WidowExplode(edict_t *self); +void gib_die(edict_t *self, edict_t *inflictor, edict_t *attacker, + int damage, vec3_t point); +void gib_touch(edict_t *self, edict_t *other, cplane_t *plane, + csurface_t *surf); +void ThrowWidowGibReal(edict_t *self, char *gibname, int damage, int type, + vec3_t startpos, qboolean large, int hitsound, qboolean fade); +void ThrowWidowGibSized(edict_t *self, char *gibname, int damage, int type, + vec3_t startpos, int hitsound, qboolean fade); +static void ThrowWidowGibLoc(edict_t *self, char *gibname, int damage, int type, + vec3_t startpos, qboolean fade); +void WidowExplosion1(edict_t *self); +void WidowExplosion2(edict_t *self); +void WidowExplosion3(edict_t *self); +void WidowExplosion4(edict_t *self); +void WidowExplosion5(edict_t *self); +void WidowExplosion6(edict_t *self); +void WidowExplosion7(edict_t *self); +void WidowExplosionLeg(edict_t *self); +void ThrowArm1(edict_t *self); +void ThrowArm2(edict_t *self); +void ClipGibVelocity(edict_t *ent); + +/* these offsets used by the tongue */ +static vec3_t offsets[] = { + {17.48, 0.10, 68.92}, + {17.47, 0.29, 68.91}, + {17.45, 0.53, 68.87}, + {17.42, 0.78, 68.81}, + {17.39, 1.02, 68.75}, + {17.37, 1.20, 68.70}, + {17.36, 1.24, 68.71}, + {17.37, 1.21, 68.72}, +}; + +void +pauseme(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags |= AI_HOLD_FRAME; +} + +void +widow2_search(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0); + } +} + +void +Widow2Beam(edict_t *self) +{ + vec3_t forward, right, target; + vec3_t start, targ_angles, vec; + int flashnum; + + if (!self) + { + return; + } + + if ((!self->enemy) || (!self->enemy->inuse)) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + + if ((self->s.frame >= FRAME_fireb05) && (self->s.frame <= FRAME_fireb09)) + { + /* regular beam attack */ + Widow2SaveBeamTarget(self); + flashnum = MZ2_WIDOW2_BEAMER_1 + self->s.frame - FRAME_fireb05; + G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, + right, start); + VectorCopy(self->pos2, target); + target[2] += self->enemy->viewheight - 10; + VectorSubtract(target, start, forward); + VectorNormalize(forward); + monster_fire_heatbeam(self, start, forward, vec3_origin, 10, 50, flashnum); + } + else if ((self->s.frame >= FRAME_spawn04) && (self->s.frame <= FRAME_spawn14)) + { + /* sweep */ + flashnum = MZ2_WIDOW2_BEAM_SWEEP_1 + self->s.frame - FRAME_spawn04; + G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], + forward, right, start); + VectorSubtract(self->enemy->s.origin, start, target); + vectoangles2(target, targ_angles); + + VectorCopy(self->s.angles, vec); + + vec[PITCH] += targ_angles[PITCH]; + vec[YAW] -= sweep_angles[flashnum - MZ2_WIDOW2_BEAM_SWEEP_1]; + + AngleVectors(vec, forward, NULL, NULL); + monster_fire_heatbeam(self, start, forward, vec3_origin, 10, 50, flashnum); + } + else + { + Widow2SaveBeamTarget(self); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_WIDOW2_BEAMER_1], + forward, right, start); + + VectorCopy(self->pos2, target); + target[2] += self->enemy->viewheight - 10; + + VectorSubtract(target, start, forward); + VectorNormalize(forward); + + monster_fire_heatbeam(self, start, forward, vec3_origin, 10, 50, 0); + } +} + +void +Widow2Spawn(edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + edict_t *ent, *designated_enemy; + int i; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + + for (i = 0; i < 2; i++) + { + VectorCopy(spawnpoints[i], offset); + + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) + { + ent = CreateGroundMonster(spawnpoint, self->s.angles, stalker_mins, + stalker_maxs, "monster_stalker", 256); + + if (!ent) + { + continue; + } + + self->monsterinfo.monster_used++; + ent->monsterinfo.commander = self; + + ent->nextthink = level.time; + ent->think(ent); + + ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW | AI_DO_NOT_COUNT | AI_IGNORE_SHOTS; + + if (!(coop && coop->value)) + { + designated_enemy = self->enemy; + } + else + { + designated_enemy = PickCoopTarget(ent); + + if (designated_enemy) + { + /* try to avoid using my enemy */ + if (designated_enemy == self->enemy) + { + designated_enemy = PickCoopTarget(ent); + + if (designated_enemy) + { + } + else + { + designated_enemy = self->enemy; + } + } + } + else + { + designated_enemy = self->enemy; + } + } + + if ((designated_enemy->inuse) && (designated_enemy->health > 0)) + { + ent->enemy = designated_enemy; + FoundTarget(ent); + ent->monsterinfo.attack(ent); + } + } + } +} + +void +widow2_spawn_check(edict_t *self) +{ + if (!self) + { + return; + } + + Widow2Beam(self); + Widow2Spawn(self); +} + +void +widow2_ready_spawn(edict_t *self) +{ + vec3_t f, r, u, offset, startpoint, spawnpoint; + int i; + + if (!self) + { + return; + } + + Widow2Beam(self); + AngleVectors(self->s.angles, f, r, u); + + for (i = 0; i < 2; i++) + { + VectorCopy(spawnpoints[i], offset); + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) + { + SpawnGrow_Spawn(spawnpoint, 1); + } + } +} + +static mframe_t widow2_frames_stand[] = { + {ai_stand, 0, NULL} +}; + +mmove_t widow2_move_stand = { + FRAME_blackwidow3, + FRAME_blackwidow3, + widow2_frames_stand, + NULL +}; + +static mframe_t widow2_frames_walk[] = { + {ai_walk, 9.01, NULL}, + {ai_walk, 7.55, NULL}, + {ai_walk, 7.01, NULL}, + {ai_walk, 6.66, NULL}, + {ai_walk, 6.20, NULL}, + {ai_walk, 5.78, NULL}, + {ai_walk, 7.25, NULL}, + {ai_walk, 8.37, NULL}, + {ai_walk, 10.41, NULL} +}; + +mmove_t widow2_move_walk = { + FRAME_walk01, + FRAME_walk09, + widow2_frames_walk, + NULL +}; + +static mframe_t widow2_frames_run[] = { + {ai_run, 9.01, NULL}, + {ai_run, 7.55, NULL}, + {ai_run, 7.01, NULL}, + {ai_run, 6.66, NULL}, + {ai_run, 6.20, NULL}, + {ai_run, 5.78, NULL}, + {ai_run, 7.25, NULL}, + {ai_run, 8.37, NULL}, + {ai_run, 10.41, NULL} +}; + +mmove_t widow2_move_run = { + FRAME_walk01, + FRAME_walk09, + widow2_frames_run, + NULL +}; + +static mframe_t widow2_frames_attack_pre_beam[] = { + {ai_charge, 4, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 4, widow2_attack_beam} +}; + +mmove_t widow2_move_attack_pre_beam = { + FRAME_fireb01, + FRAME_fireb04, + widow2_frames_attack_pre_beam, + NULL +}; + +/* Loop this */ +static mframe_t widow2_frames_attack_beam[] = { + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, widow2_reattack_beam} +}; + +mmove_t widow2_move_attack_beam = { + FRAME_fireb05, + FRAME_fireb09, + widow2_frames_attack_beam, + NULL +}; + +static mframe_t widow2_frames_attack_post_beam[] = { + {ai_charge, 4, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 4, NULL} +}; + +mmove_t widow2_move_attack_post_beam = { + FRAME_fireb06, + FRAME_fireb07, + widow2_frames_attack_post_beam, + widow2_run +}; + +void +WidowDisrupt(edict_t *self) +{ + vec3_t start; + vec3_t dir; + vec3_t forward, right; + float len; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_WIDOW_DISRUPTOR], + forward, right, start); + + VectorSubtract(self->pos1, self->enemy->s.origin, dir); + len = VectorLength(dir); + + if (len < 30) + { + VectorSubtract(self->pos1, start, dir); + VectorNormalize(dir); + + monster_fire_tracker(self, start, dir, 20, 500, self->enemy, MZ2_WIDOW_DISRUPTOR); + } + else + { + PredictAim(self->enemy, start, 1200, true, 0, dir, NULL); + monster_fire_tracker(self, start, dir, 20, 1200, NULL, MZ2_WIDOW_DISRUPTOR); + } +} + +void +Widow2SaveDisruptLoc(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->enemy && self->enemy->inuse) + { + VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */ + self->pos1[2] += self->enemy->viewheight; + } + else + { + VectorCopy(vec3_origin, self->pos1); + } +} + +void +widow2_disrupt_reattack(edict_t *self) +{ + float luck; + + if (!self) + { + return; + } + + luck = random(); + + if (luck < (0.25 + ((float)(skill->value)) * 0.15)) + { + self->monsterinfo.nextframe = FRAME_firea01; + } +} + +static mframe_t widow2_frames_attack_disrupt[] = { + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, Widow2SaveDisruptLoc}, + {ai_charge, -20, WidowDisrupt}, + {ai_charge, 2, NULL}, + {ai_charge, 2, NULL}, + {ai_charge, 2, widow2_disrupt_reattack} +}; + +mmove_t widow2_move_attack_disrupt = { + FRAME_firea01, + FRAME_firea07, + widow2_frames_attack_disrupt, + widow2_run +}; + +void +Widow2SaveBeamTarget(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->enemy && self->enemy->inuse) + { + VectorCopy(self->pos1, self->pos2); + VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */ + } + else + { + VectorCopy(vec3_origin, self->pos1); + VectorCopy(vec3_origin, self->pos2); + } +} + +void +Widow2BeamTargetRemove(edict_t *self) +{ + if (!self) + { + return; + } + + VectorCopy(vec3_origin, self->pos1); + VectorCopy(vec3_origin, self->pos2); +} + +void +Widow2StartSweep(edict_t *self) +{ + if (!self) + { + return; + } + + Widow2SaveBeamTarget(self); +} + +static mframe_t widow2_frames_spawn[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, widow_start_spawn}, + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, Widow2Beam}, /* 5 */ + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, widow2_ready_spawn}, /* 10 */ + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, Widow2Beam}, + {ai_charge, 0, widow2_spawn_check}, + {ai_charge, 0, NULL}, /* 15 */ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, widow2_reattack_beam} +}; + +mmove_t widow2_move_spawn = { + FRAME_spawn01, + FRAME_spawn18, + widow2_frames_spawn, + NULL +}; + +static qboolean +widow2_tongue_attack_ok(vec3_t start, vec3_t end, float range) +{ + vec3_t dir, angles; + + /* check for max distance */ + VectorSubtract(start, end, dir); + + if (VectorLength(dir) > range) + { + return false; + } + + /* check for min/max pitch */ + vectoangles(dir, angles); + + if (angles[0] < -180) + { + angles[0] += 360; + } + + if (fabs(angles[0]) > 30) + { + return false; + } + + return true; +} + +void +Widow2Tongue(edict_t *self) +{ + vec3_t f, r, u; + vec3_t start, end, dir; + trace_t tr; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offsets[self->s.frame - FRAME_tongs01], f, r, u, start); + VectorCopy(self->enemy->s.origin, end); + + if (!widow2_tongue_attack_ok(start, end, 256)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8; + + if (!widow2_tongue_attack_ok(start, end, 256)) + { + end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8; + + if (!widow2_tongue_attack_ok(start, end, 256)) + { + return; + } + } + } + + VectorCopy(self->enemy->s.origin, end); + + tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT); + + if (tr.ent != self->enemy) + { + return; + } + + gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_PARASITE_ATTACK); + gi.WriteShort(self - g_edicts); + gi.WritePosition(start); + gi.WritePosition(end); + gi.multicast(self->s.origin, MULTICAST_PVS); + + VectorSubtract(start, end, dir); + T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, + vec3_origin, 2, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN); +} + +void +Widow2TonguePull(edict_t *self) +{ + vec3_t vec; + vec3_t f, r, u; + vec3_t start, end; + + if (!self) + { + return; + } + + if ((!self->enemy) || (!self->enemy->inuse)) + { + self->monsterinfo.run(self); + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offsets[self->s.frame - FRAME_tongs01], + f, r, u, start); + VectorCopy(self->enemy->s.origin, end); + + if (!widow2_tongue_attack_ok(start, end, 256)) + { + return; + } + + if (self->enemy->groundentity) + { + self->enemy->s.origin[2] += 1; + self->enemy->groundentity = NULL; + } + + VectorSubtract(self->s.origin, self->enemy->s.origin, vec); + + if (self->enemy->client) + { + VectorNormalize(vec); + VectorMA(self->enemy->velocity, 1000, vec, self->enemy->velocity); + } + else + { + self->enemy->ideal_yaw = vectoyaw(vec); + M_ChangeYaw(self->enemy); + VectorScale(f, 1000, self->enemy->velocity); + } +} + +void +Widow2Crunch(edict_t *self) +{ + vec3_t aim; + + if (!self) + { + return; + } + + if ((!self->enemy) || (!self->enemy->inuse)) + { + self->monsterinfo.run(self); + return; + } + + Widow2TonguePull(self); + + /* 70 + 32 */ + VectorSet(aim, 150, 0, 4); + + if (self->s.frame != FRAME_tongs07) + { + fire_hit(self, aim, 20 + (rand() % 6), 0); + } + else + { + if (self->enemy->groundentity) + { + fire_hit(self, aim, (20 + (rand() % 6)), 500); + } + else /* not as much kick if they're in the air .. makes it harder to land on her head */ + { + fire_hit(self, aim, (20 + (rand() % 6)), 250); + } + } +} + +void +Widow2Toss(edict_t *self) +{ + if (!self) + { + return; + } + + self->timestamp = level.time + 3; + return; +} + +static mframe_t widow2_frames_tongs[] = { + {ai_charge, 0, Widow2Tongue}, + {ai_charge, 0, Widow2Tongue}, + {ai_charge, 0, Widow2Tongue}, + {ai_charge, 0, Widow2TonguePull}, + {ai_charge, 0, Widow2TonguePull}, /* 5 */ + {ai_charge, 0, Widow2TonguePull}, + {ai_charge, 0, Widow2Crunch}, + {ai_charge, 0, Widow2Toss} +}; + +mmove_t widow2_move_tongs = { + FRAME_tongs01, + FRAME_tongs08, + widow2_frames_tongs, + widow2_run +}; + +static mframe_t widow2_frames_pain[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t widow2_move_pain = { + FRAME_pain01, + FRAME_pain05, + widow2_frames_pain, + widow2_run +}; + +static mframe_t widow2_frames_death[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, WidowExplosion1}, /* 3 boom */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 5 */ + + {ai_move, 0, WidowExplosion2}, /* 6 boom */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 10 */ + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 12 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 15 */ + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, WidowExplosion3}, /* 18 */ + {ai_move, 0, NULL}, /* 19 */ + {ai_move, 0, NULL}, /* 20 */ + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, WidowExplosion4}, /* 25 */ + + {ai_move, 0, NULL}, /* 26 */ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, WidowExplosion5}, + {ai_move, 0, WidowExplosionLeg}, /* 30 */ + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, WidowExplosion6}, + {ai_move, 0, NULL}, /* 35 */ + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, WidowExplosion7}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, /* 40 */ + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, WidowExplode} /* 44 */ +}; + +mmove_t widow2_move_death = { + FRAME_death01, + FRAME_death44, + widow2_frames_death, + NULL +}; + +static mframe_t widow2_frames_dead[] = { + {ai_move, 0, widow2_start_searching}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, widow2_keep_searching} +}; + +mmove_t widow2_move_dead = { + FRAME_dthsrh01, + FRAME_dthsrh15, + widow2_frames_dead, + NULL +}; + +static mframe_t widow2_frames_really_dead[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, widow2_finaldeath} +}; + +mmove_t widow2_move_really_dead = { + FRAME_dthsrh16, + FRAME_dthsrh22, + widow2_frames_really_dead, + NULL +}; + +void +widow2_start_searching(edict_t *self) +{ + if (!self) + { + return; + } + + self->count = 0; +} + +void +widow2_keep_searching(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->count <= 2) + { + self->monsterinfo.currentmove = &widow2_move_dead; + self->s.frame = FRAME_dthsrh01; + self->count++; + return; + } + + self->monsterinfo.currentmove = &widow2_move_really_dead; +} + +void +widow2_finaldeath(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -70, -70, 0); + VectorSet(self->maxs, 70, 70, 80); + self->movetype = MOVETYPE_TOSS; + self->takedamage = DAMAGE_YES; + self->nextthink = 0; + gi.linkentity(self); +} + +void +widow2_stand(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &widow2_move_stand; +} + +void +widow2_run(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &widow2_move_stand; + } + else + { + self->monsterinfo.currentmove = &widow2_move_run; + } +} + +void +widow2_walk(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &widow2_move_walk; +} + +void +widow2_melee(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &widow2_move_tongs; +} + +void +widow2_attack(edict_t *self) +{ + float range, luck; + qboolean blocked = false; + + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_BLOCKED) + { + blocked = true; + self->monsterinfo.aiflags &= ~AI_BLOCKED; + } + + if (!self->enemy) + { + return; + } + + if (self->bad_area) + { + if ((random() < 0.75) || (level.time < self->monsterinfo.attack_finished)) + { + self->monsterinfo.currentmove = &widow2_move_attack_pre_beam; + } + else + { + self->monsterinfo.currentmove = &widow2_move_attack_disrupt; + } + + return; + } + + WidowCalcSlots(self); + + /* if we can't see the target, spawn stuff */ + if ((self->monsterinfo.attack_state == AS_BLIND) && (SELF_SLOTS_LEFT >= 2)) + { + self->monsterinfo.currentmove = &widow2_move_spawn; + return; + } + + /* accept bias towards spawning */ + if (blocked && (SELF_SLOTS_LEFT >= 2)) + { + self->monsterinfo.currentmove = &widow2_move_spawn; + return; + } + + range = realrange(self, self->enemy); + + if (range < 600) + { + luck = random(); + + if (SELF_SLOTS_LEFT >= 2) + { + if (luck <= 0.40) + { + self->monsterinfo.currentmove = &widow2_move_attack_pre_beam; + } + else if ((luck <= 0.7) && !(level.time < self->monsterinfo.attack_finished)) + { + self->monsterinfo.currentmove = &widow2_move_attack_disrupt; + } + else + { + self->monsterinfo.currentmove = &widow2_move_spawn; + } + } + else + { + if ((luck <= 0.50) || (level.time < self->monsterinfo.attack_finished)) + { + self->monsterinfo.currentmove = &widow2_move_attack_pre_beam; + } + else + { + self->monsterinfo.currentmove = &widow2_move_attack_disrupt; + } + } + } + else + { + luck = random(); + + if (SELF_SLOTS_LEFT >= 2) + { + if (luck < 0.3) + { + self->monsterinfo.currentmove = &widow2_move_attack_pre_beam; + } + else if ((luck < 0.65) || (level.time < self->monsterinfo.attack_finished)) + { + self->monsterinfo.currentmove = &widow2_move_spawn; + } + else + { + self->monsterinfo.currentmove = &widow2_move_attack_disrupt; + } + } + else + { + if ((luck < 0.45) || (level.time < self->monsterinfo.attack_finished)) + { + self->monsterinfo.currentmove = &widow2_move_attack_pre_beam; + } + else + { + self->monsterinfo.currentmove = &widow2_move_attack_disrupt; + } + } + } +} + +void +widow2_attack_beam(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &widow2_move_attack_beam; +} + +void +widow2_reattack_beam(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + + if (infront(self, self->enemy)) + { + if (random() <= 0.5) + { + if ((random() < 0.7) || (SELF_SLOTS_LEFT < 2)) + { + self->monsterinfo.currentmove = &widow2_move_attack_beam; + } + else + { + self->monsterinfo.currentmove = &widow2_move_spawn; + } + } + else + { + self->monsterinfo.currentmove = &widow2_move_attack_post_beam; + } + } + else + { + self->monsterinfo.currentmove = &widow2_move_attack_post_beam; + } +} + +void +widow2_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage) +{ + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum = 1; + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + if (level.time < self->pain_debounce_time) + { + return; + } + + self->pain_debounce_time = level.time + 5; + + if (damage < 15) + { + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0); + } + else if (damage < 75) + { + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0); + + if ((skill->value < SKILL_HARDPLUS) && + (random() < (0.6 - (0.2 * ((float)skill->value))))) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &widow2_move_pain; + } + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0); + + if ((skill->value < SKILL_HARDPLUS) && + (random() < (0.75 - (0.1 * ((float)skill->value))))) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &widow2_move_pain; + } + } +} + +void +widow2_dead(edict_t *self) +{ +} + +void +KillChildren(edict_t *self) +{ + edict_t *ent; + int field; + + if (!self) + { + return; + } + + ent = NULL; + field = FOFS(classname); + + while (1) + { + ent = G_Find(ent, field, "monster_stalker"); + + if (!ent) + { + return; + } + + if ((ent->inuse) && (ent->health > 0)) + { + T_Damage(ent, self, self, vec3_origin, self->enemy->s.origin, vec3_origin, + (ent->health + 1), 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN); + } + } +} + +void +widow2_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, + int damage, vec3_t point /* unused */) +{ + int n; + int clipped; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + clipped = Q_min(damage, 100); + + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/bone/tris.md2", clipped, GIB_ORGANIC, + NULL, false); + } + + for (n = 0; n < 3; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", clipped, GIB_ORGANIC, + NULL, false); + } + + for (n = 0; n < 3; n++) + { + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib1/tris.md2", clipped, + GIB_METALLIC, NULL, 0, false); + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib2/tris.md2", clipped, + GIB_METALLIC, NULL, gi.soundindex("misc/fhit3.wav"), false); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib3/tris.md2", clipped, + GIB_METALLIC, NULL, 0, false); + ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2", clipped, + GIB_METALLIC, NULL, 0, false); + } + + ThrowGib(self, "models/objects/gibs/chest/tris.md2", clipped, GIB_ORGANIC); + ThrowHead(self, "models/objects/gibs/head2/tris.md2", clipped, GIB_ORGANIC); + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + { + return; + } + + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0); + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_NO; + self->count = 0; + KillChildren(self); + self->monsterinfo.quad_framenum = 0; + self->monsterinfo.double_framenum = 0; + self->monsterinfo.invincible_framenum = 0; + self->monsterinfo.currentmove = &widow2_move_death; +} + +qboolean +Widow2_CheckAttack(edict_t *self) +{ + vec3_t spot1, spot2; + vec3_t temp; + float chance = 0; + trace_t tr; + int enemy_range; + float enemy_yaw; + float real_enemy_range; + vec3_t f, r, u; + + if (!self) + { + return false; + } + + if (!self->enemy) + { + return false; + } + + WidowPowerups(self); + + if ((random() < 0.8) && (SELF_SLOTS_LEFT >= 2) && + (realrange(self, self->enemy) > 150)) + { + self->monsterinfo.aiflags |= AI_BLOCKED; + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + if (self->enemy->health > 0) + { + /* see if any entities are in the way of the shot */ + VectorCopy(self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy(self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace(spot1, NULL, NULL, spot2, self, + CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | + CONTENTS_LAVA); + + /* do we have a clear shot? */ + if (tr.ent != self->enemy) + { + /* go ahead and spawn stuff if we're mad a a client */ + if (self->enemy->client && (SELF_SLOTS_LEFT >= 2)) + { + self->monsterinfo.attack_state = AS_BLIND; + return true; + } + + if ((self->enemy->solid != SOLID_NOT) || (tr.fraction < 1.0)) + { + return false; + } + } + } + + enemy_range = range(self, self->enemy); + VectorSubtract(self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw2(temp); + + self->ideal_yaw = enemy_yaw; + + /* melee attack */ + if (self->timestamp < level.time) + { + real_enemy_range = realrange(self, self->enemy); + + if (real_enemy_range < 300) + { + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offsets[0], f, r, u, spot1); + VectorCopy(self->enemy->s.origin, spot2); + + if (widow2_tongue_attack_ok(spot1, spot2, 256)) + { + /* be nice in easy mode */ + if ((skill->value == SKILL_EASY) && (rand() & 3)) + { + return false; + } + + if (self->monsterinfo.melee) + { + self->monsterinfo.attack_state = AS_MELEE; + } + else + { + self->monsterinfo.attack_state = AS_MISSILE; + } + + return true; + } + } + } + + if (level.time < self->monsterinfo.attack_finished) + { + return false; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.8; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.8; + } + else if (enemy_range == RANGE_FAR) + { + chance = 0.5; + } + + if ((random() < chance) || (self->enemy->solid == SOLID_NOT)) + { + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + + return false; +} + +static void +Widow2Precache(void) +{ + /* cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs */ + gi.soundindex("parasite/parpain1.wav"); + gi.soundindex("parasite/parpain2.wav"); + gi.soundindex("parasite/pardeth1.wav"); + gi.soundindex("parasite/paratck1.wav"); + gi.soundindex("parasite/parsght1.wav"); + gi.soundindex("infantry/melee2.wav"); + gi.soundindex("misc/fhit3.wav"); + + gi.soundindex("tank/tnkatck3.wav"); + gi.soundindex("weapons/disrupt.wav"); + gi.soundindex("weapons/disint2.wav"); + + gi.modelindex("models/monsters/stalker/tris.md2"); + gi.modelindex("models/items/spawngro2/tris.md2"); + gi.modelindex("models/objects/gibs/sm_metal/tris.md2"); + gi.modelindex("models/proj/laser2/tris.md2"); + gi.modelindex("models/proj/disintegrator/tris.md2"); + + gi.modelindex("models/monsters/blackwidow/gib1/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib2/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib3/tris.md2"); + gi.modelindex("models/monsters/blackwidow/gib4/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib1/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib3/tris.md2"); + gi.modelindex("models/monsters/blackwidow2/gib4/tris.md2"); +} + +/* + * QUAKED monster_widow2 (1 .5 0) (-70 -70 0) (70 70 144) Ambush Trigger_Spawn Sight + */ +void +SP_monster_widow2(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + sound_pain1 = gi.soundindex("widow/bw2pain1.wav"); + sound_pain2 = gi.soundindex("widow/bw2pain2.wav"); + sound_pain3 = gi.soundindex("widow/bw2pain3.wav"); + sound_death = gi.soundindex("widow/death.wav"); + sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav"); + sound_tentacles_retract = gi.soundindex("brain/brnatck3.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + self->s.modelindex = gi.modelindex("models/monsters/blackwidow2/tris.md2"); + VectorSet(self->mins, -70, -70, 0); + VectorSet(self->maxs, 70, 70, 144); + + self->health = 2000 + 800 + 1000 * (skill->value); + + if (coop->value) + { + self->health += 500 * (skill->value); + } + + self->gib_health = -900; + self->mass = 2500; + + if (skill->value == SKILL_HARDPLUS) + { + self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD; + self->monsterinfo.power_armor_power = 750; + } + + self->yaw_speed = 30; + + self->flags |= FL_IMMUNE_LASER; + self->monsterinfo.aiflags |= AI_IGNORE_SHOTS; + + self->pain = widow2_pain; + self->die = widow2_die; + + self->monsterinfo.melee = widow2_melee; + self->monsterinfo.stand = widow2_stand; + self->monsterinfo.walk = widow2_walk; + self->monsterinfo.run = widow2_run; + self->monsterinfo.attack = widow2_attack; + self->monsterinfo.search = widow2_search; + self->monsterinfo.checkattack = Widow2_CheckAttack; + gi.linkentity(self); + + self->monsterinfo.currentmove = &widow2_move_stand; + self->monsterinfo.scale = MODEL_SCALE; + + Widow2Precache(); + WidowCalcSlots(self); + walkmonster_start(self); +} + +static void +WidowVelocityForDamage(int damage, vec3_t v) +{ + v[0] = damage * crandom(); + v[1] = damage * crandom(); + v[2] = damage * crandom() + 200.0; +} + +void +widow_gib_touch(edict_t *self, edict_t *other /* unused */, cplane_t *plane /* unused */, + csurface_t *surf /* unused */) +{ + if (!self) + { + return; + } + + self->solid = SOLID_NOT; + self->touch = NULL; + self->s.angles[PITCH] = 0; + self->s.angles[ROLL] = 0; + VectorClear(self->avelocity); + + if (self->plat2flags) + { + gi.sound(self, CHAN_VOICE, self->plat2flags, 1, ATTN_NORM, 0); + } +} + +void +ThrowWidowGib(edict_t *self, char *gibname, int damage, int type) +{ + if (!self || !gibname) + { + return; + } + + ThrowWidowGibReal(self, gibname, damage, type, NULL, false, 0, true); +} + +static void +ThrowWidowGibLoc(edict_t *self, char *gibname, int damage, + int type, vec3_t startpos, qboolean fade) +{ + if (!self || !gibname) + { + return; + } + + ThrowWidowGibReal(self, gibname, damage, type, startpos, false, 0, fade); +} + +void +ThrowWidowGibSized(edict_t *self, char *gibname, int damage, int type, + vec3_t startpos, int hitsound, qboolean fade) +{ + if (!self || !gibname) + { + return; + } + + ThrowWidowGibReal(self, gibname, damage, type, startpos, + true, hitsound, fade); +} + +void +ThrowWidowGibReal(edict_t *self, char *gibname, int damage, int type, + vec3_t startpos, qboolean sized, int hitsound, qboolean fade) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + if (!self || !gibname) + { + return; + } + + gib = G_Spawn(); + + if (startpos) + { + VectorCopy(startpos, gib->s.origin); + } + else + { + VectorScale(self->size, 0.5, size); + VectorAdd(self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + } + + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + gib->s.renderfx |= RF_IR_VISIBLE; + + if (fade) + { + gib->think = G_FreeEdict; + + /* sized gibs last longer */ + if (sized) + { + gib->nextthink = level.time + 20 + random() * 15; + } + else + { + gib->nextthink = level.time + 5 + random() * 10; + } + } + else + { + gib->think = G_FreeEdict; + + /* sized gibs last longer */ + if (sized) + { + gib->nextthink = level.time + 60 + random() * 15; + } + else + { + gib->nextthink = level.time + 25 + random() * 10; + } + } + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + WidowVelocityForDamage(damage, vd); + VectorMA(self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity(gib); + + gi.setmodel(gib, gibname); + + if (sized) + { + gib->plat2flags = hitsound; + gib->solid = SOLID_BBOX; + gib->avelocity[0] = random() * 400; + gib->avelocity[1] = random() * 400; + gib->avelocity[2] = random() * 200; + + if (gib->velocity[2] < 0) + { + gib->velocity[2] *= -1; + } + + gib->velocity[0] *= 2; + gib->velocity[1] *= 2; + ClipGibVelocity(gib); + gib->velocity[2] = Q_max((350 + (random() * 100.0)), gib->velocity[2]); + gib->gravity = 0.25; + gib->touch = widow_gib_touch; + gib->owner = self; + + if (gib->s.modelindex == gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2")) + { + VectorSet(gib->mins, -10, -10, 0); + VectorSet(gib->maxs, 10, 10, 10); + } + else + { + VectorSet(gib->mins, -5, -5, 0); + VectorSet(gib->maxs, 5, 5, 5); + } + } + else + { + gib->velocity[0] *= 2; + gib->velocity[1] *= 2; + gib->avelocity[0] = random() * 600; + gib->avelocity[1] = random() * 600; + gib->avelocity[2] = random() * 600; + } + + gi.linkentity(gib); +} + +void +ThrowSmallStuff(edict_t *self, vec3_t point) +{ + int n; + + if (!self) + { + return; + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, point, false); + } + + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 300, GIB_METALLIC, point, false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, point, false); +} + +static void +ThrowMoreStuff(edict_t *self, vec3_t point) +{ + int n; + + if (!self) + { + return; + } + + if (coop && coop->value) + { + ThrowSmallStuff(self, point); + return; + } + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, point, false); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 300, GIB_METALLIC, point, false); + } + + for (n = 0; n < 3; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, point, false); + } +} + +void +WidowExplode(edict_t *self) +{ + vec3_t org; + int n; + + if (!self) + { + return; + } + + self->think = WidowExplode; + VectorCopy(self->s.origin, org); + org[2] += 24 + (rand() & 15); + + if (self->count < 8) + { + org[2] += 24 + (rand() & 31); + } + + switch (self->count) + { + case 0: + org[0] -= 24; + org[1] -= 24; + break; + case 1: + org[0] += 24; + org[1] += 24; + ThrowSmallStuff(self, org); + break; + case 2: + org[0] += 24; + org[1] -= 24; + break; + case 3: + org[0] -= 24; + org[1] += 24; + ThrowMoreStuff(self, org); + break; + case 4: + org[0] -= 48; + org[1] -= 48; + break; + case 5: + org[0] += 48; + org[1] += 48; + ThrowArm1(self); + break; + case 6: + org[0] -= 48; + org[1] += 48; + ThrowArm2(self); + break; + case 7: + org[0] += 48; + org[1] -= 48; + ThrowSmallStuff(self, org); + break; + case 8: + org[0] += 18; + org[1] += 18; + org[2] = self->s.origin[2] + 48; + ThrowMoreStuff(self, org); + break; + case 9: + org[0] -= 18; + org[1] += 18; + org[2] = self->s.origin[2] + 48; + break; + case 10: + org[0] += 18; + org[1] -= 18; + org[2] = self->s.origin[2] + 48; + break; + case 11: + org[0] -= 18; + org[1] -= 18; + org[2] = self->s.origin[2] + 48; + break; + case 12: + self->s.sound = 0; + + for (n = 0; n < 1; n++) + { + ThrowWidowGib(self, "models/objects/gibs/sm_meat/tris.md2", + 400, GIB_ORGANIC); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGib(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGib(self, "models/objects/gibs/sm_metal/tris.md2", + 400, GIB_METALLIC); + } + + self->deadflag = DEAD_DEAD; + self->think = monster_think; + self->nextthink = level.time + 0.1; + self->monsterinfo.currentmove = &widow2_move_dead; + return; + } + + self->count++; + + if ((self->count >= 9) && (self->count <= 12)) + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_ALL); + } + else + { + gi.WriteByte(svc_temp_entity); + + if (self->count % 2) + { + gi.WriteByte(TE_EXPLOSION1); + } + else + { + gi.WriteByte(TE_EXPLOSION1_NP); + } + + gi.WritePosition(org); + gi.multicast(self->s.origin, MULTICAST_ALL); + } + + self->nextthink = level.time + 0.1; +} + +void +WidowExplosion1(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = {23.74, -37.67, 76.96}; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL); + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, startpoint, false); + } + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, startpoint, false); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 300, GIB_METALLIC, startpoint, false); + } +} + +void +WidowExplosion2(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = {-20.49, 36.92, 73.52}; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL); + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, startpoint, false); + } + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, startpoint, false); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 300, GIB_METALLIC, startpoint, false); + } +} + +void +WidowExplosion3(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = {2.11, 0.05, 92.20}; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL); + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, startpoint, false); + } + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, startpoint, false); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 300, GIB_METALLIC, startpoint, false); + } +} + +void +WidowExplosion4(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = {-28.04, -35.57, -77.56}; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL); + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, startpoint, false); + } + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, startpoint, false); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 300, GIB_METALLIC, startpoint, false); + } +} + +void +WidowExplosion5(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = {-20.11, -1.11, 40.76}; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL); + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, startpoint, false); + } + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, startpoint, false); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 300, GIB_METALLIC, startpoint, false); + } +} + +void +WidowExplosion6(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = {-20.11, -1.11, 40.76}; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL); + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, startpoint, false); + } + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, startpoint, false); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 300, GIB_METALLIC, startpoint, false); + } +} + +void +WidowExplosion7(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset = {-20.11, -1.11, 40.76}; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL); + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, startpoint, false); + } + + for (n = 0; n < 1; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, startpoint, false); + } + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 300, GIB_METALLIC, startpoint, false); + } +} + +void +WidowExplosionLeg(edict_t *self) +{ + vec3_t f, r, u, startpoint; + vec3_t offset1 = {-31.89, -47.86, 67.02}; + vec3_t offset2 = {-44.9, -82.14, 54.72}; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offset1, f, r, u, startpoint); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL); + + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib2/tris.md2", + 200, GIB_METALLIC, startpoint, gi.soundindex("misc/fhit3.wav"), + false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, startpoint, false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, startpoint, false); + + G_ProjectSource2(self->s.origin, offset2, f, r, u, startpoint); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL); + + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib1/tris.md2", + 300, GIB_METALLIC, startpoint, gi.soundindex("misc/fhit3.wav"), + false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, startpoint, false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, startpoint, false); +} + +void +ThrowArm1(edict_t *self) +{ + int n; + vec3_t f, r, u, startpoint; + vec3_t offset1 = {65.76, 17.52, 7.56}; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offset1, f, r, u, startpoint); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1_BIG); + gi.WritePosition(startpoint); + gi.multicast(self->s.origin, MULTICAST_ALL); + + for (n = 0; n < 2; n++) + { + ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", + 100, GIB_METALLIC, startpoint, false); + } +} + +void +ThrowArm2(edict_t *self) +{ + vec3_t f, r, u, startpoint; + vec3_t offset1 = {65.76, 17.52, 7.56}; + + if (!self) + { + return; + } + + AngleVectors(self->s.angles, f, r, u); + G_ProjectSource2(self->s.origin, offset1, f, r, u, startpoint); + + ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib4/tris.md2", + 200, GIB_METALLIC, startpoint, gi.soundindex("misc/fhit3.wav"), + false); + ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", + 300, GIB_ORGANIC, startpoint, false); +} diff --git a/src/game/monster/widow/widow2.h b/src/game/monster/widow/widow2.h new file mode 100644 index 000000000..5f08d6fe9 --- /dev/null +++ b/src/game/monster/widow/widow2.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media 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. + * + * ======================================================================= + * + * Black Widow (stage 2) animations. + * + * ======================================================================= + */ + +#define FRAME_blackwidow3 0 +#define FRAME_walk01 1 +#define FRAME_walk02 2 +#define FRAME_walk03 3 +#define FRAME_walk04 4 +#define FRAME_walk05 5 +#define FRAME_walk06 6 +#define FRAME_walk07 7 +#define FRAME_walk08 8 +#define FRAME_walk09 9 +#define FRAME_spawn01 10 +#define FRAME_spawn02 11 +#define FRAME_spawn03 12 +#define FRAME_spawn04 13 +#define FRAME_spawn05 14 +#define FRAME_spawn06 15 +#define FRAME_spawn07 16 +#define FRAME_spawn08 17 +#define FRAME_spawn09 18 +#define FRAME_spawn10 19 +#define FRAME_spawn11 20 +#define FRAME_spawn12 21 +#define FRAME_spawn13 22 +#define FRAME_spawn14 23 +#define FRAME_spawn15 24 +#define FRAME_spawn16 25 +#define FRAME_spawn17 26 +#define FRAME_spawn18 27 +#define FRAME_firea01 28 +#define FRAME_firea02 29 +#define FRAME_firea03 30 +#define FRAME_firea04 31 +#define FRAME_firea05 32 +#define FRAME_firea06 33 +#define FRAME_firea07 34 +#define FRAME_fireb01 35 +#define FRAME_fireb02 36 +#define FRAME_fireb03 37 +#define FRAME_fireb04 38 +#define FRAME_fireb05 39 +#define FRAME_fireb06 40 +#define FRAME_fireb07 41 +#define FRAME_fireb08 42 +#define FRAME_fireb09 43 +#define FRAME_fireb10 44 +#define FRAME_fireb11 45 +#define FRAME_fireb12 46 +#define FRAME_tongs01 47 +#define FRAME_tongs02 48 +#define FRAME_tongs03 49 +#define FRAME_tongs04 50 +#define FRAME_tongs05 51 +#define FRAME_tongs06 52 +#define FRAME_tongs07 53 +#define FRAME_tongs08 54 +#define FRAME_pain01 55 +#define FRAME_pain02 56 +#define FRAME_pain03 57 +#define FRAME_pain04 58 +#define FRAME_pain05 59 +#define FRAME_death01 60 +#define FRAME_death02 61 +#define FRAME_death03 62 +#define FRAME_death04 63 +#define FRAME_death05 64 +#define FRAME_death06 65 +#define FRAME_death07 66 +#define FRAME_death08 67 +#define FRAME_death09 68 +#define FRAME_death10 69 +#define FRAME_death11 70 +#define FRAME_death12 71 +#define FRAME_death13 72 +#define FRAME_death14 73 +#define FRAME_death15 74 +#define FRAME_death16 75 +#define FRAME_death17 76 +#define FRAME_death18 77 +#define FRAME_death19 78 +#define FRAME_death20 79 +#define FRAME_death21 80 +#define FRAME_death22 81 +#define FRAME_death23 82 +#define FRAME_death24 83 +#define FRAME_death25 84 +#define FRAME_death26 85 +#define FRAME_death27 86 +#define FRAME_death28 87 +#define FRAME_death29 88 +#define FRAME_death30 89 +#define FRAME_death31 90 +#define FRAME_death32 91 +#define FRAME_death33 92 +#define FRAME_death34 93 +#define FRAME_death35 94 +#define FRAME_death36 95 +#define FRAME_death37 96 +#define FRAME_death38 97 +#define FRAME_death39 98 +#define FRAME_death40 99 +#define FRAME_death41 100 +#define FRAME_death42 101 +#define FRAME_death43 102 +#define FRAME_death44 103 +#define FRAME_dthsrh01 104 +#define FRAME_dthsrh02 105 +#define FRAME_dthsrh03 106 +#define FRAME_dthsrh04 107 +#define FRAME_dthsrh05 108 +#define FRAME_dthsrh06 109 +#define FRAME_dthsrh07 110 +#define FRAME_dthsrh08 111 +#define FRAME_dthsrh09 112 +#define FRAME_dthsrh10 113 +#define FRAME_dthsrh11 114 +#define FRAME_dthsrh12 115 +#define FRAME_dthsrh13 116 +#define FRAME_dthsrh14 117 +#define FRAME_dthsrh15 118 +#define FRAME_dthsrh16 119 +#define FRAME_dthsrh17 120 +#define FRAME_dthsrh18 121 +#define FRAME_dthsrh19 122 +#define FRAME_dthsrh20 123 +#define FRAME_dthsrh21 124 +#define FRAME_dthsrh22 125 +#define MODEL_SCALE 2.000000 diff --git a/src/game/monster/wizard/wizard.c b/src/game/monster/wizard/wizard.c new file mode 100644 index 000000000..c9921e0f1 --- /dev/null +++ b/src/game/monster/wizard/wizard.c @@ -0,0 +1,422 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "wizard.h" + +static int sound_proj_hit; +static int sound_attack; +static int sound_death; +static int sound_idle1; +static int sound_idle2; +static int sound_pain; +static int sound_sight; + +// Stand +static mframe_t wizard_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}, +}; +mmove_t wizard_move_stand = +{ + FRAME_hover1, + FRAME_hover15, + wizard_frames_stand, + NULL +}; + +void +wizard_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &wizard_move_stand; +} + +// Run +static mframe_t wizard_frames_run [] = +{ + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + + {ai_run, 16, NULL}, + {ai_run, 16, NULL}, + {ai_run, 16, NULL} +}; +mmove_t wizard_move_run = +{ + FRAME_fly1, + FRAME_fly14, + wizard_frames_run, + NULL +}; + +void +wizard_run(edict_t *self) +{ + self->monsterinfo.currentmove = &wizard_move_run; +} + +static void +wizard_frame(edict_t *self) +{ + static int frame = 0; + + self->s.frame = (FRAME_magatt5 - frame); + frame++; + + if (frame > 5) + { + frame = 0; + } +} + +// decino: Quake plays this animation backwards, so we'll have to do some hacking +static mframe_t wizard_frames_finish [] = +{ + {ai_charge, 0, wizard_frame}, + {ai_charge, 0, wizard_frame}, + {ai_charge, 0, wizard_frame}, + {ai_charge, 0, wizard_frame}, + + {ai_charge, 0, wizard_frame} +}; +mmove_t wizard_move_finish = +{ + FRAME_magatt1, + FRAME_magatt5, + wizard_frames_finish, + wizard_run +}; + +void +wizard_finish_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &wizard_move_finish; +} + +void +spit_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + { + return; + } + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, 0); + else + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(15); + gi.WritePosition(self->s.origin); + gi.WriteDir((!plane) ? vec3_origin : plane->normal); + gi.WriteByte(209); + gi.multicast(self->s.origin, MULTICAST_PVS); + + gi.sound (self, CHAN_WEAPON, sound_proj_hit, 1, ATTN_NORM, 0); + } + G_FreeEdict(self); +} + +static void +spit_fire(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed) +{ + edict_t *spit; + trace_t tr; + + if (!self) + { + return; + } + + spit = G_Spawn(); + spit->svflags = SVF_DEADMONSTER; + VectorCopy(start, spit->s.origin); + VectorCopy(start, spit->s.old_origin); + vectoangles(dir, spit->s.angles); + VectorScale(dir, speed, spit->velocity); + + spit->movetype = MOVETYPE_FLYMISSILE; + spit->clipmask = MASK_SHOT; + spit->solid = SOLID_BBOX; + spit->s.effects |= (EF_BLASTER|EF_TRACKER); + VectorClear(spit->mins); + VectorClear(spit->maxs); + + spit->s.modelindex = gi.modelindex("models/proj/spit/tris.md2"); + spit->owner = self; + spit->touch = spit_touch; + spit->nextthink = level.time + 2; + spit->think = G_FreeEdict; + spit->dmg = damage; + spit->classname = "spit"; + gi.linkentity(spit); + + tr = gi.trace (self->s.origin, NULL, NULL, spit->s.origin, spit, MASK_SHOT); + + if (tr.fraction < 1.0) + { + VectorMA(spit->s.origin, -10, dir, spit->s.origin); + spit->touch(spit, tr.ent, NULL, NULL); + } +} + +static void +wizard_spit(edict_t *self) +{ + vec3_t forward, right; + vec3_t start; + vec3_t dir; + vec3_t vec; + vec3_t offset = {0, 0, 30}; + + AngleVectors (self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, offset, forward, right, start); + VectorCopy(self->enemy->s.origin, vec); + vec[2] += self->enemy->viewheight; + + VectorSubtract(vec, start, dir); + VectorNormalize(dir); + + spit_fire(self, start, dir, 9, 600); +} + +static void +wizard_prespit(edict_t *self) +{ + gi.sound(self, CHAN_WEAPON, sound_attack, 1, ATTN_NORM, 0); +} + +// Attack +static mframe_t wizard_frames_attack [] = +{ + {ai_charge, 0, wizard_prespit}, + {ai_charge, 0, wizard_spit}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, wizard_spit}, + {ai_charge, 0, NULL} +}; +mmove_t wizard_move_attack = +{ + FRAME_magatt1, + FRAME_magatt6, + wizard_frames_attack, + wizard_finish_attack +}; + +void +wizard_attack(edict_t *self) +{ + self->monsterinfo.currentmove = &wizard_move_attack; +} + +// Pain +static mframe_t wizard_frames_pain [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t wizard_move_pain = +{ + FRAME_pain1, + FRAME_pain4, + wizard_frames_pain, + wizard_run +}; + +void +wizard_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage) +{ + if (level.time < self->pain_debounce_time) + return; + self->pain_debounce_time = level.time + 3; + gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + + // decino: No pain animations in Nightmare mode + if (skill->value == SKILL_HARDPLUS) + return; + self->monsterinfo.currentmove = &wizard_move_pain; +} + +static void +wizard_fling(edict_t *self) +{ + self->velocity[0] = -200 + 400 * random(); + self->velocity[1] = -200 + 400 * random(); + self->velocity[2] = 100 + 100 * random(); + + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, -8); + + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; +} + +void +wizard_dead(edict_t *self) +{ + self->nextthink = 0; + gi.linkentity(self); +} + +// Death +static mframe_t wizard_frames_death [] = +{ + {ai_move, 0, wizard_fling}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t wizard_move_death = +{ + FRAME_death1, + FRAME_death8, + wizard_frames_death, + wizard_dead +}; + +void +wizard_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health <= self->gib_health) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 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; + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->monsterinfo.currentmove = &wizard_move_death; +} + +void +wizard_sight(edict_t *self, edict_t *other /* unused */) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +wizard_search(edict_t *self) +{ + float r; + r = random() * 5; + + if (r > 4.5) + gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_NORM, 0); + if (r < 1.5) + gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_NORM, 0); +} + +/* + * QUAKED monster_wizard (1 .5 0) (-16, -16, -24) (16, 16, 40) Ambush Trigger_Spawn Sight + */ +void +SP_monster_wizard(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/wizard/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 40); + self->health = 80; + + sound_proj_hit = gi.soundindex("wizard/hit.wav"); + sound_attack = gi.soundindex("wizard/wattack.wav"); + sound_death = gi.soundindex("wizard/wdeath.wav"); + sound_idle1 = gi.soundindex("wizard/widle1.wav"); + sound_idle2 = gi.soundindex("wizard/widle2.wav"); + sound_pain = gi.soundindex("wizard/wpain.wav"); + sound_sight = gi.soundindex("wizard/wsight.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -40; + self->mass = 80; + + self->monsterinfo.stand = wizard_stand; + self->monsterinfo.walk = wizard_run; + self->monsterinfo.run = wizard_run; + self->monsterinfo.attack = wizard_attack; + self->monsterinfo.sight = wizard_sight; + self->monsterinfo.search = wizard_search; + + self->pain = wizard_pain; + self->die = wizard_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + flymonster_start(self); +} diff --git a/src/game/monster/wizard/wizard.h b/src/game/monster/wizard/wizard.h new file mode 100644 index 000000000..664446e69 --- /dev/null +++ b/src/game/monster/wizard/wizard.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Wizard animations. + * + * ======================================================================= + */ + +#define FRAME_hover1 0 +#define FRAME_hover2 1 +#define FRAME_hover3 2 +#define FRAME_hover4 3 +#define FRAME_hover5 4 +#define FRAME_hover6 5 +#define FRAME_hover7 6 +#define FRAME_hover8 7 +#define FRAME_hover9 8 +#define FRAME_hover10 9 +#define FRAME_hover11 10 +#define FRAME_hover12 11 +#define FRAME_hover13 12 +#define FRAME_hover14 13 +#define FRAME_hover15 14 +#define FRAME_fly1 15 +#define FRAME_fly2 16 +#define FRAME_fly3 17 +#define FRAME_fly4 18 +#define FRAME_fly5 19 +#define FRAME_fly6 20 +#define FRAME_fly7 21 +#define FRAME_fly8 22 +#define FRAME_fly9 23 +#define FRAME_fly10 24 +#define FRAME_fly11 25 +#define FRAME_fly12 26 +#define FRAME_fly13 27 +#define FRAME_fly14 28 +#define FRAME_magatt1 29 +#define FRAME_magatt2 30 +#define FRAME_magatt3 31 +#define FRAME_magatt4 32 +#define FRAME_magatt5 33 +#define FRAME_magatt6 34 +#define FRAME_magatt7 35 +#define FRAME_magatt8 36 +#define FRAME_magatt9 37 +#define FRAME_magatt10 38 +#define FRAME_magatt11 39 +#define FRAME_magatt12 40 +#define FRAME_magatt13 41 +#define FRAME_pain1 42 +#define FRAME_pain2 43 +#define FRAME_pain3 44 +#define FRAME_pain4 45 +#define FRAME_death1 46 +#define FRAME_death2 47 +#define FRAME_death3 48 +#define FRAME_death4 49 +#define FRAME_death5 50 +#define FRAME_death6 51 +#define FRAME_death7 52 +#define FRAME_death8 53 + +#define MODEL_SCALE 1.000000 diff --git a/src/game/monster/zombie/zombie.c b/src/game/monster/zombie/zombie.c new file mode 100644 index 000000000..56ab037dc --- /dev/null +++ b/src/game/monster/zombie/zombie.c @@ -0,0 +1,765 @@ +/* +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. + +*/ + +#include "../../header/local.h" +#include "zombie.h" + +static int sound_sight; +static int sound_search; +static int sound_fling; +static int sound_pain1; +static int sound_pain2; +static int sound_fall; +static int sound_miss; +static int sound_hit; + +static void zombie_down(edict_t *self); +static void zombie_get_up_attempt(edict_t *self); + +/* Stand */ +static mframe_t zombie_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}, +}; +mmove_t zombie_move_stand = +{ + FRAME_stand1, + FRAME_stand15, + zombie_frames_stand, + NULL +}; + +void +zombie_stand(edict_t *self) +{ + self->monsterinfo.currentmove = &zombie_move_stand; +} + +/* Run */ +static mframe_t zombie_frames_run[] = +{ + {ai_run, 1, NULL}, + {ai_run, 1, NULL}, + {ai_run, 0, NULL}, + {ai_run, 1, NULL}, + + {ai_run, 2, NULL}, + {ai_run, 3, NULL}, + {ai_run, 4, NULL}, + {ai_run, 4, NULL}, + + {ai_run, 2, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + {ai_run, 0, NULL}, + + {ai_run, 2, NULL}, + {ai_run, 4, NULL}, + {ai_run, 6, NULL}, + {ai_run, 7, NULL}, + + {ai_run, 3, NULL}, + {ai_run, 8, NULL} +}; + +mmove_t zombie_move_run = +{ + FRAME_run1, + FRAME_run18, + zombie_frames_run, + NULL +}; + +void +zombie_run(edict_t *self) +{ + self->monsterinfo.currentmove = &zombie_move_run; +} + +/* Walk */ +static mframe_t zombie_frames_walk[] = +{ + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL} +}; + +mmove_t zombie_move_walk = +{ + FRAME_walk1, + FRAME_walk19, + zombie_frames_walk, + NULL +}; + +void +zombie_walk(edict_t *self) +{ + self->monsterinfo.currentmove = &zombie_move_walk; +} + +/* Sight */ +void +zombie_sight(edict_t *self, edict_t *other /* unused */) +{ + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); +} + +void +zombie_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + G_FreeEdict(ent); +} + +void +zombie_gib_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict(ent); + return; + } + if (other->takedamage) + { + gi.sound(ent, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0); + T_Damage(other, ent, ent->owner, ent->s.origin, ent->s.origin, vec3_origin, ent->dmg, ent->dmg, 0, 0); + G_FreeEdict(ent); + return; + } + gi.sound(ent, CHAN_WEAPON, sound_miss, 1, ATTN_NORM, 0); + VectorSet(ent->avelocity, 0, 0, 0); + VectorSet(ent->velocity, 0, 0, 0); + ent->touch = zombie_touch; +} + +static void +fire_zombie_gib(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed) +{ + edict_t *gib; + vec3_t dir; + vec3_t forward, right, up; + + if (!self) + { + return; + } + + vectoangles(aimdir, dir); + AngleVectors(dir, forward, right, up); + + gib = G_Spawn(); + VectorCopy(start, gib->s.origin); + VectorScale(aimdir, speed, gib->velocity); + VectorMA(gib->velocity, 200 + crandom() * 10.0, up, gib->velocity); + VectorMA(gib->velocity, crandom() * 10.0, right, gib->velocity); + VectorSet(gib->avelocity, 300, 300, 300); + gib->movetype = MOVETYPE_BOUNCE; + gib->clipmask = MASK_SHOT; + gib->solid = SOLID_BBOX; + gib->s.effects |= EF_GIB; + VectorClear(gib->mins); + VectorClear(gib->maxs); + gib->s.modelindex = gi.modelindex("models/monsters/objects/gibs/sm_meat/tris.md2"); + gib->owner = self; + gib->touch = zombie_gib_touch; + gib->nextthink = level.time + 2.5; + gib->think = G_FreeEdict; + gib->dmg = damage; + gib->classname = "zombie_gib"; + + gi.linkentity(gib); + gi.sound(self, CHAN_WEAPON, sound_fling, 1, ATTN_NORM, 0); +} + +static void +zombie_fire_gib_step(edict_t *self) +{ + vec3_t start; + vec3_t forward, right; + vec3_t aim; + vec3_t offset = {16, 0, 8}; + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, offset, forward, right, start); + VectorCopy(forward, aim); + + fire_zombie_gib(self, start, aim, 10, 600); +} + +/* Attack (1) */ +static mframe_t zombie_frames_attack1 [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, zombie_fire_gib_step} +}; +mmove_t zombie_move_attack1 = +{ + FRAME_atta1, + FRAME_atta13, + zombie_frames_attack1, + zombie_run +}; + +/* Attack (2) */ +static mframe_t zombie_frames_attack2 [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, zombie_fire_gib_step} +}; +mmove_t zombie_move_attack2 = +{ + FRAME_attb1, + FRAME_attb14, + zombie_frames_attack2, + zombie_run +}; + +/* Attack (3) */ +static mframe_t zombie_frames_attack3 [] = +{ + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, zombie_fire_gib_step}, +}; +mmove_t zombie_move_attack3 = +{ + FRAME_attc1, + FRAME_attc12, + zombie_frames_attack3, + zombie_run +}; + +/* Attack */ +void +zombie_attack(edict_t *self) +{ + float r = random(); + + if (r < 0.3) + { + self->monsterinfo.currentmove = &zombie_move_attack1; + } + else if (r < 0.6) + { + self->monsterinfo.currentmove = &zombie_move_attack2; + } + else + { + self->monsterinfo.currentmove = &zombie_move_attack3; + } +} + +static mframe_t zombie_frames_get_up [] = +{ + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t zombie_move_get_up = +{ + FRAME_paine12, + FRAME_paine30, + zombie_frames_get_up, + zombie_run +}; + +static void +zombie_pain1(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); +} + +static void +zombie_pain2(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); +} + +static void +zombie_hit_floor(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_fall, 1, ATTN_NORM, 0); +} + +static void +zombie_get_up(edict_t *self) +{ + VectorSet(self->maxs, 16, 16, 40); + self->takedamage = DAMAGE_YES; + self->health = 60; + zombie_sight(self, NULL); + + if (!M_walkmove(self, 0, 0)) + { + zombie_get_up_attempt(self); + return; + } + self->monsterinfo.currentmove = &zombie_move_get_up; +} + +static void +zombie_start_fall(edict_t *self) +{ + gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); +} + +/* Down */ +static mframe_t zombie_frames_get_up_attempt [] = +{ + {ai_move, 0, zombie_get_up_attempt} +}; + +mmove_t zombie_move_get_up_attempt = +{ + FRAME_paine12, + FRAME_paine12, + zombie_frames_get_up_attempt, + NULL +}; + +static void +zombie_get_up_attempt(edict_t *self) +{ + static int down = 0; + + zombie_down(self); + + /* Try getting up in 5 seconds */ + if (down >= 50) + { + down = 0; + zombie_get_up(self); + return; + } + self->s.frame = FRAME_paine11; + self->monsterinfo.currentmove = &zombie_move_get_up_attempt; + + down++; +} + +static void +zombie_down(edict_t *self) +{ + self->takedamage = DAMAGE_NO; + self->health = 60; + VectorSet(self->maxs, 16, 16, 0); +} + +/* Pain (1) */ +static mframe_t zombie_frames_pain1 [] = +{ + {ai_move, 0, zombie_pain1}, + {ai_move, 3, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + + {ai_move, 3, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t zombie_move_pain1 = +{ + FRAME_paina1, + FRAME_paina12, + zombie_frames_pain1, + zombie_run +}; + +/* Pain (2) */ +static mframe_t zombie_frames_pain2 [] = +{ + {ai_move, 0, zombie_pain2}, + {ai_move, 2, NULL}, + {ai_move, 8, NULL}, + {ai_move, 6, NULL}, + + {ai_move, 2, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, zombie_hit_floor}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t zombie_move_pain2 = +{ + FRAME_painb1, + FRAME_painb28, + zombie_frames_pain2, + zombie_run +}; + +/* Pain (3) */ +static mframe_t zombie_frames_pain3 [] = +{ + {ai_move, 0, zombie_pain2}, + {ai_move, 0, NULL}, + {ai_move, 3, NULL}, + {ai_move, 1, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; +mmove_t zombie_move_pain3 = +{ + FRAME_painc1, + FRAME_painc18, + zombie_frames_pain3, + zombie_run +}; + +/* Pain (4) */ +static mframe_t zombie_frames_pain4 [] = +{ + {ai_move, 0, zombie_pain1}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL} +}; +mmove_t zombie_move_pain4 = +{ + FRAME_paind1, + FRAME_paind13, + zombie_frames_pain4, + zombie_run +}; + +/* Pain (5) */ +static mframe_t zombie_frames_fall_start [] = +{ + {ai_move, 0, zombie_start_fall}, + {ai_move, -8, NULL}, + {ai_move, -5, NULL}, + {ai_move, -3, NULL}, + + {ai_move, -1, NULL}, + {ai_move, -2, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + + {ai_move, -2, NULL}, + {ai_move, 0, zombie_hit_floor}, + {ai_move, 0, zombie_down} +}; +mmove_t zombie_move_fall_start = +{ + FRAME_paine1, + FRAME_paine11, + zombie_frames_fall_start, + zombie_get_up_attempt +}; + +/* Pain */ +void +zombie_pain(edict_t *self, edict_t *other, float kick, int damage) +{ + float r; + self->health = 60; + + if (damage < 9) + { + return; + } + + if (self->monsterinfo.currentmove == &zombie_move_fall_start) + { + return; + } + + if (damage >= 25) + { + self->monsterinfo.currentmove = &zombie_move_fall_start; + return; + } + + if (self->pain_debounce_time > level.time) + { + self->monsterinfo.currentmove = &zombie_move_fall_start; + return; + } + + if ( + (self->monsterinfo.currentmove == &zombie_move_pain1) || + (self->monsterinfo.currentmove == &zombie_move_pain2) || + (self->monsterinfo.currentmove == &zombie_move_pain3) || + (self->monsterinfo.currentmove == &zombie_move_pain4) + ) + { + self->pain_debounce_time = level.time + 3; + return; + } + + /* decino: No pain animations in Nightmare mode */ + if (skill->value >= SKILL_HARDPLUS) + { + return; + } + + r = random(); + + if (r < 0.25) + { + self->monsterinfo.currentmove = &zombie_move_pain1; + } + else if (r < 0.5) + { + self->monsterinfo.currentmove = &zombie_move_pain2; + } + else if (r < 0.75) + { + self->monsterinfo.currentmove = &zombie_move_pain3; + } + else + { + self->monsterinfo.currentmove = &zombie_move_pain4; + } +} + +// Death +void +zombie_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 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; +} + +/* Search */ +void +zombie_search(edict_t *self) +{ + if (random() < 0.2) + { + gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0); + } +} + +/* + * QUAKED monster_zombie (1 .5 0) (-16, -16, -24) (16, 16, 40) Ambush Trigger_Spawn Sight + */ +void +SP_monster_zombie(edict_t *self) +{ + self->s.modelindex = gi.modelindex("models/monsters/zombie/tris.md2"); + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 40); + self->health = 60; + + sound_sight = gi.soundindex("zombie/z_idle.wav"); + sound_search = gi.soundindex("zombie/z_idle.wav"); + sound_fling = gi.soundindex("zombie/z_shot1.wav"); + sound_pain1 = gi.soundindex("zombie/z_pain.wav"); + sound_pain2 = gi.soundindex("zombie/z_pain1.wav"); + sound_fall = gi.soundindex("zombie/z_fall.wav"); + sound_miss = gi.soundindex("zombie/z_miss.wav"); + sound_hit = gi.soundindex("zombie/z_hit.wav"); + + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + self->gib_health = -5; + self->mass = 60; + + self->monsterinfo.stand = zombie_stand; + self->monsterinfo.walk = zombie_walk; + self->monsterinfo.run = zombie_run; + self->monsterinfo.attack = zombie_attack; + self->monsterinfo.sight = zombie_sight; + self->monsterinfo.search = zombie_search; + + self->pain = zombie_pain; + self->die = zombie_die; + + self->monsterinfo.scale = MODEL_SCALE; + gi.linkentity(self); + + walkmonster_start(self); +} diff --git a/src/game/monster/zombie/zombie.h b/src/game/monster/zombie/zombie.h new file mode 100644 index 000000000..9fd25940f --- /dev/null +++ b/src/game/monster/zombie/zombie.h @@ -0,0 +1,227 @@ +/* + * Copyright (C) 1997-2001 Id Software 30 Inc. + * Copyright (c) ZeniMax Media 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 30 or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful 30 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 30 write to the Free Software + * Foundation 30 Inc. 30 59 Temple Place - Suite 330 30 Boston 30 MA + * 02111-1307 30 USA. + * + * ======================================================================= + * + * Zombie animations. + * + * ======================================================================= + */ + +#define FRAME_stand1 0 +#define FRAME_stand2 1 +#define FRAME_stand3 2 +#define FRAME_stand4 3 +#define FRAME_stand5 4 +#define FRAME_stand6 5 +#define FRAME_stand7 6 +#define FRAME_stand8 7 +#define FRAME_stand9 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_walk1 15 +#define FRAME_walk2 16 +#define FRAME_walk3 17 +#define FRAME_walk4 18 +#define FRAME_walk5 19 +#define FRAME_walk6 20 +#define FRAME_walk7 21 +#define FRAME_walk8 22 +#define FRAME_walk9 23 +#define FRAME_walk10 24 +#define FRAME_walk11 25 +#define FRAME_walk12 26 +#define FRAME_walk13 27 +#define FRAME_walk14 28 +#define FRAME_walk15 29 +#define FRAME_walk16 30 +#define FRAME_walk17 31 +#define FRAME_walk18 32 +#define FRAME_walk19 33 +#define FRAME_run1 34 +#define FRAME_run2 35 +#define FRAME_run3 36 +#define FRAME_run4 37 +#define FRAME_run5 38 +#define FRAME_run6 39 +#define FRAME_run7 40 +#define FRAME_run8 41 +#define FRAME_run9 42 +#define FRAME_run10 43 +#define FRAME_run11 44 +#define FRAME_run12 45 +#define FRAME_run13 46 +#define FRAME_run14 47 +#define FRAME_run15 48 +#define FRAME_run16 49 +#define FRAME_run17 50 +#define FRAME_run18 51 +#define FRAME_atta1 52 +#define FRAME_atta2 53 +#define FRAME_atta3 54 +#define FRAME_atta4 55 +#define FRAME_atta5 56 +#define FRAME_atta6 57 +#define FRAME_atta7 58 +#define FRAME_atta8 59 +#define FRAME_atta9 60 +#define FRAME_atta10 61 +#define FRAME_atta11 62 +#define FRAME_atta12 63 +#define FRAME_atta13 64 +#define FRAME_attb1 65 +#define FRAME_attb2 66 +#define FRAME_attb3 67 +#define FRAME_attb4 68 +#define FRAME_attb5 69 +#define FRAME_attb6 70 +#define FRAME_attb7 71 +#define FRAME_attb8 72 +#define FRAME_attb9 73 +#define FRAME_attb10 74 +#define FRAME_attb11 75 +#define FRAME_attb12 76 +#define FRAME_attb13 77 +#define FRAME_attb14 78 +#define FRAME_attc1 79 +#define FRAME_attc2 80 +#define FRAME_attc3 81 +#define FRAME_attc4 82 +#define FRAME_attc5 83 +#define FRAME_attc6 84 +#define FRAME_attc7 85 +#define FRAME_attc8 86 +#define FRAME_attc9 87 +#define FRAME_attc10 88 +#define FRAME_attc11 89 +#define FRAME_attc12 90 +#define FRAME_paina1 91 +#define FRAME_paina2 92 +#define FRAME_paina3 93 +#define FRAME_paina4 94 +#define FRAME_paina5 95 +#define FRAME_paina6 96 +#define FRAME_paina7 97 +#define FRAME_paina8 98 +#define FRAME_paina9 99 +#define FRAME_paina10 100 +#define FRAME_paina11 101 +#define FRAME_paina12 102 +#define FRAME_painb1 103 +#define FRAME_painb2 104 +#define FRAME_painb3 105 +#define FRAME_painb4 106 +#define FRAME_painb5 107 +#define FRAME_painb6 108 +#define FRAME_painb7 109 +#define FRAME_painb8 110 +#define FRAME_painb9 111 +#define FRAME_painb10 112 +#define FRAME_painb11 113 +#define FRAME_painb12 114 +#define FRAME_painb13 115 +#define FRAME_painb14 116 +#define FRAME_painb15 117 +#define FRAME_painb16 118 +#define FRAME_painb17 119 +#define FRAME_painb18 120 +#define FRAME_painb19 121 +#define FRAME_painb20 122 +#define FRAME_painb21 123 +#define FRAME_painb22 124 +#define FRAME_painb23 125 +#define FRAME_painb24 126 +#define FRAME_painb25 127 +#define FRAME_painb26 128 +#define FRAME_painb27 129 +#define FRAME_painb28 130 +#define FRAME_painc1 131 +#define FRAME_painc2 132 +#define FRAME_painc3 133 +#define FRAME_painc4 134 +#define FRAME_painc5 135 +#define FRAME_painc6 136 +#define FRAME_painc7 137 +#define FRAME_painc8 138 +#define FRAME_painc9 139 +#define FRAME_painc10 140 +#define FRAME_painc11 141 +#define FRAME_painc12 142 +#define FRAME_painc13 143 +#define FRAME_painc14 144 +#define FRAME_painc15 145 +#define FRAME_painc16 146 +#define FRAME_painc17 147 +#define FRAME_painc18 148 +#define FRAME_paind1 149 +#define FRAME_paind2 150 +#define FRAME_paind3 151 +#define FRAME_paind4 152 +#define FRAME_paind5 153 +#define FRAME_paind6 154 +#define FRAME_paind7 155 +#define FRAME_paind8 156 +#define FRAME_paind9 157 +#define FRAME_paind10 158 +#define FRAME_paind11 159 +#define FRAME_paind12 160 +#define FRAME_paind13 161 +#define FRAME_paine1 162 +#define FRAME_paine2 163 +#define FRAME_paine3 164 +#define FRAME_paine4 165 +#define FRAME_paine5 166 +#define FRAME_paine6 167 +#define FRAME_paine7 168 +#define FRAME_paine8 169 +#define FRAME_paine9 170 +#define FRAME_paine10 171 +#define FRAME_paine11 172 +#define FRAME_paine12 173 +#define FRAME_paine13 174 +#define FRAME_paine14 175 +#define FRAME_paine15 176 +#define FRAME_paine16 177 +#define FRAME_paine17 178 +#define FRAME_paine18 179 +#define FRAME_paine19 180 +#define FRAME_paine20 181 +#define FRAME_paine21 182 +#define FRAME_paine22 183 +#define FRAME_paine23 184 +#define FRAME_paine24 185 +#define FRAME_paine25 186 +#define FRAME_paine26 187 +#define FRAME_paine27 188 +#define FRAME_paine28 189 +#define FRAME_paine29 190 +#define FRAME_paine30 191 +#define FRAME_cruc_1 192 +#define FRAME_cruc_2 193 +#define FRAME_cruc_3 194 +#define FRAME_cruc_4 195 +#define FRAME_cruc_5 196 +#define FRAME_cruc_6 197 + +#define MODEL_SCALE 1.000000 diff --git a/stuff/models/entity.dat b/stuff/models/entity.dat index b430923da..ebb4f0994 100644 --- a/stuff/models/entity.dat +++ b/stuff/models/entity.dat @@ -303,10 +303,10 @@ monster_army|models/monsters/army/tris.md2|1.0|1.0|1.0|general|-16,|-16,|-24|16, monster_demon|models/monsters/demon/tris.md2|1.0|1.0|1.0|general|-32,|-32,|-24|32,|32,|64|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 monster_dog|models/monsters/dog/tris.md2|1.0|1.0|1.0|general|-32,|-32,|-24|32,|32,|40|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 monster_enforcer|models/monsters/enforcer/tris.md2|1.0|1.0|1.0|general|-16,|-16,|-24|16,|16,|40|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 -monster_fish|models/monsters/fish/tris.md2|1.0|1.0|1.0|general|-25.0|-25.0|-14.0|25.0|25.0|14.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Fish|1.0|0.5|0.0 monster_hknight|models/monsters/hknight/tris.md2|1.0|1.0|1.0|general|-16,|-16,|-24|16,|16,|40|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 monster_knight|models/monsters/knight/tris.md2|1.0|1.0|1.0|general|-16,|-16,|-24|16,|16,|40|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 monster_ogre|models/monsters/ogre/tris.md2|1.0|1.0|1.0|general|-32,|-32,|-24|32,|32,|64|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 +monster_rotfish|models/monsters/rotfish/tris.md2|1.0|1.0|1.0|general|-25.0|-25.0|-14.0|25.0|25.0|14.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Rotfish|1.0|0.5|0.0 monster_shalrath|models/monsters/shalrath/tris.md2|1.0|1.0|1.0|general|-32,|-32,|-24|32,|32,|48|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 monster_shambler|models/monsters/shambler/tris.md2|1.0|1.0|1.0|general|-32.0|-32.0|-24.0|32.0|32.0|64.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Shambler|1.0|0.5|0.0 monster_tarbaby|models/monsters/tarbaby/tris.md2|1.0|1.0|1.0|general|-16,|-16,|-24|16,|16,|40|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 @@ -678,33 +678,34 @@ misc_flag||1.0|1.0|1.0|general|-10|-10|0|10|10|80|shadow|0|0.0|0.0|0|0|0|0:0|0|0 misc_magic_portal||1.0|1.0|1.0|general|-32.0|-32.0|-24.0|32.0|32.0|-16.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Magic Portal|1.0|0.0|0.0 misc_remote_camera||1.0|1.0|1.0|general|-32.0|-32.0|-24.0|32.0|32.0|-16.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Remote Camera|1.0|0.0|0.0 misc_update_spawner||1.0|1.0|1.0|general|0.0|0.0|0.0|0.0|0.0|0.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Updates level spawn point to trigger's position. Relevant for teleport spell.|.5|.5|.5 -monster_assassin||1.0|1.0|1.0|general|-16.0|-16.0|-32.0|16.0|16.0|48.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Assassin|1.0|0.5|0.0 -monster_bee||1.0|1.0|1.0|general|-16.0|-16.0|-24.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Bee (unfinished)|1.0|0.5|0.0 -monster_chicken||1.0|1.0|1.0|general|-16.0|-16.0|-24.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Chicken|1.0|0.5|0.0 -monster_chkroktk||1.0|1.0|1.0|general|-16|-16|-26|16|16|26|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 +monster_assassin|models/monsters/assassin/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-32.0|16.0|16.0|48.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Assassin|1.0|0.5|0.0 +monster_bee|models/monsters/bee/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-24.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Bee (unfinished)|1.0|0.5|0.0 +monster_chicken|models/monsters/chicken2/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-24.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Chicken|1.0|0.5|0.0 +monster_chkroktk|models/monsters/rat/tris.fm|1.0|1.0|1.0|general|-16|-16|-26|16|16|26|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 monster_elflord|models/monsters/elflord/tris.fm|1.0|1.0|1.0|general|-24.0|-24.0|-64.0|24.0|24.0|16.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Celestial Watcher|1.0|0.5|0.0 -monster_gkrokon||1.0|1.0|1.0|general|-20.0|-20.0|-0.0|20.0|20.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Gkrokon|1.0|0.5|0.0 -monster_gorgon||1.0|1.0|1.0|general|-16.0|-16.0|0.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Gorgon|1.0|0.5|0.0 -monster_gorgon_leader||1.0|1.0|1.0|general|-16|-16|-0|16|16|32|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 -monster_harpy1||1.0|1.0|1.0|general|-16|-12|16|16|12|AMBUSH|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||.5|0|-16 -monster_high_priestess||1.0|1.0|1.0|general|-24.0|-24.0|0.0|24.0|24.0|72.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|High Priestess|1.0|0.5|0.0 -monster_imp1||1.0|1.0|1.0|general|-16|0|16|16|32|AMBUSH|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||.5|0|-16 -monster_morcalavin1||1.0|1.0|1.0|general|-24|-50|24|24|40||shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||.5|0|-24 -monster_mssithra||1.0|1.0|1.0|general|-36.0|-36.0|0.0|36.0|36.0|96.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Mssithra|1.0|0.5|0.0 -monster_ogle1||1.0|1.0|1.0|general|-16|-24|16|16|16|pushing|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||.5|0|-16 +monster_fish|models/monsters/fish/tris.fm|1.0|1.0|1.0|general|-25.0|-25.0|-14.0|25.0|25.0|14.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Fish|1.0|0.5|0.0 +monster_gkrokon|models/monsters/gkrokon/tris.fm|1.0|1.0|1.0|general|-20.0|-20.0|-0.0|20.0|20.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Gkrokon|1.0|0.5|0.0 +monster_gorgon|models/monsters/gorgon/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|0.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Gorgon|1.0|0.5|0.0 +monster_gorgon_leader|models/monsters/gorgon/tris.fm|1.0|1.0|1.0|general|-16|-16|-0|16|16|32|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||1|.5|0 +monster_harpy|models/monsters/harpy/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-12.0|16.0|16.0|12.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Harpy|1.0|0.5|0.0 +monster_high_priestess|models/monsters/highpriestess/tris.fm|1.0|1.0|1.0|general|-24.0|-24.0|0.0|24.0|24.0|72.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|High Priestess|1.0|0.5|0.0 +monster_imp|models/monsters/imp/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-24.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Imp (unfinished)|1.0|0.5|0.0 +monster_morcalavin|models/monsters/morcalavin/tris.fm|1.0|1.0|1.0|general|-24.0|-24.0|-50.0|24.0|24.0|40.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Morcalavin|1.0|0.5|0.0 +monster_mssithra|models/monsters/mutantsithra/tris.fm|1.0|1.0|1.0|general|-36.0|-36.0|0.0|36.0|36.0|96.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Mssithra|1.0|0.5|0.0 +monster_ogle|models/monsters/ogle/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-24.0|16.0|16.0|16.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Ogle|1.0|0.5|0.0 monster_palace_plague_guard||1.0|1.0|1.0|general|-17.0|-25.0|-1.0|22.0|12.0|63.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Plague Guard|1.0|0.5|0.0 monster_palace_plague_guard_invisible||1.0|1.0|1.0|general|-17.0|-25.0|-1.0|22.0|12.0|63.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Invisible Plague Guard|1.0|0.5|0.0 monster_plagueElf|models/monsters/plaguelf/tris.fm|1.0|1.0|1.0|general|-17.0|-25.0|-1.0|22.0|12.0|63.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Elf|1.0|0.5|0.0 monster_rat|models/monsters/rat/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-0.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Rat|1.0|0.5|0.0 monster_rat_giant|models/monsters/rat/superduperat/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-0.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Giant Rat|1.0|0.5|0.0 -monster_seraph_guard1||1.0|1.0|1.0|general|-24|-34|24|24|34|AMBUSH|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||.5|0|-24 -monster_seraph_overlord1||1.0|1.0|1.0|general|-24|-34|24|24|34|AMBUSH|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none||.5|0|-24 +monster_seraph_guard|models/monsters/guard/tris.fm|1.0|1.0|1.0|general|-24.0|-24.0|-34.0|24.0|24.0|34.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Seraph Guard|1.0|0.5|0.0 +monster_seraph_overlord|models/monsters/overlord/tris.fm|1.0|1.0|1.0|general|-24.0|-24.0|-34.0|24.0|24.0|34.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Seraph Overlord|1.0|0.5|0.0 monster_spreader|models/monsters/spreader/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-0.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Spreader|1.0|0.5|0.0 -monster_ssithra||1.0|1.0|1.0|general|-16.0|-16.0|-32.0|16.0|16.0|26.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Plague Ssithra|1.0|0.5|0.0 -monster_tcheckrik_female||1.0|1.0|1.0|general|-16.0|-16.0|-32.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Female Tcheckrik|1.0|0.5|0.0 -monster_tcheckrik_male||1.0|1.0|1.0|general|-16.0|-16.0|-32.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Male Tcheckrik|1.0|0.5|0.0 -monster_tcheckrik_mothers||1.0|1.0|1.0|general|-40.0|-40.0|-75.0|40.0|40.0|75.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Tcheckrik Mother|1.0|0.5|0.0 -monster_trial_beast||1.0|1.0|1.0|general|-100.0|-100.0|-36.0|100.0|100.0|150.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Trial Beast|1.0|0.5|0.0 +monster_ssithra|models/monsters/ssithra/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-32.0|16.0|16.0|26.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Plague Ssithra|1.0|0.5|0.0 +monster_tcheckrik_female|models/monsters/tcheckrik/female/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-32.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Female Tcheckrik|1.0|0.5|0.0 +monster_tcheckrik_male|models/monsters/tcheckrik/male/tris.fm|1.0|1.0|1.0|general|-16.0|-16.0|-32.0|16.0|16.0|32.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Male Tcheckrik|1.0|0.5|0.0 +monster_tcheckrik_mothers|models/monsters/mother/tris.fm|1.0|1.0|1.0|general|-40.0|-40.0|-75.0|40.0|40.0|75.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Tcheckrik Mother|1.0|0.5|0.0 +monster_trial_beast|models/monsters/beast/tris.fm|1.0|1.0|1.0|general|-100.0|-100.0|-36.0|100.0|100.0|150.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Trial Beast|1.0|0.5|0.0 obj_andwallhanging|models/objects/andwallhang/tris.fm|1.0|1.0|1.0|general|0.0|-19.0|-24.0|4.0|19.0|24.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Circular Andorian wall hanging|0.3|0.3|1.0 obj_banner||1.0|1.0|1.0|general|-8.0|-44.0|-296.0|8.0|44.0|0.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Banner|0.3|0.3|1.0 obj_banneronpole||1.0|1.0|1.0|general|-8.0|-28.0|-30.0|8.0|28.0|30.0|shadow|0|0.0|0.0|0|0|0|0:0|0|0|none|Banner on Pole|0.3|0.3|1.0