mirror of
https://github.com/id-Software/quake2-rerelease-dll.git
synced 2025-02-22 11:21:22 +00:00
1570 lines
40 KiB
C++
1570 lines
40 KiB
C++
// Copyright (c) ZeniMax Media Inc.
|
|
// Licensed under the GNU General Public License 2.0.
|
|
/*
|
|
==============================================================================
|
|
|
|
black widow, part 2
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
// timestamp used to prevent rapid fire of melee attack
|
|
|
|
#include "../g_local.h"
|
|
#include "m_rogue_widow2.h"
|
|
#include "../m_flash.h"
|
|
|
|
static cached_soundindex sound_pain1;
|
|
static cached_soundindex sound_pain2;
|
|
static cached_soundindex sound_pain3;
|
|
static cached_soundindex sound_death;
|
|
static cached_soundindex sound_search1;
|
|
static cached_soundindex sound_tentacles_retract;
|
|
|
|
// sqrt(64*64*2) + sqrt(28*28*2) => 130.1
|
|
constexpr vec3_t spawnpoints[] = {
|
|
{ 30, 135, 0 },
|
|
{ 30, -135, 0 }
|
|
};
|
|
|
|
constexpr 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
|
|
};
|
|
|
|
constexpr vec3_t stalker_mins = { -28, -28, -18 };
|
|
constexpr vec3_t stalker_maxs = { 28, 28, 18 };
|
|
|
|
bool 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_dead(edict_t *self);
|
|
void widow2_attack_beam(edict_t *self);
|
|
void widow2_reattack_beam(edict_t *self);
|
|
void widow_start_spawn(edict_t *self);
|
|
void widow_done_spawn(edict_t *self);
|
|
void widow2_spawn_check(edict_t *self);
|
|
void Widow2SaveBeamTarget(edict_t *self);
|
|
|
|
// death stuff
|
|
void WidowExplode(edict_t *self);
|
|
void ThrowWidowGibReal(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool large, int hitsound, bool fade);
|
|
void ThrowWidowGibSized(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, int hitsound, bool fade);
|
|
void ThrowWidowGibLoc(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool 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);
|
|
// end of death stuff
|
|
|
|
// these offsets used by the tongue
|
|
constexpr vec3_t offsets[] = {
|
|
{ 17.48f, 0.10f, 68.92f },
|
|
{ 17.47f, 0.29f, 68.91f },
|
|
{ 17.45f, 0.53f, 68.87f },
|
|
{ 17.42f, 0.78f, 68.81f },
|
|
{ 17.39f, 1.02f, 68.75f },
|
|
{ 17.37f, 1.20f, 68.70f },
|
|
{ 17.36f, 1.24f, 68.71f },
|
|
{ 17.37f, 1.21f, 68.72f },
|
|
};
|
|
|
|
void showme(edict_t *self);
|
|
|
|
void pauseme(edict_t *self)
|
|
{
|
|
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
|
|
}
|
|
|
|
MONSTERINFO_SEARCH(widow2_search) (edict_t *self) -> void
|
|
{
|
|
if (frandom() < 0.5f)
|
|
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;
|
|
monster_muzzleflash_id_t flashnum;
|
|
|
|
if ((!self->enemy) || (!self->enemy->inuse))
|
|
return;
|
|
|
|
AngleVectors(self->s.angles, forward, right, nullptr);
|
|
|
|
if ((self->s.frame >= FRAME_fireb05) && (self->s.frame <= FRAME_fireb09))
|
|
{
|
|
// regular beam attack
|
|
Widow2SaveBeamTarget(self);
|
|
flashnum = static_cast<monster_muzzleflash_id_t>(MZ2_WIDOW2_BEAMER_1 + self->s.frame - FRAME_fireb05);
|
|
start = G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, right);
|
|
target = self->pos2;
|
|
target[2] += self->enemy->viewheight - 10;
|
|
forward = target - start;
|
|
forward.normalize();
|
|
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 = static_cast<monster_muzzleflash_id_t>(MZ2_WIDOW2_BEAM_SWEEP_1 + self->s.frame - FRAME_spawn04);
|
|
start = G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, right);
|
|
target = self->enemy->s.origin - start;
|
|
targ_angles = vectoangles(target);
|
|
|
|
vec = self->s.angles;
|
|
|
|
vec[PITCH] += targ_angles[PITCH];
|
|
vec[YAW] -= sweep_angles[flashnum - MZ2_WIDOW2_BEAM_SWEEP_1];
|
|
|
|
AngleVectors(vec, forward, nullptr, nullptr);
|
|
monster_fire_heatbeam(self, start, forward, vec3_origin, 10, 50, flashnum);
|
|
}
|
|
else
|
|
{
|
|
Widow2SaveBeamTarget(self);
|
|
start = G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_WIDOW2_BEAMER_1], forward, right);
|
|
|
|
target = self->pos2;
|
|
target[2] += self->enemy->viewheight - 10;
|
|
|
|
forward = target - start;
|
|
forward.normalize();
|
|
|
|
monster_fire_heatbeam(self, start, forward, vec3_origin, 10, 50, MZ2_WIDOW2_BEAM_SWEEP_1);
|
|
}
|
|
}
|
|
|
|
void Widow2Spawn(edict_t *self)
|
|
{
|
|
vec3_t f, r, u, offset, startpoint, spawnpoint;
|
|
edict_t *ent, *designated_enemy;
|
|
int i;
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
offset = spawnpoints[i];
|
|
|
|
startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u);
|
|
|
|
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->integer)
|
|
{
|
|
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)
|
|
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)
|
|
{
|
|
Widow2Beam(self);
|
|
Widow2Spawn(self);
|
|
}
|
|
|
|
void widow2_ready_spawn(edict_t *self)
|
|
{
|
|
vec3_t f, r, u, offset, startpoint, spawnpoint;
|
|
int i;
|
|
|
|
Widow2Beam(self);
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
offset = spawnpoints[i];
|
|
startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u);
|
|
if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
|
|
{
|
|
float radius = (stalker_maxs - stalker_mins).length() * 0.5f;
|
|
|
|
SpawnGrow_Spawn(spawnpoint + (stalker_mins + stalker_maxs), radius, radius * 2.f);
|
|
}
|
|
}
|
|
}
|
|
|
|
void widow2_step(edict_t *self)
|
|
{
|
|
gi.sound(self, CHAN_BODY, gi.soundindex("widow/bwstep1.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
mframe_t widow2_frames_stand[] = {
|
|
{ ai_stand }
|
|
};
|
|
MMOVE_T(widow2_move_stand) = { FRAME_blackwidow3, FRAME_blackwidow3, widow2_frames_stand, nullptr };
|
|
|
|
mframe_t widow2_frames_walk[] = {
|
|
{ ai_walk, 9.01f, widow2_step },
|
|
{ ai_walk, 7.55f },
|
|
{ ai_walk, 7.01f },
|
|
{ ai_walk, 6.66f },
|
|
{ ai_walk, 6.20f },
|
|
{ ai_walk, 5.78f, widow2_step },
|
|
{ ai_walk, 7.25f },
|
|
{ ai_walk, 8.37f },
|
|
{ ai_walk, 10.41f }
|
|
};
|
|
MMOVE_T(widow2_move_walk) = { FRAME_walk01, FRAME_walk09, widow2_frames_walk, nullptr };
|
|
|
|
mframe_t widow2_frames_run[] = {
|
|
{ ai_run, 9.01f, widow2_step },
|
|
{ ai_run, 7.55f },
|
|
{ ai_run, 7.01f },
|
|
{ ai_run, 6.66f },
|
|
{ ai_run, 6.20f },
|
|
{ ai_run, 5.78f, widow2_step },
|
|
{ ai_run, 7.25f },
|
|
{ ai_run, 8.37f },
|
|
{ ai_run, 10.41f }
|
|
};
|
|
MMOVE_T(widow2_move_run) = { FRAME_walk01, FRAME_walk09, widow2_frames_run, nullptr };
|
|
|
|
mframe_t widow2_frames_attack_pre_beam[] = {
|
|
{ ai_charge, 4 },
|
|
{ ai_charge, 4, widow2_step },
|
|
{ ai_charge, 4 },
|
|
{ ai_charge, 4, widow2_attack_beam }
|
|
};
|
|
MMOVE_T(widow2_move_attack_pre_beam) = { FRAME_fireb01, FRAME_fireb04, widow2_frames_attack_pre_beam, nullptr };
|
|
|
|
// Loop this
|
|
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, nullptr };
|
|
|
|
mframe_t widow2_frames_attack_post_beam[] = {
|
|
{ ai_charge, 4 },
|
|
{ ai_charge, 4 }
|
|
};
|
|
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;
|
|
|
|
AngleVectors(self->s.angles, forward, right, nullptr);
|
|
start = G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_WIDOW_DISRUPTOR], forward, right);
|
|
|
|
dir = self->pos1 - self->enemy->s.origin;
|
|
len = dir.length();
|
|
|
|
if (len < 30)
|
|
{
|
|
// calc direction to where we targeted
|
|
dir = self->pos1 - start;
|
|
dir.normalize();
|
|
|
|
monster_fire_tracker(self, start, dir, 20, 500, self->enemy, MZ2_WIDOW_DISRUPTOR);
|
|
}
|
|
else
|
|
{
|
|
PredictAim(self, self->enemy, start, 1200, true, 0, &dir, nullptr);
|
|
monster_fire_tracker(self, start, dir, 20, 1200, nullptr, MZ2_WIDOW_DISRUPTOR);
|
|
}
|
|
|
|
widow2_step(self);
|
|
}
|
|
|
|
void Widow2SaveDisruptLoc(edict_t *self)
|
|
{
|
|
if (self->enemy && self->enemy->inuse)
|
|
{
|
|
self->pos1 = self->enemy->s.origin; // save for aiming the shot
|
|
self->pos1[2] += self->enemy->viewheight;
|
|
}
|
|
else
|
|
self->pos1 = {};
|
|
}
|
|
|
|
void widow2_disrupt_reattack(edict_t *self)
|
|
{
|
|
float luck = frandom();
|
|
|
|
if (luck < (0.25f + (skill->integer * 0.15f)))
|
|
self->monsterinfo.nextframe = FRAME_firea01;
|
|
}
|
|
|
|
mframe_t widow2_frames_attack_disrupt[] = {
|
|
{ ai_charge, 2 },
|
|
{ ai_charge, 2 },
|
|
{ ai_charge, 2, Widow2SaveDisruptLoc },
|
|
{ ai_charge, -20, WidowDisrupt },
|
|
{ ai_charge, 2 },
|
|
{ ai_charge, 2 },
|
|
{ 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->enemy && self->enemy->inuse)
|
|
{
|
|
self->pos2 = self->pos1;
|
|
self->pos1 = self->enemy->s.origin; // save for aiming the shot
|
|
}
|
|
else
|
|
{
|
|
self->pos1 = {};
|
|
self->pos2 = {};
|
|
}
|
|
}
|
|
|
|
void Widow2BeamTargetRemove(edict_t *self)
|
|
{
|
|
self->pos1 = {};
|
|
self->pos2 = {};
|
|
}
|
|
|
|
void Widow2StartSweep(edict_t *self)
|
|
{
|
|
Widow2SaveBeamTarget(self);
|
|
}
|
|
|
|
mframe_t widow2_frames_spawn[] = {
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, [](edict_t *self) { widow_start_spawn(self); widow2_step(self); } },
|
|
{ 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 }, // 15
|
|
{ ai_charge },
|
|
{ ai_charge },
|
|
{ ai_charge, 0, widow2_reattack_beam }
|
|
};
|
|
MMOVE_T(widow2_move_spawn) = { FRAME_spawn01, FRAME_spawn18, widow2_frames_spawn, nullptr };
|
|
|
|
static bool widow2_tongue_attack_ok(const vec3_t &start, const vec3_t &end, float range)
|
|
{
|
|
vec3_t dir, angles;
|
|
|
|
// check for max distance
|
|
dir = start - end;
|
|
if (dir.length() > range)
|
|
return false;
|
|
|
|
// check for min/max pitch
|
|
angles = vectoangles(dir);
|
|
if (angles[0] < -180)
|
|
angles[0] += 360;
|
|
if (fabsf(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;
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
start = G_ProjectSource2(self->s.origin, offsets[self->s.frame - FRAME_tongs01], f, r, u);
|
|
end = self->enemy->s.origin;
|
|
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;
|
|
}
|
|
}
|
|
end = self->enemy->s.origin;
|
|
|
|
tr = gi.traceline(start, end, self, MASK_PROJECTILE);
|
|
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.WriteEntity(self);
|
|
gi.WritePosition(start);
|
|
gi.WritePosition(end);
|
|
gi.multicast(self->s.origin, MULTICAST_PVS, false);
|
|
|
|
dir = start - end;
|
|
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->enemy) || (!self->enemy->inuse))
|
|
{
|
|
self->monsterinfo.run(self);
|
|
return;
|
|
}
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
start = G_ProjectSource2(self->s.origin, offsets[self->s.frame - FRAME_tongs01], f, r, u);
|
|
end = self->enemy->s.origin;
|
|
|
|
if (!widow2_tongue_attack_ok(start, end, 256))
|
|
return;
|
|
|
|
if (self->enemy->groundentity)
|
|
{
|
|
self->enemy->s.origin[2] += 1;
|
|
self->enemy->groundentity = nullptr;
|
|
// interesting, you don't have to relink the player
|
|
}
|
|
|
|
vec = self->s.origin - self->enemy->s.origin;
|
|
|
|
if (self->enemy->client)
|
|
{
|
|
vec.normalize();
|
|
self->enemy->velocity += (vec * 1000);
|
|
}
|
|
else
|
|
{
|
|
self->enemy->ideal_yaw = vectoyaw(vec);
|
|
M_ChangeYaw(self->enemy);
|
|
self->enemy->velocity = f * 1000;
|
|
}
|
|
}
|
|
|
|
void Widow2Crunch(edict_t *self)
|
|
{
|
|
vec3_t aim;
|
|
|
|
if ((!self->enemy) || (!self->enemy->inuse))
|
|
{
|
|
self->monsterinfo.run(self);
|
|
return;
|
|
}
|
|
|
|
Widow2TonguePull(self);
|
|
|
|
// 70 + 32
|
|
aim = { 150, 0, 4 };
|
|
if (self->s.frame != FRAME_tongs07)
|
|
fire_hit(self, aim, irandom(20, 26), 0);
|
|
else if (self->enemy->groundentity)
|
|
fire_hit(self, aim, irandom(20, 26), 500);
|
|
else // not as much kick if they're in the air .. makes it harder to land on her head
|
|
fire_hit(self, aim, irandom(20, 26), 250);
|
|
}
|
|
|
|
void Widow2Toss(edict_t *self)
|
|
{
|
|
self->timestamp = level.time + 3_sec;
|
|
}
|
|
|
|
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 };
|
|
|
|
mframe_t widow2_frames_pain[] = {
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move }
|
|
};
|
|
MMOVE_T(widow2_move_pain) = { FRAME_pain01, FRAME_pain05, widow2_frames_pain, widow2_run };
|
|
|
|
mframe_t widow2_frames_death[] = {
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, WidowExplosion1 }, // 3 boom
|
|
{ ai_move },
|
|
{ ai_move }, // 5
|
|
|
|
{ ai_move, 0, WidowExplosion2 }, // 6 boom
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move }, // 10
|
|
|
|
{ ai_move },
|
|
{ ai_move }, // 12
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move }, // 15
|
|
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, WidowExplosion3 }, // 18
|
|
{ ai_move }, // 19
|
|
{ ai_move }, // 20
|
|
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, WidowExplosion4 }, // 25
|
|
|
|
{ ai_move }, // 26
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, WidowExplosion5 },
|
|
{ ai_move, 0, WidowExplosionLeg }, // 30
|
|
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, WidowExplosion6 },
|
|
{ ai_move }, // 35
|
|
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, WidowExplosion7 },
|
|
{ ai_move },
|
|
{ ai_move }, // 40
|
|
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, WidowExplode } // 44
|
|
};
|
|
MMOVE_T(widow2_move_death) = { FRAME_death01, FRAME_death44, widow2_frames_death, nullptr };
|
|
|
|
void widow2_start_searching(edict_t *self);
|
|
void widow2_keep_searching(edict_t *self);
|
|
void widow2_finaldeath(edict_t *self);
|
|
|
|
mframe_t widow2_frames_dead[] = {
|
|
{ ai_move, 0, widow2_start_searching },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move, 0, widow2_keep_searching }
|
|
};
|
|
MMOVE_T(widow2_move_dead) = { FRAME_dthsrh01, FRAME_dthsrh15, widow2_frames_dead, nullptr };
|
|
|
|
mframe_t widow2_frames_really_dead[] = {
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
{ ai_move },
|
|
|
|
{ ai_move },
|
|
{ ai_move, 0, widow2_finaldeath }
|
|
};
|
|
MMOVE_T(widow2_move_really_dead) = { FRAME_dthsrh16, FRAME_dthsrh22, widow2_frames_really_dead, nullptr };
|
|
|
|
void widow2_start_searching(edict_t *self)
|
|
{
|
|
self->count = 0;
|
|
}
|
|
|
|
void widow2_keep_searching(edict_t *self)
|
|
{
|
|
if (self->count <= 2)
|
|
{
|
|
M_SetAnimation(self, &widow2_move_dead);
|
|
self->s.frame = FRAME_dthsrh01;
|
|
self->count++;
|
|
return;
|
|
}
|
|
|
|
M_SetAnimation(self, &widow2_move_really_dead);
|
|
}
|
|
|
|
void widow2_finaldeath(edict_t *self)
|
|
{
|
|
self->mins = { -70, -70, 0 };
|
|
self->maxs = { 70, 70, 80 };
|
|
self->movetype = MOVETYPE_TOSS;
|
|
self->takedamage = true;
|
|
self->nextthink = 0_ms;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
MONSTERINFO_STAND(widow2_stand) (edict_t *self) -> void
|
|
{
|
|
M_SetAnimation(self, &widow2_move_stand);
|
|
}
|
|
|
|
MONSTERINFO_RUN(widow2_run) (edict_t *self) -> void
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
|
|
|
|
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
|
|
M_SetAnimation(self, &widow2_move_stand);
|
|
else
|
|
M_SetAnimation(self, &widow2_move_run);
|
|
}
|
|
|
|
MONSTERINFO_WALK(widow2_walk) (edict_t *self) -> void
|
|
{
|
|
M_SetAnimation(self, &widow2_move_walk);
|
|
}
|
|
|
|
void widow2_attack(edict_t *self);
|
|
|
|
MONSTERINFO_MELEE(widow2_melee) (edict_t *self) -> void
|
|
{
|
|
if (self->timestamp >= level.time)
|
|
widow2_attack(self);
|
|
else
|
|
M_SetAnimation(self, &widow2_move_tongs);
|
|
}
|
|
|
|
MONSTERINFO_ATTACK(widow2_attack) (edict_t *self) -> void
|
|
{
|
|
float luck;
|
|
bool blocked = false;
|
|
|
|
if (self->monsterinfo.aiflags & AI_BLOCKED)
|
|
{
|
|
blocked = true;
|
|
self->monsterinfo.aiflags &= ~AI_BLOCKED;
|
|
}
|
|
|
|
if (!self->enemy)
|
|
return;
|
|
|
|
float real_enemy_range = realrange(self, self->enemy);
|
|
|
|
// melee attack
|
|
if (self->timestamp < level.time)
|
|
{
|
|
if (real_enemy_range < 300)
|
|
{
|
|
vec3_t f, r, u;
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
vec3_t spot1 = G_ProjectSource2(self->s.origin, offsets[0], f, r, u);
|
|
vec3_t spot2 = self->enemy->s.origin;
|
|
if (widow2_tongue_attack_ok(spot1, spot2, 256))
|
|
{
|
|
// melee attack ok
|
|
|
|
// be nice in easy mode
|
|
if (skill->integer != 0 || irandom(4))
|
|
{
|
|
M_SetAnimation(self, &widow2_move_tongs);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->bad_area)
|
|
{
|
|
if ((frandom() < 0.75f) || (level.time < self->monsterinfo.attack_finished))
|
|
M_SetAnimation(self, &widow2_move_attack_pre_beam);
|
|
else
|
|
{
|
|
M_SetAnimation(self, &widow2_move_attack_disrupt);
|
|
}
|
|
return;
|
|
}
|
|
|
|
WidowCalcSlots(self);
|
|
|
|
// if we can't see the target, spawn stuff
|
|
if ((self->monsterinfo.attack_state == AS_BLIND) && (M_SlotsLeft(self) >= 2))
|
|
{
|
|
M_SetAnimation(self, &widow2_move_spawn);
|
|
return;
|
|
}
|
|
|
|
// accept bias towards spawning
|
|
if (blocked && (M_SlotsLeft(self) >= 2))
|
|
{
|
|
M_SetAnimation(self, &widow2_move_spawn);
|
|
return;
|
|
}
|
|
|
|
if (real_enemy_range < 600)
|
|
{
|
|
luck = frandom();
|
|
if (M_SlotsLeft(self) >= 2)
|
|
{
|
|
if (luck <= 0.40f)
|
|
M_SetAnimation(self, &widow2_move_attack_pre_beam);
|
|
else if ((luck <= 0.7f) && !(level.time < self->monsterinfo.attack_finished))
|
|
{
|
|
M_SetAnimation(self, &widow2_move_attack_disrupt);
|
|
}
|
|
else
|
|
M_SetAnimation(self, &widow2_move_spawn);
|
|
}
|
|
else
|
|
{
|
|
if ((luck <= 0.50f) || (level.time < self->monsterinfo.attack_finished))
|
|
M_SetAnimation(self, &widow2_move_attack_pre_beam);
|
|
else
|
|
{
|
|
M_SetAnimation(self, &widow2_move_attack_disrupt);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
luck = frandom();
|
|
if (M_SlotsLeft(self) >= 2)
|
|
{
|
|
if (luck < 0.3f)
|
|
M_SetAnimation(self, &widow2_move_attack_pre_beam);
|
|
else if ((luck < 0.65f) || (level.time < self->monsterinfo.attack_finished))
|
|
M_SetAnimation(self, &widow2_move_spawn);
|
|
else
|
|
{
|
|
M_SetAnimation(self, &widow2_move_attack_disrupt);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((luck < 0.45f) || (level.time < self->monsterinfo.attack_finished))
|
|
M_SetAnimation(self, &widow2_move_attack_pre_beam);
|
|
else
|
|
{
|
|
M_SetAnimation(self, &widow2_move_attack_disrupt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void widow2_attack_beam(edict_t *self)
|
|
{
|
|
M_SetAnimation(self, &widow2_move_attack_beam);
|
|
widow2_step(self);
|
|
}
|
|
|
|
void widow2_reattack_beam(edict_t *self)
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
|
|
|
|
if (infront(self, self->enemy))
|
|
if (frandom() <= 0.5f)
|
|
if ((frandom() < 0.7f) || (M_SlotsLeft(self) < 2))
|
|
M_SetAnimation(self, &widow2_move_attack_beam);
|
|
else
|
|
M_SetAnimation(self, &widow2_move_spawn);
|
|
else
|
|
M_SetAnimation(self, &widow2_move_attack_post_beam);
|
|
else
|
|
M_SetAnimation(self, &widow2_move_attack_post_beam);
|
|
}
|
|
|
|
PAIN(widow2_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
|
|
{
|
|
if (level.time < self->pain_debounce_time)
|
|
return;
|
|
|
|
self->pain_debounce_time = level.time + 5_sec;
|
|
|
|
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);
|
|
else
|
|
gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
|
|
|
|
if (!M_ShouldReactToPain(self, mod))
|
|
return; // no pain anims in nightmare
|
|
|
|
if (damage >= 15)
|
|
{
|
|
if (damage < 75)
|
|
{
|
|
if ((skill->integer < 3) && (frandom() < (0.6f - (0.2f * skill->integer))))
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
|
|
M_SetAnimation(self, &widow2_move_pain);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((skill->integer < 3) && (frandom() < (0.75f - (0.1f * skill->integer))))
|
|
{
|
|
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
|
|
M_SetAnimation(self, &widow2_move_pain);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MONSTERINFO_SETSKIN(widow2_setskin) (edict_t *self) -> void
|
|
{
|
|
if (self->health < (self->max_health / 2))
|
|
self->s.skinnum = 1;
|
|
else
|
|
self->s.skinnum = 0;
|
|
}
|
|
|
|
void widow2_dead(edict_t *self)
|
|
{
|
|
}
|
|
|
|
void KillChildren(edict_t *self)
|
|
{
|
|
edict_t *ent = nullptr;
|
|
|
|
while (1)
|
|
{
|
|
ent = G_FindByString<&edict_t::classname>(ent, "monster_stalker");
|
|
if (!ent)
|
|
return;
|
|
|
|
// FIXME - may need to stagger
|
|
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);
|
|
}
|
|
}
|
|
|
|
DIE(widow2_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
|
|
{
|
|
int n;
|
|
int clipped;
|
|
|
|
// check for gib
|
|
if (self->deadflag && M_CheckGib(self, mod))
|
|
{
|
|
clipped = 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_NONE, nullptr, false);
|
|
for (n = 0; n < 3; n++)
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", clipped, GIB_NONE, nullptr, false);
|
|
for (n = 0; n < 3; n++)
|
|
{
|
|
ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib1/tris.md2", clipped, GIB_METALLIC, nullptr,
|
|
0, false);
|
|
ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib2/tris.md2", clipped, GIB_METALLIC, nullptr,
|
|
gi.soundindex("misc/fhit3.wav"), false);
|
|
}
|
|
for (n = 0; n < 2; n++)
|
|
{
|
|
ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib3/tris.md2", clipped, GIB_METALLIC, nullptr,
|
|
0, false);
|
|
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2", clipped, GIB_METALLIC, nullptr,
|
|
0, false);
|
|
}
|
|
ThrowGibs(self, damage, {
|
|
{ "models/objects/gibs/chest/tris.md2" },
|
|
{ "models/objects/gibs/head2/tris.md2", GIB_HEAD }
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
if (self->deadflag)
|
|
return;
|
|
|
|
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
|
|
self->deadflag = true;
|
|
self->takedamage = false;
|
|
self->count = 0;
|
|
KillChildren(self);
|
|
self->monsterinfo.quad_time = 0_ms;
|
|
self->monsterinfo.double_time = 0_ms;
|
|
self->monsterinfo.invincible_time = 0_ms;
|
|
M_SetAnimation(self, &widow2_move_death);
|
|
}
|
|
|
|
MONSTERINFO_CHECKATTACK(Widow2_CheckAttack) (edict_t *self) -> bool
|
|
{
|
|
if (!self->enemy)
|
|
return false;
|
|
|
|
WidowPowerups(self);
|
|
|
|
if ((frandom() < 0.8f) && (M_SlotsLeft(self) >= 2) && (realrange(self, self->enemy) > 150))
|
|
{
|
|
self->monsterinfo.aiflags |= AI_BLOCKED;
|
|
self->monsterinfo.attack_state = AS_MISSILE;
|
|
return true;
|
|
}
|
|
|
|
return M_CheckAttack_Base(self, 0.4f, 0.8f, 0.8f, 0.5f, 0.f, 0.f);
|
|
}
|
|
|
|
void Widow2Precache()
|
|
{
|
|
// 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/spawngro3/tris.md2");
|
|
gi.modelindex("models/objects/gibs/sm_metal/tris.md2");
|
|
gi.modelindex("models/objects/laser/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 ( !M_AllowSpawn( self ) ) {
|
|
G_FreeEdict( self );
|
|
return;
|
|
}
|
|
|
|
sound_pain1.assign("widow/bw2pain1.wav");
|
|
sound_pain2.assign("widow/bw2pain2.wav");
|
|
sound_pain3.assign("widow/bw2pain3.wav");
|
|
sound_death.assign("widow/death.wav");
|
|
sound_search1.assign("bosshovr/bhvunqv1.wav");
|
|
sound_tentacles_retract.assign("brain/brnatck3.wav");
|
|
|
|
// self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");
|
|
|
|
self->movetype = MOVETYPE_STEP;
|
|
self->solid = SOLID_BBOX;
|
|
self->s.modelindex = gi.modelindex("models/monsters/blackwidow2/tris.md2");
|
|
self->mins = { -70, -70, 0 };
|
|
self->maxs = { 70, 70, 144 };
|
|
|
|
self->health = (2000 + 800 + 1000 * skill->integer) * st.health_multiplier;
|
|
if (coop->integer)
|
|
self->health += 500 * skill->integer;
|
|
// self->health = 1;
|
|
self->gib_health = -900;
|
|
self->mass = 2500;
|
|
|
|
/* if (skill->integer == 2)
|
|
{
|
|
self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD;
|
|
self->monsterinfo.power_armor_power = 500;
|
|
}
|
|
else */
|
|
if (skill->integer == 3)
|
|
{
|
|
if (!st.was_key_specified("power_armor_type"))
|
|
self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD;
|
|
if (!st.was_key_specified("power_armor_power"))
|
|
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;
|
|
self->monsterinfo.setskin = widow2_setskin;
|
|
gi.linkentity(self);
|
|
|
|
M_SetAnimation(self, &widow2_move_stand);
|
|
self->monsterinfo.scale = MODEL_SCALE;
|
|
|
|
Widow2Precache();
|
|
WidowCalcSlots(self);
|
|
walkmonster_start(self);
|
|
}
|
|
|
|
//
|
|
// Death sequence stuff
|
|
//
|
|
|
|
void WidowVelocityForDamage(int damage, vec3_t &v)
|
|
{
|
|
v[0] = damage * crandom();
|
|
v[1] = damage * crandom();
|
|
v[2] = damage * crandom() + 200.0f;
|
|
}
|
|
|
|
TOUCH(widow_gib_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
|
|
{
|
|
self->solid = SOLID_NOT;
|
|
self->touch = nullptr;
|
|
self->s.angles[PITCH] = 0;
|
|
self->s.angles[ROLL] = 0;
|
|
self->avelocity = {};
|
|
|
|
if (self->style)
|
|
gi.sound(self, CHAN_VOICE, self->style, 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
void ThrowWidowGib(edict_t *self, const char *gibname, int damage, gib_type_t type)
|
|
{
|
|
ThrowWidowGibReal(self, gibname, damage, type, nullptr, false, 0, true);
|
|
}
|
|
|
|
void ThrowWidowGibLoc(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool fade)
|
|
{
|
|
ThrowWidowGibReal(self, gibname, damage, type, startpos, false, 0, fade);
|
|
}
|
|
|
|
void ThrowWidowGibSized(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, int hitsound, bool fade)
|
|
{
|
|
ThrowWidowGibReal(self, gibname, damage, type, startpos, true, hitsound, fade);
|
|
}
|
|
|
|
void ThrowWidowGibReal(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool sized, int hitsound, bool fade)
|
|
{
|
|
edict_t *gib;
|
|
vec3_t vd;
|
|
vec3_t origin;
|
|
vec3_t size;
|
|
float vscale;
|
|
|
|
if (!gibname)
|
|
return;
|
|
|
|
gib = G_Spawn();
|
|
|
|
if (startpos)
|
|
gib->s.origin = *startpos;
|
|
else
|
|
{
|
|
origin = (self->absmin + self->absmax) * 0.5f;
|
|
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 = true;
|
|
gib->die = gib_die;
|
|
gib->s.renderfx |= RF_IR_VISIBLE;
|
|
gib->s.renderfx &= ~RF_DOT_SHADOW;
|
|
|
|
if (fade)
|
|
{
|
|
gib->think = G_FreeEdict;
|
|
// sized gibs last longer
|
|
if (sized)
|
|
gib->nextthink = level.time + random_time(20_sec, 35_sec);
|
|
else
|
|
gib->nextthink = level.time + random_time(5_sec, 15_sec);
|
|
}
|
|
else
|
|
{
|
|
gib->think = G_FreeEdict;
|
|
// sized gibs last longer
|
|
if (sized)
|
|
gib->nextthink = level.time + random_time(60_sec, 75_sec);
|
|
else
|
|
gib->nextthink = level.time + random_time(25_sec, 35_sec);
|
|
}
|
|
|
|
if (!(type & GIB_METALLIC))
|
|
{
|
|
gib->movetype = MOVETYPE_TOSS;
|
|
vscale = 0.5;
|
|
}
|
|
else
|
|
{
|
|
gib->movetype = MOVETYPE_BOUNCE;
|
|
vscale = 1.0;
|
|
}
|
|
|
|
WidowVelocityForDamage(damage, vd);
|
|
gib->velocity = self->velocity + (vd * vscale);
|
|
ClipGibVelocity(gib);
|
|
|
|
gi.setmodel(gib, gibname);
|
|
|
|
if (sized)
|
|
{
|
|
gib->style = hitsound;
|
|
gib->solid = SOLID_BBOX;
|
|
gib->avelocity[0] = frandom(400);
|
|
gib->avelocity[1] = frandom(400);
|
|
gib->avelocity[2] = frandom(400);
|
|
if (gib->velocity[2] < 0)
|
|
gib->velocity[2] *= -1;
|
|
gib->velocity[0] *= 2;
|
|
gib->velocity[1] *= 2;
|
|
ClipGibVelocity(gib);
|
|
gib->velocity[2] = max(frandom(350.f, 450.f), 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"))
|
|
{
|
|
gib->mins = { -10, -10, 0 };
|
|
gib->maxs = { 10, 10, 10 };
|
|
}
|
|
else
|
|
{
|
|
gib->mins = { -5, -5, 0 };
|
|
gib->maxs = { 5, 5, 5 };
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gib->velocity[0] *= 2;
|
|
gib->velocity[1] *= 2;
|
|
gib->avelocity[0] = frandom(600);
|
|
gib->avelocity[1] = frandom(600);
|
|
gib->avelocity[2] = frandom(600);
|
|
}
|
|
|
|
gi.linkentity(gib);
|
|
}
|
|
|
|
void ThrowSmallStuff(edict_t *self, const vec3_t &point)
|
|
{
|
|
int n;
|
|
|
|
for (n = 0; n < 2; n++)
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &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);
|
|
}
|
|
|
|
void ThrowMoreStuff(edict_t *self, const vec3_t &point)
|
|
{
|
|
int n;
|
|
|
|
if (coop->integer)
|
|
{
|
|
ThrowSmallStuff(self, point);
|
|
return;
|
|
}
|
|
|
|
for (n = 0; n < 1; n++)
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &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);
|
|
}
|
|
|
|
THINK(WidowExplode) (edict_t *self) -> void
|
|
{
|
|
vec3_t org;
|
|
int n;
|
|
|
|
self->think = WidowExplode;
|
|
|
|
// redo:
|
|
org = self->s.origin;
|
|
org[2] += irandom(24, 40);
|
|
if (self->count < 8)
|
|
org[2] += irandom(24, 56);
|
|
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_NONE);
|
|
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 = true;
|
|
self->think = monster_think;
|
|
self->nextthink = level.time + 10_hz;
|
|
M_SetAnimation(self, &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, false);
|
|
// goto redo;
|
|
}
|
|
else
|
|
{
|
|
// 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, false);
|
|
}
|
|
|
|
self->nextthink = level.time + 10_hz;
|
|
}
|
|
|
|
void WidowExplosion1(edict_t *self)
|
|
{
|
|
int n;
|
|
vec3_t f, r, u, startpoint;
|
|
vec3_t offset = { 23.74f, -37.67f, 76.96f };
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_EXPLOSION1);
|
|
gi.WritePosition(startpoint);
|
|
gi.multicast(self->s.origin, MULTICAST_ALL, false);
|
|
|
|
for (n = 0; n < 1; n++)
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &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.49f, 36.92f, 73.52f };
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_EXPLOSION1);
|
|
gi.WritePosition(startpoint);
|
|
gi.multicast(self->s.origin, MULTICAST_ALL, false);
|
|
|
|
for (n = 0; n < 1; n++)
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &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.11f, 0.05f, 92.20f };
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_EXPLOSION1);
|
|
gi.WritePosition(startpoint);
|
|
gi.multicast(self->s.origin, MULTICAST_ALL, false);
|
|
|
|
for (n = 0; n < 1; n++)
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &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.04f, -35.57f, -77.56f };
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_EXPLOSION1);
|
|
gi.WritePosition(startpoint);
|
|
gi.multicast(self->s.origin, MULTICAST_ALL, false);
|
|
|
|
for (n = 0; n < 1; n++)
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &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.11f, -1.11f, 40.76f };
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_EXPLOSION1);
|
|
gi.WritePosition(startpoint);
|
|
gi.multicast(self->s.origin, MULTICAST_ALL, false);
|
|
|
|
for (n = 0; n < 1; n++)
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &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.11f, -1.11f, 40.76f };
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_EXPLOSION1);
|
|
gi.WritePosition(startpoint);
|
|
gi.multicast(self->s.origin, MULTICAST_ALL, false);
|
|
|
|
for (n = 0; n < 1; n++)
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &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.11f, -1.11f, 40.76f };
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_EXPLOSION1);
|
|
gi.WritePosition(startpoint);
|
|
gi.multicast(self->s.origin, MULTICAST_ALL, false);
|
|
|
|
for (n = 0; n < 1; n++)
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_NONE, &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.89f, -47.86f, 67.02f };
|
|
vec3_t offset2 = { -44.9f, -82.14f, 54.72f };
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
startpoint = G_ProjectSource2(self->s.origin, offset1, f, r, u);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_EXPLOSION1_BIG);
|
|
gi.WritePosition(startpoint);
|
|
gi.multicast(self->s.origin, MULTICAST_ALL, false);
|
|
|
|
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_NONE, &startpoint, false);
|
|
ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, &startpoint, false);
|
|
|
|
startpoint = G_ProjectSource2(self->s.origin, offset2, f, r, u);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_EXPLOSION1);
|
|
gi.WritePosition(startpoint);
|
|
gi.multicast(self->s.origin, MULTICAST_ALL, false);
|
|
|
|
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_NONE, &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.76f, 17.52f, 7.56f };
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
startpoint = G_ProjectSource2(self->s.origin, offset1, f, r, u);
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_EXPLOSION1_BIG);
|
|
gi.WritePosition(startpoint);
|
|
gi.multicast(self->s.origin, MULTICAST_ALL, false);
|
|
|
|
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.76f, 17.52f, 7.56f };
|
|
|
|
AngleVectors(self->s.angles, f, r, u);
|
|
startpoint = G_ProjectSource2(self->s.origin, offset1, f, r, u);
|
|
|
|
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_NONE, &startpoint, false);
|
|
}
|