#include "../tempents" #include "monsters" #include "../weapons/projectile" const string Q1_SHAMBLER_MODEL = "models/quake1/m_shambler.mdl"; const string Q1_SHAMBLER_IDLE = "quake1/monsters/shambler/idle.wav"; const string Q1_SHAMBLER_SIGHT = "quake1/monsters/shambler/sight.wav"; const string Q1_SHAMBLER_PAIN = "quake1/monsters/shambler/pain.wav"; const string Q1_SHAMBLER_DEATH = "quake1/monsters/shambler/death.wav"; const string Q1_SHAMBLER_CHARGE = "quake1/monsters/shambler/charge.wav"; const string Q1_SHAMBLER_SHOOT = "quake1/monsters/shambler/shoot.wav"; const string Q1_SHAMBLER_MELEE1 = "quake1/monsters/shambler/melee1.wav"; const string Q1_SHAMBLER_MELEE2 = "quake1/monsters/shambler/melee2.wav"; const string Q1_SHAMBLER_STEP1 = "quake1/monsters/shambler/step1.wav"; const string Q1_SHAMBLER_STEP2 = "quake1/monsters/shambler/step2.wav"; const string Q1_SHAMBLER_HIT = "quake1/monsters/shambler/hit.wav"; enum Q1_SHAMBLER_EVENTS { SHAMBLER_IDLE_SOUND = 1, SHAMBLER_LEFT_STEP = 2, SHAMBLER_RIGHT_STEP = 3, SHAMBLER_CAST_LIGHTNING = 4, SHAMBLER_BEGIN_CHARGING = 5, SHAMBLER_END_CHARGING = 6, SHAMBLER_SMASH = 7, SHAMBLER_SWING_RIGHT = 8, SHAMBLER_SWING_LEFT = 9, SHAMBLER_SMASH_SOUND = 10, SHAMBLER_SWING_SOUND = 11 } enum Q1_SHAMBLER_STATES { SHAMBLER_STATE_START_CHARGING = 0, SHAMBLER_STATE_CHARGING = 2, SHAMBLER_STATE_END_CHARGING = 4 } class monster_qshambler : ScriptBaseMonsterEntity, monster_qgeneric { bool m_fCharging = false; int m_iChargeState = SHAMBLER_STATE_START_CHARGING; int m_iAttackCount = 0; void Spawn() { Precache(); g_EntityFuncs.SetModel(self, Q1_SHAMBLER_MODEL); self.m_bloodColor = BLOOD_COLOR_RED; self.pev.health = 600; self.pev.solid = SOLID_SLIDEBOX; self.pev.movetype = MOVETYPE_STEP; g_EntityFuncs.SetSize(self.pev, Vector(-32, -32, -24), Vector(32, 32, 64)); self.StartMonster(); self.m_MonsterState = MONSTERSTATE_NONE; self.m_FormattedName = "Shambler"; self.pev.pain_finished = 0.0; m_iGibHealth = -200; WalkMonsterInit(); } void Precache() { g_Game.PrecacheModel(Q1_SHAMBLER_MODEL); g_Game.PrecacheModel("sprites/laserbeam.spr"); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_IDLE); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_SIGHT); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_PAIN); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_DEATH); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_CHARGE); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_SHOOT); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_MELEE1); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_MELEE2); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_STEP1); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_STEP2); g_SoundSystem.PrecacheSound(Q1_SHAMBLER_HIT); } void MonsterIdle() { m_iAIState = STATE_IDLE; SetActivity(ACT_IDLE); m_flMonsterSpeed = 0; } void MonsterSight() { g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SHAMBLER_SIGHT, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); } void MonsterWalk() { m_iAIState = STATE_WALK; SetActivity(ACT_WALK); m_flMonsterSpeed = 8; } void MonsterRun() { m_iAIState = STATE_RUN; SetActivity(ACT_RUN); m_flMonsterSpeed = 20; } void MonsterMissileAttack() { m_iAIState = STATE_ATTACK; SetActivity(ACT_RANGE_ATTACK1); } void MonsterMeleeAttack() { m_iAIState = STATE_ATTACK; SetActivity(ACT_MELEE_ATTACK1); } bool MonsterCheckAttack() { if (m_fCharging) return true; if (!m_hEnemy) return false; CBaseEntity@ pEnemy = m_hEnemy; if (m_iEnemyRange == RANGE_MELEE) { if (pEnemy.pev.takedamage != 0) { m_iAttackState = ATTACK_MELEE; return true; } } if (g_Engine.time < m_flAttackFinished) return false; if (!m_fEnemyVisible) return false; // see if any entities are in the way of the shot Vector spot1 = self.EyePosition(); Vector spot2 = pEnemy.EyePosition(); if ((spot1 - spot2).Length() > 600) return false; TraceResult tr; g_Utility.TraceLine(spot1, spot2, dont_ignore_monsters, dont_ignore_glass, self.edict(), tr); if (tr.fInOpen != 0 && tr.fInWater != 0) return false; // sight line crossed contents if (tr.pHit !is pEnemy.edict()) return false; // don't have a clear shot // missile attack if (m_iEnemyRange == RANGE_FAR) return false; m_iAttackState = ATTACK_MISSILE; AttackFinished(2 + Math.RandomFloat(0.0f, 2.0f)); return true; } void BeginCharging() { if (m_fCharging) return; m_fCharging = true; self.StopAnimation(); SetThink(ThinkFunction(Charge)); self.pev.nextthink = g_Engine.time + 0.1; self.pev.effects |= EF_MUZZLEFLASH; g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_WEAPON, Q1_SHAMBLER_CHARGE, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); m_iChargeState = SHAMBLER_STATE_START_CHARGING; // reset charge counter m_iAttackCount = 0; } void Charge() { if (m_iChargeState == SHAMBLER_STATE_CHARGING || m_iChargeState >= SHAMBLER_STATE_END_CHARGING) { // continue animation self.ResetSequenceInfo(); if (m_iChargeState >= SHAMBLER_STATE_END_CHARGING) { m_fCharging = false; SetThink(ThinkFunction(MonsterThink)); m_iChargeState = SHAMBLER_STATE_START_CHARGING; self.pev.nextthink = g_Engine.time + 0.1; return; } } float flInterval = self.StudioFrameAdvance(0.099); // animate self.DispatchAnimEvents(flInterval); self.pev.nextthink = g_Engine.time + 0.1; self.pev.effects |= EF_MUZZLEFLASH; m_iChargeState++; } void EndCharging() { if (!m_fCharging) return; self.StopAnimation(); } void CastLightning() { if (!m_hEnemy) return; CBaseEntity@ pEnemy = m_hEnemy; AI_Face(); if (m_iAttackCount == 0) g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_WEAPON, Q1_SHAMBLER_SHOOT, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); self.pev.effects |= EF_MUZZLEFLASH; ++m_iAttackCount; AI_Face(); Vector vecOrg = self.pev.origin + Vector(0, 0, 40); Vector vecDir = (pEnemy.pev.origin + Vector(0, 0, 16) - vecOrg).Normalize(); Vector vecEnd = vecOrg + vecDir * 600; TraceResult tr; g_Utility.TraceLine(vecOrg, vecEnd, dont_ignore_monsters, self.edict(), tr); q1_TE_BeamPoints(vecOrg, tr.vecEndPos); if (tr.pHit !is null) { CBaseEntity@ pHit = g_EntityFuncs.Instance(tr.pHit); if (pHit !is null && !pHit.IsBSPModel() && pHit.pev.takedamage != 0) { g_WeaponFuncs.ClearMultiDamage(); pHit.TraceAttack(self.pev, 10, vecDir, tr, DMG_SHOCK); g_WeaponFuncs.ApplyMultiDamage(self.pev, self.pev); } } } void MonsterClaw(float side) { if (!m_hEnemy) return; CBaseEntity@ pEnemy = m_hEnemy; AI_Charge(10); Vector delta = pEnemy.pev.origin - self.pev.origin; if (delta.Length() > 100) return; float ldmg = (Math.RandomFloat(0, 1) + Math.RandomFloat(0, 1) + Math.RandomFloat(0, 1)) * 20; pEnemy.TakeDamage(self.pev, self.pev, ldmg, DMG_SLASH); g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SHAMBLER_HIT, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); if (side != 0.0) { g_EngineFuncs.MakeVectors(self.pev.angles); q1_SpawnMeatSpray(self.pev.origin + g_Engine.v_forward * 16, side * g_Engine.v_right); } } void MonsterSmash() { if (!m_hEnemy) return; CBaseEntity@ pEnemy = m_hEnemy; AI_Charge(0); Vector delta = pEnemy.pev.origin - self.pev.origin; if (delta.Length() > 100) return; float ldmg = (Math.RandomFloat(0, 1) + Math.RandomFloat(0, 1) + Math.RandomFloat(0, 1)) * 40; pEnemy.TakeDamage(self.pev, self.pev, ldmg, DMG_SLASH); g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SHAMBLER_HIT, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); g_EngineFuncs.MakeVectors(self.pev.angles); q1_SpawnMeatSpray(self.pev.origin + g_Engine.v_forward * 16, Math.RandomFloat(-100, 100) * g_Engine.v_right); q1_SpawnMeatSpray(self.pev.origin + g_Engine.v_forward * 16, Math.RandomFloat(-100, 100) * g_Engine.v_right); } void MonsterPain(CBaseEntity@ pAttacker, float flDamage) { g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SHAMBLER_PAIN, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); if (self.pev.health <= 0) return; // allready dying, don't go into pain frame if (Math.RandomFloat(0.0, 1.0) * 400 > flDamage) return; // didn't flinch if (self.pev.pain_finished > g_Engine.time) return; self.pev.pain_finished = g_Engine.time + 2; m_iAIState = STATE_PAIN; SetActivity(ACT_BIG_FLINCH); } bool MonsterHasMissileAttack() { return true; } bool MonsterHasMeleeAttack() { return true; } bool MonsterHasPain() { return true; } void MonsterAttack() { AI_Charge(5); if (m_iAIState == STATE_ATTACK && self.m_fSequenceFinished) MonsterRun(); } void MonsterKilled(entvars_t@ pevAttacker, int iGib) { if (ShouldGibMonster(iGib)) { g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, "quake1/gib.wav", Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); g_EntityFuncs.SpawnRandomGibs(self.pev, 1, 1); g_EntityFuncs.Remove(self); return; } g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SHAMBLER_DEATH, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); } void HandleAnimEvent(MonsterEvent@ pEvent) { CBaseEntity@ pEnemy = m_hEnemy; switch (pEvent.event) { case SHAMBLER_IDLE_SOUND: if (Math.RandomFloat(0, 1) < 0.1) g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SHAMBLER_IDLE, Math.RandomFloat(0.95, 1.0), ATTN_IDLE, 0, 93 + Math.RandomLong(0, 0xf)); break; case SHAMBLER_LEFT_STEP: // dont make sound steps while shambler do melee attack if (!m_hEnemy || (pEnemy.pev.origin - self.pev.origin).Length() > 100) { g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_BODY, Q1_SHAMBLER_STEP1, Math.RandomFloat(0.95, 1.0), ATTN_IDLE, 0, 93 + Math.RandomLong(0, 0xf)); g_PlayerFuncs.ScreenShake(self.pev.origin + Vector(0, 0, -24), 2.0f, 2.0f, 0.5f, 250.0f); } break; case SHAMBLER_RIGHT_STEP: if (!m_hEnemy || (pEnemy.pev.origin - self.pev.origin).Length() > 100) { g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_BODY, Q1_SHAMBLER_STEP2, Math.RandomFloat(0.95, 1.0), ATTN_IDLE, 0, 93 + Math.RandomLong(0, 0xf)); g_PlayerFuncs.ScreenShake(self.pev.origin + Vector(0, 0, -24), 2.0f, 2.0f, 0.5f, 250.0f); } break; case SHAMBLER_CAST_LIGHTNING: CastLightning(); break; case SHAMBLER_BEGIN_CHARGING: BeginCharging(); break; case SHAMBLER_END_CHARGING: EndCharging(); break; case SHAMBLER_SMASH: MonsterSmash(); break; case SHAMBLER_SMASH_SOUND: g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SHAMBLER_MELEE1, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); break; case SHAMBLER_SWING_SOUND: g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SHAMBLER_MELEE2, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); break; case SHAMBLER_SWING_LEFT: MonsterClaw(250); break; case SHAMBLER_SWING_RIGHT: MonsterClaw(-250); break; default: BaseClass.HandleAnimEvent(pEvent); } } } void q1_RegisterMonster_SHAMBLER() { g_CustomEntityFuncs.RegisterCustomEntity("monster_qshambler", "monster_qshambler"); }