diff --git a/cfg/example_monster.cfg b/cfg/example_monster.cfg index d1bbcb7..2fe1681 100755 --- a/cfg/example_monster.cfg +++ b/cfg/example_monster.cfg @@ -11,7 +11,7 @@ // "displayname" to change the monster's name, shown in HUD when you point at it. // "classify" to change the monster's default classification, use one of these values: // -// CLASS_NONE 0 +// CLASS_NONE -1 // CLASS_MACHINE 1 // CLASS_PLAYER 2 // CLASS_HUMAN_PASSIVE 3 diff --git a/cfg/monster_precache.cfg b/cfg/monster_precache.cfg index 2fbe81e..b255eec 100755 --- a/cfg/monster_precache.cfg +++ b/cfg/monster_precache.cfg @@ -3,8 +3,11 @@ // // Install this file to your mod's base directory. // -// (just remove the comment characters at the beginning of the line for the -// monsters that you always want to precache.) +// Just remove the comment characters at the beginning of the line for the +// monsters that you always want to precache. +// +// Be wary if you precache too many monsters, it may easily crash your +// server due to the age-old 512 limit. //monster_alien_grunt //monster_apache @@ -24,3 +27,4 @@ //monster_turret //monster_miniturret //monster_sentry +//monster_gonome diff --git a/cfg/monster_skill.cfg b/cfg/monster_skill.cfg index 2c7267c..97a97a7 100755 --- a/cfg/monster_skill.cfg +++ b/cfg/monster_skill.cfg @@ -2,6 +2,8 @@ // // Install this file to your mod's base directory. +// ORIGINAL HALF-LIFE MONSTERS + // Alien Grunt sk_agrunt_health 90 sk_agrunt_dmg_punch 20 @@ -55,10 +57,6 @@ sk_islave_dmg_claw 10 sk_islave_dmg_clawrake 25 sk_islave_dmg_zap 10 -// Icthyosaur -sk_ichthyosaur_health 200 -sk_ichthyosaur_shake 35 - // Controller sk_controller_health 60 sk_controller_dmgzap 25 @@ -88,6 +86,15 @@ sk_miniturret_health 40 // Sentry Turret sk_sentry_health 40 +// OPPOSING FORCE MONSTERS + +// Gonome +sk_gonome_health 85 +sk_gonome_dmg_one_slash 20 +sk_gonome_dmg_guts 10 +sk_gonome_dmg_one_bite 14 + + // MONSTER WEAPON DAMAGE sk_9mm_bullet 5 sk_9mmAR_bullet 4 diff --git a/src/dlls/Makefile b/src/dlls/Makefile index 3db3e86..983528f 100644 --- a/src/dlls/Makefile +++ b/src/dlls/Makefile @@ -20,6 +20,7 @@ OBJ = \ explode.o \ effects.o \ gargantua.o \ + gonome.o \ ggrenade.o \ h_ai.o \ h_export.o \ diff --git a/src/dlls/cmbasemonster.h b/src/dlls/cmbasemonster.h index 2428e68..b0595b0 100644 --- a/src/dlls/cmbasemonster.h +++ b/src/dlls/cmbasemonster.h @@ -1237,4 +1237,78 @@ public: }; +// +// opposing force monsters +// + +//========================================================= +// Gonome's guts projectile +//========================================================= +class CGonomeGuts : public CMBaseEntity +{ +public: + void Spawn( void ); + + static edict_t *Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + void GutsTouch( edict_t *pOther ); + void EXPORT Animate( void ); + + int m_maxFrame; +}; + +//========================================================= +// CGonome +//========================================================= +class CMGonome : public CMBaseMonster +{ +public: + + void Spawn(void); + void Precache(void); + + int Classify(void); + void SetYawSpeed(); + void HandleAnimEvent(MonsterEvent_t *pEvent); + int IgnoreConditions(); + void IdleSound( void ); + void PainSound( void ); + void DeathSound( void ); + void AlertSound( void ); + void StartTask(Task_t *pTask); + + BOOL CheckMeleeAttack2(float flDot, float flDist); + BOOL CheckRangeAttack1(float flDot, float flDist); + void SetActivity( Activity NewActivity ); + + Schedule_t *GetSchedule(); + Schedule_t *GetScheduleOfType( int Type ); + void RunTask(Task_t* pTask); + + int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void Killed(entvars_t *pevAttacker, int iGib); + + void UnlockPlayer(); + CGonomeGuts* GetGonomeGuts(entvars_t *pevOwner, const Vector& pos); + void ClearGuts(); + + CUSTOM_SCHEDULES; + + static const char* pPainSounds[]; + static const char* pIdleSounds[]; + static const char* pDeathSounds[]; + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + +protected: + float m_flNextFlinch; + float m_flNextThrowTime;// last time the gonome used the guts attack. + CGonomeGuts* m_pGonomeGuts; + + BOOL m_fPlayerLocked; + EHANDLE m_lockedPlayer; + + bool m_meleeAttack2; + bool m_playedAttackSound; +}; + #endif // BASEMONSTER_H diff --git a/src/dlls/dllapi.cpp b/src/dlls/dllapi.cpp index 860d549..cf72bd5 100644 --- a/src/dlls/dllapi.cpp +++ b/src/dlls/dllapi.cpp @@ -134,7 +134,7 @@ monster_type_t monster_types[]= // can be spawned. Monsters should go first. // DO NOT ALTER THE ORDER OF ELEMENTS! - "monster_alien_grunt", FALSE, // Monsters + "monster_alien_grunt", FALSE, // Original Half-Life Monsters "monster_apache", FALSE, "monster_barney", FALSE, "monster_bigmomma", FALSE, @@ -148,10 +148,11 @@ monster_type_t monster_types[]= "monster_scientist", FALSE, "monster_snark", FALSE, "monster_zombie", FALSE, - "monster_gargantua", FALSE, + "monster_gargantua", FALSE, "monster_turret", FALSE, "monster_miniturret", FALSE, "monster_sentry", FALSE, + "monster_gonome", FALSE, // Opposing Force Monsters "info_node", FALSE, // Nodes "info_node_air", FALSE, "", FALSE @@ -615,10 +616,11 @@ bool spawn_monster(int monster_type, Vector origin, Vector angles, int respawn_i case 11: monsters[monster_index].pMonster = CreateClassPtr((CMScientist *)NULL); break; case 12: monsters[monster_index].pMonster = CreateClassPtr((CMSqueakGrenade *)NULL); break; case 13: monsters[monster_index].pMonster = CreateClassPtr((CMZombie *)NULL); break; - case 14: monsters[monster_index].pMonster = CreateClassPtr((CMGargantua *)NULL); break; + case 14: monsters[monster_index].pMonster = CreateClassPtr((CMGargantua *)NULL); break; case 15: monsters[monster_index].pMonster = CreateClassPtr((CMTurret *)NULL); break; case 16: monsters[monster_index].pMonster = CreateClassPtr((CMMiniTurret *)NULL); break; case 17: monsters[monster_index].pMonster = CreateClassPtr((CMSentry *)NULL); break; + case 18: monsters[monster_index].pMonster = CreateClassPtr((CMGonome *)NULL); break; } if (monsters[monster_index].pMonster == NULL) @@ -1294,6 +1296,7 @@ void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) CMTurret turret; CMMiniTurret miniturret; CMSentry sentry; + CMGonome gonome; g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); @@ -1329,6 +1332,7 @@ void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) case 15: turret.Precache(); break; case 16: miniturret.Precache(); break; case 17: sentry.Precache(); break; + case 18: gonome.Precache(); break; } } } diff --git a/src/dlls/gonome.cpp b/src/dlls/gonome.cpp new file mode 100644 index 0000000..8dbd73f --- /dev/null +++ b/src/dlls/gonome.cpp @@ -0,0 +1,881 @@ +// HUGE thanks to DrBeef for his hlsdk-xash3d-opfor repository! + +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +//========================================================= +// Gonome.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "decals.h" +#include "nodes.h" + +#define GONOME_MELEE_ATTACK_RADIUS 70 + +enum +{ + TASK_GONOME_GET_PATH_TO_ENEMY_CORPSE = LAST_COMMON_TASK + 1 +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +#define GONOME_AE_SLASH_RIGHT ( 1 ) +#define GONOME_AE_SLASH_LEFT ( 2 ) +#define GONOME_AE_SPIT ( 3 ) +#define GONOME_AE_THROW ( 4 ) + +#define GONOME_AE_BITE1 ( 19 ) +#define GONOME_AE_BITE2 ( 20 ) +#define GONOME_AE_BITE3 ( 21 ) +#define GONOME_AE_BITE4 ( 22 ) + +#define GONOME_SCRIPT_EVENT_SOUND ( 1011 ) + +void CGonomeGuts :: Spawn( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->classname = MAKE_STRING( "gonomeguts" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + + SET_MODEL( ENT( pev ), "sprites/bigspit.spr" ); + pev->frame = 0; + pev->scale = 0.5; + pev->rendercolor.x = 255; + + UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); + + m_maxFrame = (float)MODEL_FRAMES( pev->modelindex ) - 1; +} + +void CGonomeGuts :: Animate( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +edict_t *CGonomeGuts :: Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CGonomeGuts *pSpit = CreateClassPtr( (CGonomeGuts *)NULL ); + + if (pSpit == NULL) + return NULL; + + pSpit->Spawn(); + + UTIL_SetOrigin( pSpit->pev, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = ENT(pevOwner); + + pSpit->SetThink ( &CGonomeGuts::Animate ); + pSpit->pev->nextthink = gpGlobals->time + 0.1; + pSpit->SetTouch ( &CGonomeGuts::GutsTouch ); + + return pSpit->edict(); +} + +void CGonomeGuts :: GutsTouch( edict_t *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "bullchicken/bc_acid1.wav", 1, ATTN_NORM, 0, iPitch ); + + switch( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "bullchicken/bc_spithit1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "bullchicken/bc_spithit2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + if( !pOther->v.takedamage ) + { + // make a splat on the wall + UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT( pev ), &tr ); + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + UTIL_BloodDrips( tr.vecEndPos, UTIL_RandomBloodVector(), BLOOD_COLOR_RED, 35 ); + } + else + { + if (UTIL_IsPlayer(pOther)) + UTIL_TakeDamage( pOther, pev, pev, gSkillData.gonomeDmgGuts, DMG_GENERIC ); + else if (pOther->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); + pMonster->TakeDamage ( pev, pev, gSkillData.gonomeDmgGuts, DMG_GENERIC ); + } + } + + SetThink( &CGonomeGuts::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + + +const char* CMGonome::pPainSounds[] = { + "gonome/gonome_pain1.wav", + "gonome/gonome_pain2.wav", + "gonome/gonome_pain3.wav", + "gonome/gonome_pain4.wav" +}; + +const char* CMGonome::pIdleSounds[] = { + "gonome/gonome_idle1.wav", + "gonome/gonome_idle2.wav", + "gonome/gonome_idle3.wav" +}; + +const char* CMGonome::pDeathSounds[] = { + "gonome/gonome_death2.wav", + "gonome/gonome_death3.wav", + "gonome/gonome_death4.wav" +}; + +const char* CMGonome::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char* CMGonome::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + + +void CMGonome::Killed(entvars_t *pevAttacker, int iGib) +{ + ClearGuts(); + UnlockPlayer(); + CMBaseMonster::Killed(pevAttacker, iGib); +} + +void CMGonome::UnlockPlayer() +{ + if (m_fPlayerLocked) + { + edict_t *player = 0; + if (m_lockedPlayer != 0 && UTIL_IsPlayer(m_lockedPlayer)) + player = m_lockedPlayer; + else // if ehandle is empty for some reason just unlock the first player + player = UTIL_FindEntityByClassname(0, "player"); + + if (player) + player->v.flags &= ~FL_FROZEN; + + m_lockedPlayer = 0; + m_fPlayerLocked = FALSE; + } +} + +CGonomeGuts* CMGonome::GetGonomeGuts(entvars_t *pevOwner, const Vector &pos) +{ + if (m_pGonomeGuts) + return m_pGonomeGuts; + edict_t *pEdict = CGonomeGuts::Shoot( pevOwner, g_vecZero, g_vecZero ); + CGonomeGuts *pGuts = GetClassPtr((CGonomeGuts*)VARS(pEdict)); + pGuts->Spawn(); + + UTIL_SetOrigin( pGuts->pev, pos ); + + m_pGonomeGuts = pGuts; + return m_pGonomeGuts; +} + +void CMGonome::ClearGuts() +{ + if (m_pGonomeGuts) + { + UTIL_Remove( m_pGonomeGuts->edict() ); + m_pGonomeGuts = 0; + } +} + +void CMGonome::PainSound( void ) +{ + int pitch = 95 + RANDOM_LONG( 0, 9 ); + + if( RANDOM_LONG( 0, 5 ) < 2 ) + EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), 1.0, ATTN_NORM, 0, pitch ); +} + +void CMGonome::DeathSound( void ) +{ + int pitch = 95 + RANDOM_LONG( 0, 9 ); + + EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), 1.0, ATTN_NORM, 0, pitch ); +} + +void CMGonome::IdleSound( void ) +{ + int pitch = 95 + RANDOM_LONG( 0, 9 ); + + // Play a random idle sound + EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), 1.0, ATTN_NORM, 0, pitch ); +} + +void CMGonome::AlertSound( void ) +{ + const int iPitch = RANDOM_LONG(0, 9) + 95; + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), 1, ATTN_NORM, 0, iPitch); +} + +void CMGonome::SetActivity( Activity NewActivity ) +{ + Activity OldActivity = m_Activity; + int iSequence = ACTIVITY_NOT_AVAILABLE; + + if (NewActivity != ACT_RANGE_ATTACK1) + { + ClearGuts(); + } + if (NewActivity == ACT_MELEE_ATTACK1 && m_hEnemy != 0) + { + // special melee animations + if ((pev->origin - m_hEnemy->v.origin).Length2D() >= 48 ) + { + m_meleeAttack2 = false; + iSequence = LookupSequence("attack1"); + } + else + { + m_meleeAttack2 = true; + iSequence = LookupSequence("attack2"); + } + } + else + { + UnlockPlayer(); + + if (NewActivity == ACT_RUN && m_hEnemy != 0) + { + // special run animations + if ((pev->origin - m_hEnemy->v.origin).Length2D() <= 512 ) + { + iSequence = LookupSequence("runshort"); + } + else + { + iSequence = LookupSequence("runlong"); + } + } + else + { + iSequence = LookupActivity(NewActivity); + } + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // In case someone calls this with something other than the ideal activity + m_IdealActivity = m_Activity; + + // Set to the desired anim, or default anim if the desired is not present + if( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if( pev->sequence != iSequence || !m_fSequenceLoops ) + { + // don't reset frame between walk and run + if( !( OldActivity == ACT_WALK || OldActivity == ACT_RUN ) || !( NewActivity == ACT_WALK || NewActivity == ACT_RUN ) ) + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo(); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT( at_aiconsole, "%s has no sequence for act:%d\n", STRING( pev->classname ), NewActivity ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMGonome::Classify(void) +{ + if ( m_iClassifyOverride == -1 ) // helper + return CLASS_NONE; + else if ( m_iClassifyOverride > 0 ) + return m_iClassifyOverride; // override + + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// TakeDamage - overridden for gonome so we can keep track +// of how much time has passed since it was last injured +//========================================================= +int CMGonome::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + // Take 15% damage from bullets + if( bitsDamageType == DMG_BULLET ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + float flForce = DamageForce( flDamage ); + pev->velocity = pev->velocity + vecDir * flForce; + flDamage *= 0.15; + } + + // HACK HACK -- until we fix this. + if( IsAlive() ) + PainSound(); + return CMBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CMGonome::CheckRangeAttack1(float flDot, float flDist) +{ + if (flDist < 256) + return FALSE; + + if (IsMoving() && flDist >= 512) + { + // squid will far too far behind if he stops running to spit at this distance from the enemy. + return FALSE; + } + + if (flDist > 64 && flDist <= 784 && flDot >= 0.5 && gpGlobals->time >= m_flNextThrowTime) + { + if (m_hEnemy != 0) + { + if (fabs(pev->origin.z - m_hEnemy->v.origin.z) > 256) + { + // don't try to spit at someone up really high or down really low. + return FALSE; + } + } + + if (IsMoving()) + { + // don't spit again for a long time, resume chasing enemy. + m_flNextThrowTime = gpGlobals->time + 5; + } + else + { + // not moving, so spit again pretty soon. + m_flNextThrowTime = gpGlobals->time + 0.5; + } + + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 - both gonome's melee attacks are ACT_MELEE_ATTACK1 +//========================================================= +BOOL CMGonome::CheckMeleeAttack2(float flDot, float flDist) +{ + return FALSE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMGonome::SetYawSpeed( void ) +{ + pev->yaw_speed = 120; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMGonome::HandleAnimEvent(MonsterEvent_t *pEvent) +{ + switch (pEvent->event) + { + case GONOME_SCRIPT_EVENT_SOUND: + if (m_Activity != ACT_MELEE_ATTACK1) + EMIT_SOUND(ENT(pev), CHAN_BODY, pEvent->options, 1, ATTN_NORM); + break; + case GONOME_AE_SPIT: + { + Vector vecArmPos, vecArmAng; + GetAttachment(0, vecArmPos, vecArmAng); + + if (GetGonomeGuts(pev, vecArmPos)) + { + m_pGonomeGuts->pev->skin = entindex(); + m_pGonomeGuts->pev->body = 1; + m_pGonomeGuts->pev->aiment = ENT(pev); + m_pGonomeGuts->pev->movetype = MOVETYPE_FOLLOW; + } + UTIL_BloodDrips( vecArmPos, UTIL_RandomBloodVector(), BLOOD_COLOR_RED, 35 ); + } + break; + case GONOME_AE_THROW: + { + UTIL_MakeVectors(pev->angles); + Vector vecArmPos, vecArmAng; + GetAttachment(0, vecArmPos, vecArmAng); + + if (GetGonomeGuts(pev, vecArmPos)) + { + Vector vecSpitDir; + + Vector vecEnemyPosition; + if (m_hEnemy != 0) + vecEnemyPosition = (m_hEnemy->v.origin + m_hEnemy->v.view_ofs); + else + vecEnemyPosition = m_vecEnemyLKP; + vecSpitDir = (vecEnemyPosition - vecArmPos).Normalize(); + + vecSpitDir.x += RANDOM_FLOAT(-0.05, 0.05); + vecSpitDir.y += RANDOM_FLOAT(-0.05, 0.05); + vecSpitDir.z += RANDOM_FLOAT(-0.05, 0); + + m_pGonomeGuts->pev->body = 0; + m_pGonomeGuts->pev->skin = 0; + m_pGonomeGuts->pev->owner = ENT( pev ); + m_pGonomeGuts->pev->aiment = 0; + m_pGonomeGuts->pev->movetype = MOVETYPE_FLY; + m_pGonomeGuts->pev->velocity = vecSpitDir * 900; + m_pGonomeGuts->SetThink( &CGonomeGuts::Animate ); + m_pGonomeGuts->pev->nextthink = gpGlobals->time + 0.1; + UTIL_SetOrigin(m_pGonomeGuts->pev, vecArmPos); + + m_pGonomeGuts = 0; + } + UTIL_BloodDrips( vecArmPos, UTIL_RandomBloodVector(), BLOOD_COLOR_RED, 35 ); + } + break; + + case GONOME_AE_SLASH_LEFT: + { + edict_t *pHurt = CheckTraceHullAttack(GONOME_MELEE_ATTACK_RADIUS, gSkillData.gonomeDmgOneSlash, DMG_SLASH); + if (pHurt) + { + if (FBitSet(pHurt->v.flags, FL_MONSTER|FL_CLIENT)) + { + pHurt->v.punchangle.z = 9; + pHurt->v.punchangle.x = 5; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_right * 25; + } + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackHitSounds), 1, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5)); + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackMissSounds), 1, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5)); + } + } + break; + + case GONOME_AE_SLASH_RIGHT: + { + edict_t *pHurt = CheckTraceHullAttack(GONOME_MELEE_ATTACK_RADIUS, gSkillData.gonomeDmgOneSlash, DMG_SLASH); + if (pHurt) + { + if (FBitSet(pHurt->v.flags, FL_MONSTER|FL_CLIENT)) + { + pHurt->v.punchangle.z = -9; + pHurt->v.punchangle.x = 5; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_right * -25; + } + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackHitSounds), 1, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5)); + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackMissSounds), 1, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5)); + } + } + break; + + case GONOME_AE_BITE1: + case GONOME_AE_BITE2: + case GONOME_AE_BITE3: + case GONOME_AE_BITE4: + { + int iPitch; + edict_t *pHurt = CheckTraceHullAttack(GONOME_MELEE_ATTACK_RADIUS, gSkillData.gonomeDmgOneBite, DMG_SLASH); + + if (pHurt) + { + // croonchy bite sound + iPitch = RANDOM_FLOAT(90, 110); + switch (RANDOM_LONG(0, 1)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "bullchicken/bc_bite2.wav", 1, ATTN_NORM, 0, iPitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "bullchicken/bc_bite3.wav", 1, ATTN_NORM, 0, iPitch); + break; + } + + if (FBitSet(pHurt->v.flags, FL_MONSTER|FL_CLIENT)) + { + if (pEvent->event == GONOME_AE_BITE4) + { + pHurt->v.punchangle.x = 15; + pHurt->v.velocity = pHurt->v.velocity - gpGlobals->v_forward * 75; + } + else + { + pHurt->v.punchangle.x = 9; + pHurt->v.velocity = pHurt->v.velocity - gpGlobals->v_forward * 25; + } + } + // lock player + if (pEvent->event == GONOME_AE_BITE4) + { + UnlockPlayer(); + } + else if (UTIL_IsPlayer( pHurt ) && UTIL_IsAlive( pHurt )) + { + if (!m_fPlayerLocked) + { + edict_t *player = pHurt; + player->v.flags |= FL_FROZEN; + m_lockedPlayer = player; + m_fPlayerLocked = TRUE; + } + } + } + } + break; + + default: + CMBaseMonster::HandleAnimEvent(pEvent); + } +} + +#define GONOME_FLINCH_DELAY 2 + +int CMGonome::IgnoreConditions( void ) +{ + int iIgnore = CMBaseMonster::IgnoreConditions(); + + if( m_Activity == ACT_MELEE_ATTACK1 ) + { + if( m_flNextFlinch >= gpGlobals->time ) + iIgnore |= ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ); + } + + if( ( m_Activity == ACT_SMALL_FLINCH ) || ( m_Activity == ACT_BIG_FLINCH ) ) + { + if( m_flNextFlinch < gpGlobals->time ) + m_flNextFlinch = gpGlobals->time + GONOME_FLINCH_DELAY; + } + + return iIgnore; +} + +//========================================================= +// Spawn +//========================================================= +void CMGonome::Spawn() +{ + Precache(); + + SET_MODEL(ENT(pev), "models/gonome.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.gonomeHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + m_flNextThrowTime = gpGlobals->time; + + MonsterInit(); + + pev->classname = MAKE_STRING( "monster_gonome" ); + if ( strlen( STRING( m_szMonsterName ) ) == 0 ) + { + // default name + m_szMonsterName = MAKE_STRING( "Gonome" ); + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMGonome::Precache() +{ + PRECACHE_MODEL("models/gonome.mdl"); + + PRECACHE_MODEL("sprites/bigspit.spr");// spit projectile. + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + PRECACHE_SOUND("gonome/gonome_eat.wav"); + PRECACHE_SOUND("gonome/gonome_jumpattack.wav"); + PRECACHE_SOUND("gonome/gonome_melee1.wav"); + PRECACHE_SOUND("gonome/gonome_melee2.wav"); + + PRECACHE_SOUND_ARRAY(pIdleSounds); + PRECACHE_SOUND_ARRAY(pPainSounds); + PRECACHE_SOUND_ARRAY(pDeathSounds); + + PRECACHE_SOUND("gonome/gonome_run.wav"); + + PRECACHE_SOUND("bullchicken/bc_acid1.wav"); + + PRECACHE_SOUND("bullchicken/bc_bite2.wav"); + PRECACHE_SOUND("bullchicken/bc_bite3.wav"); + + PRECACHE_SOUND("bullchicken/bc_spithit1.wav"); + PRECACHE_SOUND("bullchicken/bc_spithit2.wav"); +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CMGonome::GetSchedule( void ) +{ + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { + // dead enemy + if( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CMBaseMonster::GetSchedule(); + } + + if( HasConditions( bits_COND_NEW_ENEMY ) ) + { + return GetScheduleOfType( SCHED_WAKE_ANGRY ); + } + + if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + if( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + + if( HasConditions( bits_COND_CAN_MELEE_ATTACK2 ) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK2 ); + } + + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + break; + } + default: + break; + } + + return CMBaseMonster::GetSchedule(); +} + +// primary range attack +Task_t tlGonomeRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slGonomeRangeAttack1[] = +{ + { + tlGonomeRangeAttack1, + ARRAYSIZE( tlGonomeRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "Gonome Range Attack1" + }, +}; + +// Chase enemy schedule +Task_t tlGonomeChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 },// !!!OEM - this will stop nasty squid oscillation. + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slGonomeChaseEnemy[] = +{ + { + tlGonomeChaseEnemy1, + ARRAYSIZE( tlGonomeChaseEnemy1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_SMELL_FOOD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED, + 0, + "Gonome Chase Enemy" + }, +}; + +// victory dance (eating body) +Task_t tlGonomeVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.1 }, + { TASK_GONOME_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE } +}; + +Schedule_t slGonomeVictoryDance[] = +{ + { + tlGonomeVictoryDance, + ARRAYSIZE( tlGonomeVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GonomeVictoryDance" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CMGonome ) +{ + slGonomeRangeAttack1, + slGonomeChaseEnemy, + slGonomeVictoryDance, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMGonome, CMBaseMonster ) + +Schedule_t* CMGonome::GetScheduleOfType(int Type) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + return &slGonomeRangeAttack1[0]; + break; + case SCHED_CHASE_ENEMY: + return &slGonomeChaseEnemy[0]; + break; + case SCHED_VICTORY_DANCE: + return &slGonomeVictoryDance[0]; + break; + default: + break; + } + return CMBaseMonster::GetScheduleOfType(Type); +} + +void CMGonome::RunTask(Task_t *pTask) +{ + // HACK to stop Gonome from playing attack sound twice + if (pTask->iTask == TASK_MELEE_ATTACK1) + { + if (!m_playedAttackSound) + { + const char* sample = NULL; + if (m_meleeAttack2) + { + sample = "gonome/gonome_melee2.wav"; + } + else + { + sample = "gonome/gonome_melee1.wav"; + } + EMIT_SOUND(ENT(pev), CHAN_BODY, sample, 1, ATTN_NORM); + m_playedAttackSound = true; + } + } + else + { + m_playedAttackSound = false; + } + CMBaseMonster::RunTask(pTask); +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CMGonome::StartTask(Task_t *pTask) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch (pTask->iTask) + { + case TASK_GONOME_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if( BuildRoute( m_vecEnemyLKP - gpGlobals->v_forward * 40, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT( at_aiconsole, "GonomeGetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + default: + CMBaseMonster::StartTask(pTask); + break; + + } +} diff --git a/src/dlls/skill.cpp b/src/dlls/skill.cpp index 609dc44..64e0270 100644 --- a/src/dlls/skill.cpp +++ b/src/dlls/skill.cpp @@ -72,6 +72,10 @@ skill_cfg_t skill_cfg[] = { {"sk_turret_health", &gSkillData.turretHealth}, {"sk_miniturret_health", &gSkillData.miniturretHealth}, {"sk_sentry_health", &gSkillData.sentryHealth}, + {"sk_gonome_health", &gSkillData.gonomeHealth}, + {"sk_gonome_dmg_guts", &gSkillData.gonomeDmgGuts}, + {"sk_gonome_dmg_one_slash", &gSkillData.gonomeDmgOneSlash}, + {"sk_gonome_dmg_one_bite", &gSkillData.gonomeDmgOneBite}, {"sk_12mm_bullet", &gSkillData.monDmg9MM}, {"sk_9mmAR_bullet", &gSkillData.monDmgMP5}, {"sk_9mm_bullet", &gSkillData.monDmg12MM}, @@ -212,6 +216,12 @@ void monster_skill_init(void) // Sentry gSkillData.sentryHealth = 40.0f; + // Gonome + gSkillData.gonomeHealth = 85.0f; + gSkillData.gonomeDmgGuts = 10.0f; + gSkillData.gonomeDmgOneSlash = 20.0f; + gSkillData.gonomeDmgOneBite = 14.0f; + // MONSTER WEAPONS gSkillData.monDmg9MM = 5.0f; gSkillData.monDmgMP5 = 4.0f; diff --git a/src/dlls/skill.h b/src/dlls/skill.h index ba61df0..9dc1648 100644 --- a/src/dlls/skill.h +++ b/src/dlls/skill.h @@ -86,6 +86,12 @@ struct skilldata_t float miniturretHealth; float sentryHealth; +//OP4 monsters + float gonomeHealth; + float gonomeDmgGuts; + float gonomeDmgOneSlash; + float gonomeDmgOneBite; + // weapons shared by monsters float monDmg9MM; float monDmgMP5;