Files
quake2-rerelease-dll/rerelease/rogue/m_rogue_stalker.cpp
2023-10-03 14:43:06 -04:00

1042 lines
25 KiB
C++

// 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);
}