1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042 |
- // Copyright (c) ZeniMax Media Inc.
- // Licensed under the GNU General Public License 2.0.
- /*
- ==============================================================================
- stalker
- ==============================================================================
- */
- #include "../g_local.h"
- #include "m_rogue_stalker.h"
- #include <float.h>
- static cached_soundindex sound_pain;
- static cached_soundindex sound_die;
- static cached_soundindex sound_sight;
- static cached_soundindex sound_punch_hit1;
- static cached_soundindex sound_punch_hit2;
- static cached_soundindex sound_idle;
- bool stalker_do_pounce(edict_t *self, const vec3_t &dest);
- void stalker_walk(edict_t *self);
- void stalker_dodge_jump(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);
- bool stalker_ok_to_transition(edict_t *self);
- void stalker_stand(edict_t *self);
- inline bool STALKER_ON_CEILING(edict_t *ent)
- {
- return (ent->gravityVector[2] > 0);
- }
- //=========================
- //=========================
- bool stalker_ok_to_transition(edict_t *self)
- {
- trace_t trace;
- vec3_t pt, start;
- float max_dist;
- float margin;
- float end_height;
- if (STALKER_ON_CEILING(self))
- {
- // [Paril-KEX] if we get knocked off the ceiling, always
- // fall downwards
- if (!self->groundentity)
- return true;
- 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;
- }
- pt = self->s.origin;
- pt[2] += max_dist;
- trace = gi.trace(self->s.origin, self->mins, self->maxs, pt, self, MASK_MONSTERSOLID);
- if (trace.fraction == 1.0f ||
- !(trace.contents & CONTENTS_SOLID) ||
- (trace.ent != world))
- {
- if (STALKER_ON_CEILING(self))
- {
- if (trace.plane.normal[2] < 0.9f)
- return false;
- }
- else
- {
- if (trace.plane.normal[2] > -0.9f)
- 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
- start = pt;
- start[2] = self->s.origin[2];
- trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID);
- if (trace.fraction == 1.0f || !(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];
- start = pt;
- start[2] = self->s.origin[2];
- trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID);
- if (trace.fraction == 1.0f || !(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];
- start = pt;
- start[2] = self->s.origin[2];
- trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID);
- if (trace.fraction == 1.0f || !(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];
- start = pt;
- start[2] = self->s.origin[2];
- trace = gi.traceline(start, pt, self, MASK_MONSTERSOLID);
- if (trace.fraction == 1.0f || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world))
- return false;
- if (fabsf(end_height + margin - trace.endpos[2]) > 8)
- return false;
- return true;
- }
- //=========================
- //=========================
- MONSTERINFO_SIGHT(stalker_sight) (edict_t *self, edict_t *other) -> void
- {
- gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
- }
- // ******************
- // IDLE
- // ******************
- void stalker_idle_noise(edict_t *self)
- {
- gi.sound(self, CHAN_VOICE, sound_idle, 0.5, ATTN_IDLE, 0);
- }
- mframe_t stalker_frames_idle[] = {
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand, 0, stalker_idle_noise },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand }
- };
- MMOVE_T(stalker_move_idle) = { FRAME_idle01, FRAME_idle21, stalker_frames_idle, stalker_stand };
- mframe_t stalker_frames_idle2[] = {
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand }
- };
- MMOVE_T(stalker_move_idle2) = { FRAME_idle201, FRAME_idle213, stalker_frames_idle2, stalker_stand };
- MONSTERINFO_IDLE(stalker_idle) (edict_t *self) -> void
- {
- if (frandom() < 0.35f)
- M_SetAnimation(self, &stalker_move_idle);
- else
- M_SetAnimation(self, &stalker_move_idle2);
- }
- // ******************
- // STAND
- // ******************
- mframe_t stalker_frames_stand[] = {
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand, 0, stalker_idle_noise },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand }
- };
- MMOVE_T(stalker_move_stand) = { FRAME_idle01, FRAME_idle21, stalker_frames_stand, stalker_stand };
- MONSTERINFO_STAND(stalker_stand) (edict_t *self) -> void
- {
- if (frandom() < 0.25f)
- M_SetAnimation(self, &stalker_move_stand);
- else
- M_SetAnimation(self, &stalker_move_idle2);
- }
- // ******************
- // RUN
- // ******************
- mframe_t stalker_frames_run[] = {
- { ai_run, 13, monster_footstep },
- { ai_run, 17 },
- { ai_run, 21, monster_footstep },
- { ai_run, 18 }
- };
- MMOVE_T(stalker_move_run) = { FRAME_run01, FRAME_run04, stalker_frames_run, nullptr };
- MONSTERINFO_RUN(stalker_run) (edict_t *self) -> void
- {
- if (self->monsterinfo.aiflags & AI_STAND_GROUND)
- M_SetAnimation(self, &stalker_move_stand);
- else
- M_SetAnimation(self, &stalker_move_run);
- }
- // ******************
- // WALK
- // ******************
- mframe_t stalker_frames_walk[] = {
- { ai_walk, 4, monster_footstep },
- { ai_walk, 6 },
- { ai_walk, 8 },
- { ai_walk, 5 },
- { ai_walk, 4, monster_footstep },
- { ai_walk, 6 },
- { ai_walk, 8 },
- { ai_walk, 4 }
- };
- MMOVE_T(stalker_move_walk) = { FRAME_walk01, FRAME_walk08, stalker_frames_walk, stalker_walk };
- MONSTERINFO_WALK(stalker_walk) (edict_t *self) -> void
- {
- M_SetAnimation(self, &stalker_move_walk);
- }
- // ******************
- // false death
- // ******************
- mframe_t stalker_frames_reactivate[] = {
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move, 0, monster_footstep }
- };
- MMOVE_T(stalker_move_false_death_end) = { FRAME_reactive01, FRAME_reactive04, stalker_frames_reactivate, stalker_run };
- void stalker_reactivate(edict_t *self)
- {
- self->monsterinfo.aiflags &= ~AI_STAND_GROUND;
- M_SetAnimation(self, &stalker_move_false_death_end);
- }
- void stalker_heal(edict_t *self)
- {
- if (skill->integer == 2)
- self->health += 2;
- else if (skill->integer == 3)
- self->health += 3;
- else
- self->health++;
- self->monsterinfo.setskin(self);
- if (self->health >= self->max_health)
- {
- self->health = self->max_health;
- stalker_reactivate(self);
- }
- }
- 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)
- {
- M_SetAnimation(self, &stalker_move_false_death);
- }
- mframe_t stalker_frames_false_death_start[] = {
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- };
- 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)
- {
- self->s.angles[2] = 0;
- self->gravityVector = { 0, 0, -1 };
- self->monsterinfo.aiflags |= AI_STAND_GROUND;
- M_SetAnimation(self, &stalker_move_false_death_start);
- }
- // ******************
- // PAIN
- // ******************
- mframe_t stalker_frames_pain[] = {
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move }
- };
- MMOVE_T(stalker_move_pain) = { FRAME_pain01, FRAME_pain04, stalker_frames_pain, stalker_run };
- PAIN(stalker_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
- {
- if (self->deadflag)
- return;
- if (self->groundentity == nullptr)
- return;
- // if we're reactivating or false dying, ignore the pain.
- if (self->monsterinfo.active_move == &stalker_move_false_death_end ||
- self->monsterinfo.active_move == &stalker_move_false_death_start)
- return;
- if (self->monsterinfo.active_move == &stalker_move_false_death)
- {
- stalker_reactivate(self);
- return;
- }
- if ((self->health > 0) && (self->health < (self->max_health / 4)))
- {
- if (frandom() < 0.30f)
- {
- 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_sec;
- gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
- if (mod.id == MOD_CHAINFIST || damage > 10) // don't react unless the damage was significant
- {
- // stalker should dodge jump periodically to help avoid damage.
- if (self->groundentity && (frandom() < 0.5f))
- stalker_dodge_jump(self);
- else if (M_ShouldReactToPain(self, mod)) // no pain anims in nightmare
- M_SetAnimation(self, &stalker_move_pain);
- }
- }
- MONSTERINFO_SETSKIN(stalker_setskin) (edict_t *self) -> void
- {
- if (self->health < (self->max_health / 2))
- self->s.skinnum = 1;
- else
- self->s.skinnum = 0;
- }
- // ******************
- // STALKER ATTACK
- // ******************
- void stalker_shoot_attack(edict_t *self)
- {
- vec3_t offset, start, f, r, dir;
- vec3_t end;
- float dist;
- trace_t trace;
- if (!has_valid_enemy(self))
- return;
- if (self->groundentity && frandom() < 0.33f)
- {
- dir = self->enemy->s.origin - self->s.origin;
- dist = dir.length();
- if ((dist > 256) || (frandom() < 0.5f))
- stalker_do_pounce(self, self->enemy->s.origin);
- else
- stalker_jump_straightup(self);
- }
- AngleVectors(self->s.angles, f, r, nullptr);
- offset = { 24, 0, 6 };
- start = M_ProjectFlashSource(self, offset, f, r);
- dir = self->enemy->s.origin - start;
- if (frandom() < 0.3f)
- PredictAim(self, self->enemy, start, 1000, true, 0, &dir, &end);
- else
- end = self->enemy->s.origin;
- trace = gi.traceline(start, end, self, MASK_PROJECTILE);
- if (trace.ent == self->enemy || trace.ent == world)
- {
- dir.normalize();
- monster_fire_blaster2(self, start, dir, 5, 800, MZ2_STALKER_BLASTER, EF_BLASTER);
- }
- }
- void stalker_shoot_attack2(edict_t *self)
- {
- if (frandom() < 0.5)
- stalker_shoot_attack(self);
- }
- mframe_t stalker_frames_shoot[] = {
- { ai_charge, 13 },
- { ai_charge, 17, stalker_shoot_attack },
- { ai_charge, 21 },
- { ai_charge, 18, stalker_shoot_attack2 }
- };
- MMOVE_T(stalker_move_shoot) = { FRAME_run01, FRAME_run04, stalker_frames_shoot, stalker_run };
- MONSTERINFO_ATTACK(stalker_attack_ranged) (edict_t *self) -> void
- {
- if (!has_valid_enemy(self))
- return;
- // PMM - circle strafe stuff
- if (frandom() > 0.5f)
- {
- self->monsterinfo.attack_state = AS_STRAIGHT;
- }
- else
- {
- if (frandom() <= 0.5f) // switch directions
- self->monsterinfo.lefty = !self->monsterinfo.lefty;
- self->monsterinfo.attack_state = AS_SLIDING;
- }
- M_SetAnimation(self, &stalker_move_shoot);
- }
- // ******************
- // close combat
- // ******************
- void stalker_swing_attack(edict_t *self)
- {
- vec3_t aim = { MELEE_DISTANCE, 0, 0 };
- if (fire_hit(self, aim, irandom(5, 10), 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);
- }
- else
- self->monsterinfo.melee_debounce_time = level.time + 0.8_sec;
- }
- mframe_t stalker_frames_swing_l[] = {
- { ai_charge, 2 },
- { ai_charge, 4 },
- { ai_charge, 6 },
- { ai_charge, 10, monster_footstep },
- { ai_charge, 5, stalker_swing_attack },
- { ai_charge, 5 },
- { ai_charge, 5 },
- { ai_charge, 5, monster_footstep } // stalker_swing_check_l
- };
- MMOVE_T(stalker_move_swing_l) = { FRAME_attack01, FRAME_attack08, stalker_frames_swing_l, stalker_run };
- mframe_t stalker_frames_swing_r[] = {
- { ai_charge, 4 },
- { ai_charge, 6, monster_footstep },
- { ai_charge, 6, stalker_swing_attack },
- { ai_charge, 10 },
- { ai_charge, 5, monster_footstep } // stalker_swing_check_r
- };
- MMOVE_T(stalker_move_swing_r) = { FRAME_attack11, FRAME_attack15, stalker_frames_swing_r, stalker_run };
- MONSTERINFO_MELEE(stalker_attack_melee) (edict_t *self) -> void
- {
- if (!has_valid_enemy(self))
- return;
- if (frandom() < 0.5f)
- M_SetAnimation(self, &stalker_move_swing_l);
- else
- M_SetAnimation(self, &stalker_move_swing_r);
- }
- // ******************
- // POUNCE
- // ******************
- // ====================
- // ====================
- bool stalker_check_lz(edict_t *self, edict_t *target, const vec3_t &dest)
- {
- if ((gi.pointcontents(dest) & MASK_WATER) || (target->waterlevel))
- return false;
- if (!target->groundentity)
- return false;
- vec3_t jumpLZ;
- // 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.25f;
- 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;
- }
- // ====================
- // ====================
- bool stalker_do_pounce(edict_t *self, const vec3_t &dest)
- {
- vec3_t dist;
- float length;
- vec3_t jumpAngles;
- vec3_t jumpLZ;
- float velocity = 400.1f;
- // 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;
- dist = dest - self->s.origin;
- // make sure we're pointing in that direction 15deg margin of error.
- jumpAngles = vectoangles(dist);
- if (fabsf(jumpAngles[YAW] - self->s.angles[YAW]) > 45)
- return false; // not facing the player...
- if (isnan(jumpAngles[YAW]))
- return false; // Switch why
- self->ideal_yaw = jumpAngles[YAW];
- M_ChangeYaw(self);
- length = dist.length();
- if (length > 450)
- return false; // can't jump that far...
- jumpLZ = dest;
- vec3_t dir = dist.normalized();
- // find a valid angle/velocity combination
- while (velocity <= 800)
- {
- if (M_CalculatePitchToFire(self, jumpLZ, self->s.origin, dir, velocity, 3, false, true))
- break;
- velocity += 200;
- }
- // nothing found
- if (velocity > 800)
- return false;
- self->velocity = dir * velocity;
- return true;
- }
- // ******************
- // DODGE
- // ******************
- //===================
- // stalker_jump_straightup
- //===================
- void stalker_jump_straightup(edict_t *self)
- {
- if (self->deadflag)
- return;
- if (STALKER_ON_CEILING(self))
- {
- if (stalker_ok_to_transition(self))
- {
- self->gravityVector[2] = -1;
- self->s.angles[2] += 180.0f;
- if (self->s.angles[2] > 360.0f)
- self->s.angles[2] -= 360.0f;
- self->groundentity = nullptr;
- }
- }
- else if (self->groundentity) // make sure we're standing on SOMETHING...
- {
- self->velocity[0] += crandom() * 5;
- self->velocity[1] += crandom() * 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 = nullptr;
- }
- }
- }
- mframe_t stalker_frames_jump_straightup[] = {
- { ai_move, 1, stalker_jump_straightup },
- { ai_move, 1, stalker_jump_wait_land },
- { ai_move, -1, monster_footstep },
- { ai_move, -1 }
- };
- MMOVE_T(stalker_move_jump_straightup) = { FRAME_jump04, FRAME_jump07, stalker_frames_jump_straightup, stalker_run };
- //===================
- // stalker_dodge_jump - abstraction so pain function can trigger a dodge jump too without
- // faking the inputs to stalker_dodge
- //===================
- void stalker_dodge_jump(edict_t *self)
- {
- M_SetAnimation(self, &stalker_move_jump_straightup);
- }
- #if 0
- mframe_t stalker_frames_dodge_run[] = {
- { ai_run, 13 },
- { ai_run, 17 },
- { ai_run, 21 },
- { ai_run, 18, monster_done_dodge }
- };
- MMOVE_T(stalker_move_dodge_run) = { FRAME_run01, FRAME_run04, stalker_frames_dodge_run, nullptr };
- #endif
- MONSTERINFO_DODGE(stalker_dodge) (edict_t *self, edict_t *attacker, gtime_t eta, trace_t *tr, bool gravity) -> void
- {
- if (!self->groundentity || self->health <= 0)
- return;
- if (!self->enemy)
- {
- self->enemy = attacker;
- FoundTarget(self);
- return;
- }
- // PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
- // seeing numbers like 13 and 14)
- if ((eta < FRAME_TIME_MS) || (eta > 5_sec))
- return;
- if (self->timestamp > level.time)
- return;
- self->timestamp = level.time + random_time(1_sec, 5_sec);
- // this will override the foundtarget call of stalker_run
- stalker_dodge_jump(self);
- }
- // ******************
- // Jump onto / off of things
- // ******************
- //===================
- //===================
- void stalker_jump_down(edict_t *self)
- {
- vec3_t forward, up;
- AngleVectors(self->s.angles, forward, nullptr, up);
- self->velocity += (forward * 100);
- self->velocity += (up * 300);
- }
- //===================
- //===================
- void stalker_jump_up(edict_t *self)
- {
- vec3_t forward, up;
- AngleVectors(self->s.angles, forward, nullptr, up);
- self->velocity += (forward * 200);
- self->velocity += (up * 450);
- }
- //===================
- //===================
- void stalker_jump_wait_land(edict_t *self)
- {
- if ((frandom() < 0.4f) && (level.time >= self->monsterinfo.attack_finished))
- {
- self->monsterinfo.attack_finished = level.time + 300_ms;
- stalker_shoot_attack(self);
- }
- if (self->groundentity == nullptr)
- {
- self->gravity = 1.3f;
- 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;
- }
- }
- mframe_t stalker_frames_jump_up[] = {
- { ai_move, -8 },
- { ai_move, -8 },
- { ai_move, -8 },
- { ai_move, -8 },
- { ai_move, 0, stalker_jump_up },
- { ai_move, 0, stalker_jump_wait_land },
- { ai_move, 0, monster_footstep }
- };
- MMOVE_T(stalker_move_jump_up) = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_up, stalker_run };
- mframe_t stalker_frames_jump_down[] = {
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move, 0, stalker_jump_down },
- { ai_move, 0, stalker_jump_wait_land },
- { ai_move, 0, monster_footstep }
- };
- MMOVE_T(stalker_move_jump_down) = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_down, stalker_run };
- //============
- // stalker_jump - this is only used for jumping onto or off of things. for dodge jumping,
- // use stalker_dodge_jump
- //============
- void stalker_jump(edict_t *self, blocked_jump_result_t result)
- {
- if (!self->enemy)
- return;
- if (result == blocked_jump_result_t::JUMP_JUMP_UP)
- M_SetAnimation(self, &stalker_move_jump_up);
- else
- M_SetAnimation(self, &stalker_move_jump_down);
- }
- // ******************
- // Blocked
- // ******************
- MONSTERINFO_BLOCKED(stalker_blocked) (edict_t *self, float dist) -> bool
- {
- if (!has_valid_enemy(self))
- return false;
- bool onCeiling = STALKER_ON_CEILING(self);
- if (!onCeiling)
- {
- if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP)
- {
- if (result != blocked_jump_result_t::JUMP_TURN)
- stalker_jump(self, result);
- return true;
- }
- if (blocked_checkplat(self, dist))
- return true;
- if (visible(self, self->enemy) && frandom() < 0.1f)
- {
- stalker_do_pounce(self, self->enemy->s.origin);
- return true;
- }
- }
- else
- {
- if (stalker_ok_to_transition(self))
- {
- self->gravityVector[2] = -1;
- self->s.angles[2] += 180.0f;
- if (self->s.angles[2] > 360.0f)
- self->s.angles[2] -= 360.0f;
- self->groundentity = nullptr;
- return true;
- }
- }
- return false;
- }
- // [Paril-KEX] quick patch-job to fix stalkers endlessly floating up into the sky
- MONSTERINFO_PHYSCHANGED(stalker_physics_change) (edict_t *self) -> void
- {
- if (STALKER_ON_CEILING(self) && !self->groundentity)
- {
- self->gravityVector[2] = -1;
- self->s.angles[2] += 180.0f;
- if (self->s.angles[2] > 360.0f)
- self->s.angles[2] -= 360.0f;
- }
- }
- // ******************
- // Death
- // ******************
- void stalker_dead(edict_t *self)
- {
- self->mins = { -28, -28, -18 };
- self->maxs = { 28, 28, -4 };
- monster_dead(self);
- }
- mframe_t stalker_frames_death[] = {
- { ai_move },
- { ai_move, -5 },
- { ai_move, -10 },
- { ai_move, -20 },
- { ai_move, -10 },
- { ai_move, -10 },
- { ai_move, -5 },
- { ai_move, -5 },
- { ai_move, 0, monster_footstep }
- };
- MMOVE_T(stalker_move_death) = { FRAME_death01, FRAME_death09, stalker_frames_death, stalker_dead };
- DIE(stalker_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
- {
- // dude bit it, make him fall!
- self->movetype = MOVETYPE_TOSS;
- self->s.angles[2] = 0;
- self->gravityVector = { 0, 0, -1 };
- // check for gib
- if (M_CheckGib(self, mod))
- {
- gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
- self->s.skinnum /= 2;
- ThrowGibs(self, damage, {
- { 2, "models/objects/gibs/sm_meat/tris.md2" },
- { 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC },
- { "models/monsters/stalker/gibs/bodya.md2", GIB_SKINNED },
- { "models/monsters/stalker/gibs/bodyb.md2", GIB_SKINNED },
- { 2, "models/monsters/stalker/gibs/claw.md2", GIB_SKINNED | GIB_UPRIGHT },
- { 2, "models/monsters/stalker/gibs/leg.md2", GIB_SKINNED | GIB_UPRIGHT },
- { 2, "models/monsters/stalker/gibs/foot.md2", GIB_SKINNED },
- { "models/monsters/stalker/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
- });
- 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;
- M_SetAnimation(self, &stalker_move_death);
- }
- // ******************
- // SPAWN
- // ******************
- /*QUAKED monster_stalker (1 .5 0) (-28 -28 -18) (28 28 18) Ambush Trigger_Spawn Sight OnRoof NoJumping
- Spider Monster
- ONROOF - Monster starts sticking to the roof.
- */
- constexpr spawnflags_t SPAWNFLAG_STALKER_ONROOF = 8_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_STALKER_NOJUMPING = 16_spawnflag;
- void SP_monster_stalker(edict_t *self)
- {
- if ( !M_AllowSpawn( self ) ) {
- G_FreeEdict( self );
- return;
- }
- sound_pain.assign("stalker/pain.wav");
- sound_die.assign("stalker/death.wav");
- sound_sight.assign("stalker/sight.wav");
- sound_punch_hit1.assign("stalker/melee1.wav");
- sound_punch_hit2.assign("stalker/melee2.wav");
- sound_idle.assign("stalker/idle.wav");
- // PMM - precache bolt2
- gi.modelindex("models/objects/laser/tris.md2");
- self->s.modelindex = gi.modelindex("models/monsters/stalker/tris.md2");
- gi.modelindex("models/monsters/stalker/gibs/bodya.md2");
- gi.modelindex("models/monsters/stalker/gibs/bodyb.md2");
- gi.modelindex("models/monsters/stalker/gibs/claw.md2");
- gi.modelindex("models/monsters/stalker/gibs/foot.md2");
- gi.modelindex("models/monsters/stalker/gibs/head.md2");
- gi.modelindex("models/monsters/stalker/gibs/leg.md2");
- self->mins = { -28, -28, -18 };
- self->maxs = { 28, 28, 18 };
- self->movetype = MOVETYPE_STEP;
- self->solid = SOLID_BBOX;
- self->health = 250 * st.health_multiplier;
- self->gib_health = -50;
- self->mass = 250;
- 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;
- self->monsterinfo.setskin = stalker_setskin;
- self->monsterinfo.physics_change = stalker_physics_change;
- gi.linkentity(self);
- M_SetAnimation(self, &stalker_move_stand);
- self->monsterinfo.scale = MODEL_SCALE;
- if (self->spawnflags.has(SPAWNFLAG_STALKER_ONROOF))
- {
- self->s.angles[2] = 180;
- self->gravityVector[2] = 1;
- }
- self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_STALKER_NOJUMPING);
- self->monsterinfo.drop_height = 256;
- self->monsterinfo.jump_height = 68;
- walkmonster_start(self);
- }
|