123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599 |
- // Copyright (c) ZeniMax Media Inc.
- // Licensed under the GNU General Public License 2.0.
- /*
- ==============================================================================
- SHAMBLER
- ==============================================================================
- */
- #include "g_local.h"
- #include "m_shambler.h"
- #include "m_flash.h"
- static cached_soundindex sound_pain;
- static cached_soundindex sound_idle;
- static cached_soundindex sound_die;
- static cached_soundindex sound_sight;
- static cached_soundindex sound_windup;
- static cached_soundindex sound_melee1;
- static cached_soundindex sound_melee2;
- static cached_soundindex sound_smack;
- static cached_soundindex sound_boom;
- //
- // misc
- //
- MONSTERINFO_SIGHT(shambler_sight) (edict_t* self, edict_t* other) -> void
- {
- gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
- }
- constexpr vec3_t lightning_left_hand[] = {
- { 44, 36, 25 },
- { 10, 44, 57 },
- { -1, 40, 70 },
- { -10, 34, 75 },
- { 7.4f, 24, 89 }
- };
- constexpr vec3_t lightning_right_hand[] = {
- { 28, -38, 25 },
- { 31, -7, 70 },
- { 20, 0, 80 },
- { 16, 1.2f, 81 },
- { 27, -11, 83 }
- };
- static void shambler_lightning_update(edict_t *self)
- {
- edict_t *lightning = self->beam;
- if (self->s.frame >= FRAME_magic01 + q_countof(lightning_left_hand))
- {
- G_FreeEdict(lightning);
- self->beam = nullptr;
- return;
- }
- vec3_t f, r;
- AngleVectors(self->s.angles, f, r, nullptr);
- lightning->s.origin = M_ProjectFlashSource(self, lightning_left_hand[self->s.frame - FRAME_magic01], f, r);
- lightning->s.old_origin = M_ProjectFlashSource(self, lightning_right_hand[self->s.frame - FRAME_magic01], f, r);
- gi.linkentity(lightning);
- }
- void shambler_windup(edict_t* self)
- {
- gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0);
- edict_t *lightning = self->beam = G_Spawn();
- lightning->s.modelindex = gi.modelindex("models/proj/lightning/tris.md2");
- lightning->s.renderfx |= RF_BEAM;
- lightning->owner = self;
- shambler_lightning_update(self);
- }
- MONSTERINFO_IDLE(shambler_idle) (edict_t* self) -> void
- {
- gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
- }
- void shambler_maybe_idle(edict_t* self)
- {
- if (frandom() > 0.8)
- gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
- }
- //
- // stand
- //
- mframe_t shambler_frames_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 },
- { ai_stand },
- { ai_stand },
- { ai_stand },
- { ai_stand }
- };
- MMOVE_T(shambler_move_stand) = { FRAME_stand01, FRAME_stand17, shambler_frames_stand, nullptr };
- MONSTERINFO_STAND(shambler_stand) (edict_t* self) -> void
- {
- M_SetAnimation(self, &shambler_move_stand);
- }
- //
- // walk
- //
- void shambler_walk(edict_t* self);
- mframe_t shambler_frames_walk[] = {
- { ai_walk, 10 }, // FIXME: add footsteps?
- { ai_walk, 9 },
- { ai_walk, 9 },
- { ai_walk, 5 },
- { ai_walk, 6 },
- { ai_walk, 12 },
- { ai_walk, 8 },
- { ai_walk, 3 },
- { ai_walk, 13 },
- { ai_walk, 9 },
- { ai_walk, 7, shambler_maybe_idle },
- { ai_walk, 5 },
- };
- MMOVE_T(shambler_move_walk) = { FRAME_walk01, FRAME_walk12, shambler_frames_walk, nullptr };
- MONSTERINFO_WALK(shambler_walk) (edict_t* self) -> void
- {
- M_SetAnimation(self, &shambler_move_walk);
- }
- //
- // run
- //
- void shambler_run(edict_t* self);
- mframe_t shambler_frames_run[] = {
- { ai_run, 20 }, // FIXME: add footsteps?
- { ai_run, 24 },
- { ai_run, 20 },
- { ai_run, 20 },
- { ai_run, 24 },
- { ai_run, 20, shambler_maybe_idle },
- };
- MMOVE_T(shambler_move_run) = { FRAME_run01, FRAME_run06, shambler_frames_run, nullptr };
- MONSTERINFO_RUN(shambler_run) (edict_t* self) -> void
- {
- if (self->enemy && self->enemy->client)
- self->monsterinfo.aiflags |= AI_BRUTAL;
- else
- self->monsterinfo.aiflags &= ~AI_BRUTAL;
- if (self->monsterinfo.aiflags & AI_STAND_GROUND)
- {
- M_SetAnimation(self, &shambler_move_stand);
- return;
- }
- M_SetAnimation(self, &shambler_move_run);
- }
- //
- // pain
- //
- // FIXME: needs halved explosion damage
- mframe_t shambler_frames_pain[] = {
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- { ai_move },
- };
- MMOVE_T(shambler_move_pain) = { FRAME_pain01, FRAME_pain06, shambler_frames_pain, shambler_run };
- PAIN(shambler_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t &mod) -> void
- {
- if (level.time < self->timestamp)
- return;
- self->timestamp = level.time + 1_ms;
- gi.sound(self, CHAN_AUTO, sound_pain, 1, ATTN_NORM, 0);
- if (mod.id != MOD_CHAINFIST && damage <= 30 && frandom() > 0.2f)
- return;
- // If hard or nightmare, don't go into pain while attacking
- if (skill->integer >= 2)
- {
- if ((self->s.frame >= FRAME_smash01) && (self->s.frame <= FRAME_smash12))
- return;
- if ((self->s.frame >= FRAME_swingl01) && (self->s.frame <= FRAME_swingl09))
- return;
- if ((self->s.frame >= FRAME_swingr01) && (self->s.frame <= FRAME_swingr09))
- return;
- }
-
- if (!M_ShouldReactToPain(self, mod))
- return; // no pain anims in nightmare
- if (level.time < self->pain_debounce_time)
- return;
- self->pain_debounce_time = level.time + 2_sec;
- M_SetAnimation(self, &shambler_move_pain);
- }
- MONSTERINFO_SETSKIN(shambler_setskin) (edict_t* self) -> void
- {
- // FIXME: create pain skin?
- //if (self->health < (self->max_health / 2))
- // self->s.skinnum |= 1;
- //else
- // self->s.skinnum &= ~1;
- }
- //
- // attacks
- //
- /*
- void() sham_magic3 =[ $magic3, sham_magic4 ] {
- ai_face();
- self.nextthink = self.nextthink + 0.2;
- local entity o;
- self.effects = self.effects | EF_MUZZLEFLASH;
- ai_face();
- self.owner = spawn();
- o = self.owner;
- setmodel (o, "progs/s_light.mdl");
- setorigin (o, self.origin);
- o.angles = self.angles;
- o.nextthink = time + 0.7;
- o.think = SUB_Remove;
- };
- */
- void ShamblerSaveLoc(edict_t* self)
- {
- self->pos1 = self->enemy->s.origin; // save for aiming the shot
- self->pos1[2] += self->enemy->viewheight;
- self->monsterinfo.nextframe = FRAME_magic09;
- gi.sound(self, CHAN_WEAPON, sound_boom, 1, ATTN_NORM, 0);
- shambler_lightning_update(self);
- }
- constexpr spawnflags_t SPAWNFLAG_SHAMBLER_PRECISE = 1_spawnflag;
- vec3_t FindShamblerOffset(edict_t *self)
- {
- vec3_t offset = { 0, 0, 48.f };
- for (int i = 0; i < 8; i++)
- {
- if (M_CheckClearShot(self, offset))
- return offset;
- offset.z -= 4.f;
- }
- return { 0, 0, 48.f };
- }
- void ShamblerCastLightning(edict_t* self)
- {
- if (!self->enemy)
- return;
- vec3_t start;
- vec3_t dir;
- vec3_t forward, right;
- vec3_t offset = FindShamblerOffset(self);
- AngleVectors(self->s.angles, forward, right, nullptr);
- start = M_ProjectFlashSource(self, offset, forward, right);
- // calc direction to where we targted
- PredictAim(self, self->enemy, start, 0, false, self->spawnflags.has(SPAWNFLAG_SHAMBLER_PRECISE) ? 0.f : 0.1f, &dir, nullptr);
- vec3_t end = start + (dir * 8192);
- trace_t tr = gi.traceline(start, end, self, MASK_PROJECTILE | CONTENTS_SLIME | CONTENTS_LAVA);
- gi.WriteByte(svc_temp_entity);
- gi.WriteByte(TE_LIGHTNING);
- gi.WriteEntity(self); // source entity
- gi.WriteEntity(world); // destination entity
- gi.WritePosition(start);
- gi.WritePosition(tr.endpos);
- gi.multicast(start, MULTICAST_PVS, false);
- fire_bullet(self, start, dir, irandom(8, 12), 15, 0, 0, MOD_TESLA);
- }
- mframe_t shambler_frames_magic[] = {
- { ai_charge, 0, shambler_windup },
- { ai_charge, 0, shambler_lightning_update },
- { ai_charge, 0, shambler_lightning_update },
- { ai_move, 0, shambler_lightning_update },
- { ai_move, 0, shambler_lightning_update },
- { ai_move, 0, ShamblerSaveLoc},
- { ai_move },
- { ai_charge },
- { ai_move, 0, ShamblerCastLightning },
- { ai_move, 0, ShamblerCastLightning },
- { ai_move, 0, ShamblerCastLightning },
- { ai_move },
- };
- MMOVE_T(shambler_attack_magic) = { FRAME_magic01, FRAME_magic12, shambler_frames_magic, shambler_run };
- MONSTERINFO_ATTACK(shambler_attack) (edict_t* self) -> void
- {
- M_SetAnimation(self, &shambler_attack_magic);
- }
- //
- // melee
- //
- void shambler_melee1(edict_t* self)
- {
- gi.sound(self, CHAN_WEAPON, sound_melee1, 1, ATTN_NORM, 0);
- }
- void shambler_melee2(edict_t* self)
- {
- gi.sound(self, CHAN_WEAPON, sound_melee2, 1, ATTN_NORM, 0);
- }
- void sham_swingl9(edict_t* self);
- void sham_swingr9(edict_t* self);
- void sham_smash10(edict_t* self)
- {
- if (!self->enemy)
- return;
- ai_charge(self, 0);
- if (!CanDamage(self->enemy, self))
- return;
- vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
- bool hit = fire_hit(self, aim, irandom(110, 120), 120); // Slower attack
- if (hit)
- gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0);
- // SpawnMeatSpray(self.origin + v_forward * 16, crandom() * 100 * v_right);
- // SpawnMeatSpray(self.origin + v_forward * 16, crandom() * 100 * v_right);
- };
- void ShamClaw(edict_t* self)
- {
- if (!self->enemy)
- return;
- ai_charge(self, 10);
- if (!CanDamage(self->enemy, self))
- return;
- vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
- bool hit = fire_hit(self, aim, irandom(70, 80), 80); // Slower attack
- if (hit)
- gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0);
-
- // 250 if left, -250 if right
- /*
- if (side)
- {
- makevectorsfixed(self.angles);
- SpawnMeatSpray(self.origin + v_forward * 16, side * v_right);
- }
- */
- };
- mframe_t shambler_frames_smash[] = {
- { ai_charge, 2, shambler_melee1 },
- { ai_charge, 6 },
- { ai_charge, 6 },
- { ai_charge, 5 },
- { ai_charge, 4 },
- { ai_charge, 1 },
- { ai_charge, 0 },
- { ai_charge, 0 },
- { ai_charge, 0 },
- { ai_charge, 0, sham_smash10 },
- { ai_charge, 5 },
- { ai_charge, 4 },
- };
- MMOVE_T(shambler_attack_smash) = { FRAME_smash01, FRAME_smash12, shambler_frames_smash, shambler_run };
- mframe_t shambler_frames_swingl[] = {
- { ai_charge, 5, shambler_melee1 },
- { ai_charge, 3 },
- { ai_charge, 7 },
- { ai_charge, 3 },
- { ai_charge, 7 },
- { ai_charge, 9 },
- { ai_charge, 5, ShamClaw },
- { ai_charge, 4 },
- { ai_charge, 8, sham_swingl9 },
- };
- MMOVE_T(shambler_attack_swingl) = { FRAME_swingl01, FRAME_swingl09, shambler_frames_swingl, shambler_run };
- mframe_t shambler_frames_swingr[] = {
- { ai_charge, 1, shambler_melee2 },
- { ai_charge, 8 },
- { ai_charge, 14 },
- { ai_charge, 7 },
- { ai_charge, 3 },
- { ai_charge, 6 },
- { ai_charge, 6, ShamClaw },
- { ai_charge, 3 },
- { ai_charge, 8, sham_swingr9 },
- };
- MMOVE_T(shambler_attack_swingr) = { FRAME_swingr01, FRAME_swingr09, shambler_frames_swingr, shambler_run };
- void sham_swingl9(edict_t* self)
- {
- ai_charge(self, 8);
- if (brandom() && self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE)
- M_SetAnimation(self, &shambler_attack_swingr);
- }
- void sham_swingr9(edict_t* self)
- {
- ai_charge(self, 1);
- ai_charge(self, 10);
- if (brandom() && self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE)
- M_SetAnimation(self, &shambler_attack_swingl);
- }
- MONSTERINFO_MELEE(shambler_melee) (edict_t* self) -> void
- {
- float chance = frandom();
- if (chance > 0.6 || self->health == 600)
- M_SetAnimation(self, &shambler_attack_smash);
- else if (chance > 0.3)
- M_SetAnimation(self, &shambler_attack_swingl);
- else
- M_SetAnimation(self, &shambler_attack_swingr);
- }
- //
- // death
- //
- void shambler_dead(edict_t* self)
- {
- self->mins = { -16, -16, -24 };
- self->maxs = { 16, 16, -0 };
- monster_dead(self);
- }
- static void shambler_shrink(edict_t* self)
- {
- self->maxs[2] = 0;
- self->svflags |= SVF_DEADMONSTER;
- gi.linkentity(self);
- }
- mframe_t shambler_frames_death[] = {
- { ai_move, 0 },
- { ai_move, 0 },
- { ai_move, 0, shambler_shrink },
- { ai_move, 0 },
- { ai_move, 0 },
- { ai_move, 0 },
- { ai_move, 0 },
- { ai_move, 0 },
- { ai_move, 0 },
- { ai_move, 0 },
- { ai_move, 0 }, // FIXME: thud?
- };
- MMOVE_T(shambler_move_death) = { FRAME_death01, FRAME_death11, shambler_frames_death, shambler_dead };
- DIE(shambler_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t &mod) -> void
- {
- if (self->beam)
- {
- G_FreeEdict(self->beam);
- self->beam = nullptr;
- }
- if (self->beam2)
- {
- G_FreeEdict(self->beam2);
- self->beam2 = nullptr;
- }
- // check for gib
- if (M_CheckGib(self, mod))
- {
- gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
- // FIXME: better gibs for shambler, shambler head
- ThrowGibs(self, damage, {
- { "models/objects/gibs/sm_meat/tris.md2" },
- { "models/objects/gibs/chest/tris.md2" },
- { "models/objects/gibs/head2/tris.md2", 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, &shambler_move_death);
- }
- void SP_monster_shambler(edict_t* self)
- {
- if ( !M_AllowSpawn( self ) ) {
- G_FreeEdict( self );
- return;
- }
- self->s.modelindex = gi.modelindex("models/monsters/shambler/tris.md2");
- self->mins = { -32, -32, -24 };
- self->maxs = { 32, 32, 64 };
- self->movetype = MOVETYPE_STEP;
- self->solid = SOLID_BBOX;
- gi.modelindex("models/proj/lightning/tris.md2");
- sound_pain.assign("shambler/shurt2.wav");
- sound_idle.assign("shambler/sidle.wav");
- sound_die.assign("shambler/sdeath.wav");
- sound_windup.assign("shambler/sattck1.wav");
- sound_melee1.assign("shambler/melee1.wav");
- sound_melee2.assign("shambler/melee2.wav");
- sound_sight.assign("shambler/ssight.wav");
- sound_smack.assign("shambler/smack.wav");
- sound_boom.assign("shambler/sboom.wav");
- self->health = 600 * st.health_multiplier;
- self->gib_health = -60;
- self->mass = 500;
- self->pain = shambler_pain;
- self->die = shambler_die;
- self->monsterinfo.stand = shambler_stand;
- self->monsterinfo.walk = shambler_walk;
- self->monsterinfo.run = shambler_run;
- self->monsterinfo.dodge = nullptr;
- self->monsterinfo.attack = shambler_attack;
- self->monsterinfo.melee = shambler_melee;
- self->monsterinfo.sight = shambler_sight;
- self->monsterinfo.idle = shambler_idle;
- self->monsterinfo.blocked = nullptr;
- self->monsterinfo.setskin = shambler_setskin;
- gi.linkentity(self);
- if (self->spawnflags.has(SPAWNFLAG_SHAMBLER_PRECISE))
- self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
- M_SetAnimation(self, &shambler_move_stand);
- self->monsterinfo.scale = MODEL_SCALE;
- walkmonster_start(self);
- }
|