#include "monsters" const string Q1_FIEND_MODEL = "models/quake1/m_fiend.mdl"; const string Q1_FIEND_IDLE = "quake1/monsters/fiend/idle.wav"; const string Q1_FIEND_SIGHT = "quake1/monsters/fiend/sight.wav"; const string Q1_FIEND_PAIN = "quake1/monsters/fiend/pain.wav"; const string Q1_FIEND_DEATH = "quake1/monsters/fiend/death.wav"; const string Q1_FIEND_SHOOT = "quake1/monsters/fiend/shoot.wav"; const string Q1_FIEND_MELEE = "quake1/monsters/fiend/melee.wav"; const string Q1_FIEND_LAND = "quake1/monsters/land.wav"; enum Q1_FIEND_EVENTS { FIEND_IDLE_SOUND = 1, FIEND_LEAP = 2, FIEND_ATTACK_RIGHT = 3, FIEND_ATTACK_LEFT = 4 } class monster_qfiend : ScriptBaseMonsterEntity, monster_qgeneric { bool m_fMidJump = false; void Spawn() { Precache(); g_EntityFuncs.SetModel(self, Q1_FIEND_MODEL); g_EntityFuncs.SetSize(self.pev, Vector(-32, -32, -24), Vector(32, 32, 64)); self.m_bloodColor = BLOOD_COLOR_RED; self.pev.health = 300; self.pev.solid = SOLID_SLIDEBOX; self.pev.movetype = MOVETYPE_STEP; self.StartMonster(); self.m_MonsterState = MONSTERSTATE_NONE; self.m_FormattedName = "Fiend"; self.pev.pain_finished = 0.0; self.pev.yaw_speed = 20; m_iGibHealth = -100; WalkMonsterInit(); } void Precache() { g_Game.PrecacheModel(Q1_FIEND_MODEL); g_SoundSystem.PrecacheSound(Q1_FIEND_IDLE); g_SoundSystem.PrecacheSound(Q1_FIEND_SIGHT); g_SoundSystem.PrecacheSound(Q1_FIEND_DEATH); g_SoundSystem.PrecacheSound(Q1_FIEND_SHOOT); g_SoundSystem.PrecacheSound(Q1_FIEND_MELEE); g_SoundSystem.PrecacheSound(Q1_FIEND_PAIN); g_SoundSystem.PrecacheSound(Q1_FIEND_LAND); } void JumpTouch(CBaseEntity@ pOther) { m_fMidJump = false; if (self.pev.health <= 0) return; if (pOther.pev.takedamage != 0) { if (self.pev.velocity.Length() > 400) { float ldmg = 40 + Math.RandomFloat(0.0, 10.0); pOther.TakeDamage(self.pev, self.pev, ldmg, DMG_GENERIC); } } if (g_EngineFuncs.EntIsOnFloor(self.edict()) == 0) { if (self.pev.FlagBitSet(FL_ONGROUND)) { // jump randomly to not get hung up self.pev.ideal_yaw += Math.RandomLong(-45, 45); SetTouch(TouchFunction(MonsterTouch)); m_iAIState = STATE_ATTACK; SetActivity(ACT_LEAP); } return; // not on ground yet } SetTouch(TouchFunction(MonsterTouch)); AI_Face(); MonsterRun(); } bool CheckMelee() { if (m_iEnemyRange == RANGE_MELEE) { // FIXME: check canreach m_iAttackState = ATTACK_MELEE; return true; } return false; } bool CheckJump() { CBaseEntity@ pEnemy = m_hEnemy; if (self.pev.origin.z + self.pev.mins.z > pEnemy.pev.origin.z + pEnemy.pev.mins.z + 0.75f * pEnemy.pev.size.z) return false; if (self.pev.origin.z + self.pev.maxs.z < pEnemy.pev.origin.z + pEnemy.pev.mins.z + 0.25f * pEnemy.pev.size.z) return false; Vector dist = pEnemy.pev.origin - self.pev.origin; dist.z = 0; float d = dist.Length(); if (d < 100) return false; if (d > 200) return Math.RandomFloat(0, 1) >= 0.9; return true; } void MonsterIdle() { m_iAIState = STATE_IDLE; SetActivity(ACT_IDLE); m_flMonsterSpeed = 0; } void MonsterSight() { g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_FIEND_SIGHT, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); } void MonsterWalk() { m_fMidJump = false; m_iAIState = STATE_WALK; SetActivity(ACT_WALK); m_flMonsterSpeed = 6; AI_Turn(); } void MonsterRun() { m_fMidJump = false; AI_Face(); m_iAIState = STATE_RUN; SetActivity(ACT_RUN); m_flMonsterSpeed = 20; } bool MonsterCheckAttack() { if (m_iAIState == STATE_ATTACK || m_fMidJump) return false; // if close enough for slashing, go for it if (CheckMelee()) { m_iAttackState = ATTACK_MELEE; return true; } if (CheckJump()) { m_iAttackState = ATTACK_MISSILE; return true; } AI_Face(); return false; } void MonsterLeap() { g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_FIEND_SHOOT, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); AI_Face(); m_fMidJump = true; SetTouch(TouchFunction(JumpTouch)); g_EngineFuncs.MakeVectors(self.pev.angles); TraceResult tr; Vector end = self.pev.origin + Vector(0, 0, 18); if (!g_Utility.TraceMonsterHull(self.edict(), self.pev.origin, end, dont_ignore_monsters, self.edict(), tr)) self.pev.origin.z += 18; self.pev.velocity = g_Engine.v_forward * 600 + Vector(0, 0, 250); self.pev.flags &= ~FL_ONGROUND; } void MonsterMelee(float side) { if (!m_hEnemy) return; CBaseEntity@ pEnemy = m_hEnemy; AI_Face(); WalkMove(self.pev.ideal_yaw, 12); Vector delta = pEnemy.pev.origin - self.pev.origin; if (delta.Length() > 100) return; if (pEnemy.pev.takedamage == 0) return; g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_FIEND_MELEE, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); float ldmg = 10 + 5.0 * Math.RandomFloat(0, 1); pEnemy.TakeDamage(self.pev, self.pev, ldmg, DMG_GENERIC); g_EngineFuncs.MakeVectors(self.pev.angles); q1_SpawnMeatSpray(self.pev.origin + g_Engine.v_forward * 16, side * g_Engine.v_right); } void MonsterMissileAttack() { m_iAIState = STATE_ATTACK; SetActivity(ACT_LEAP); } void MonsterMeleeAttack() { m_iAIState = STATE_ATTACK; SetActivity(ACT_MELEE_ATTACK1); } void MonsterPain(CBaseEntity@ pAttacker, float flDamage) { if (m_fMidJump) return; if (self.pev.pain_finished > g_Engine.time) return; g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_FIEND_PAIN, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); self.pev.pain_finished = g_Engine.time + 1; if (Math.RandomFloat(0, 1) * 200.0 > flDamage) return; m_iAIState = STATE_PAIN; self.SetActivity(ACT_BIG_FLINCH); } bool MonsterHasMissileAttack() { return true; } bool MonsterHasMeleeAttack() { return true; } bool MonsterHasPain() { return true; } void MonsterAttack() { if(m_iAttackState == ATTACK_MELEE) { if (!m_fMidJump) AI_Face(); } else if (m_iAttackState == ATTACK_MISSILE) { AI_Charge(18); } if (m_iAIState == STATE_ATTACK && self.m_fSequenceFinished) { m_iAttackState = ATTACK_NONE; // reset shadow of attack state 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_FIEND_DEATH, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf)); } void HandleAnimEvent(MonsterEvent@ pEvent) { switch (pEvent.event) { case FIEND_ATTACK_LEFT: MonsterMelee(-200.0); break; case FIEND_ATTACK_RIGHT: MonsterMelee(200.0); break; case FIEND_LEAP: MonsterLeap(); break; case FIEND_IDLE_SOUND: if (Math.RandomFloat(0, 1) < 0.05) g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_FIEND_IDLE, Math.RandomFloat(0.95, 1.0), ATTN_IDLE, 0, 93 + Math.RandomLong(0, 0xf)); break; default: BaseClass.HandleAnimEvent(pEvent); } } } void q1_RegisterMonster_FIEND() { g_CustomEntityFuncs.RegisterCustomEntity("monster_qfiend", "monster_qfiend"); }