Compare commits

...

17 Commits
master ... 1.0

Author SHA1 Message Date
1upD
9602325bbb Shadow Walker: Added default 'custom melee weapon'. If no weapons are supplied, the Shadow Walker will spawn with a crowbar with a custom model applied. 2018-10-07 16:29:47 -04:00
1upD
77a3f1d837 Shadow Walker: Improved fgd file to be more mapper friendly. 2018-10-07 12:37:26 -04:00
1upD
f681520ef2 Lost Soul / Shadow Walker: Added new NPC source files to VPC 2018-10-07 11:55:45 -04:00
1upD
617fd48546 Shadow Walker / Lost Soul: Committing fgd file and sound scripts 2018-10-06 23:35:14 -04:00
1upD
4ac6828023 Lost Soul / Shadow Walker: Refactoring, cleanup 2018-10-06 22:34:08 -04:00
Derek Dik
b7873d0529 Shadow Walker: Refactoring. Removed found enemy sound on chase. 2018-10-04 17:11:34 -04:00
1upD
f7ad644200 Shadow Walker: Added adjustable squad slots and sound delay times 2018-10-03 22:08:55 -04:00
Derek Dik
d0f4fccefa Lost Soul: Configurable health, should play sounds from soundscript 2018-10-03 13:18:04 -04:00
1upD
d3d4bc5ccc Shadow Walker: Configurable health and sound precaching 2018-10-01 22:11:33 -04:00
Derek Dik
6b0c4554c0 Shadow Walker: Added Hammer configurable sound scripts 2018-10-01 13:09:09 -04:00
1upD
ca02f50e58 Shadow Walker: Fixed up melee attack (sort of), squad behavior, made model Hammer configurable 2018-09-30 23:54:08 -04:00
1upD
544e360640 Lost Soul: Removed all manhack sounds except for blade open / close sound. Had to make some manhack methods virtual. 2018-09-27 21:38:54 -04:00
1upD
4e56c1205b Lost Soul: Added new NPC based on manhack. Inspired by Doom 2018-09-27 17:05:14 -04:00
1upD
86bb8f7c7e Shadow Walker: Added experimental squad behavior 2018-09-27 14:29:34 -04:00
1upD
0b42a245d1 Shadow Walker: WIP combat function 2018-09-27 14:13:32 -04:00
1upD
bcaa781ad9 Added idle and alert schedule selection to npc_shadow_walker 2018-09-27 12:02:59 -04:00
1upD
06a40e6dc0 Created Shadow Walker NPC by copying experimental npc_collaborator code. WIP 2018-09-27 11:19:40 -04:00
8 changed files with 1562 additions and 6 deletions

View File

@ -0,0 +1,52 @@
//=============================================================================
//
// Purpose: Half-Life 2 mod game definition file (.fgd)
// Defines new entities for Halloween themed mods
//
//=============================================================================
@include "base.fgd"
@include "halflife2"
//-------------------------------------------------------------------------
//
// NPCs
//
//-------------------------------------------------------------------------
@NPCClass base(BaseNPC) studioprop() = npc_shadow_walker : "Shadow Walker"
[
additionalequipment(choices) : "Weapons" : "0" : "It is recommended that the Shadow Walker be equipped with a melee weapon such as the crowbar or stunstick. The shotgun and Annabelle also work surprisingly well, but firing patterns have not been set up for automatic weapons." =
[
"0" : "Custom Melee Weapon"
"weapon_crowbar" : "Crow Bar"
"weapon_stunstick" : "Stun Stick"
"weapon_shotgun" : "Shotgun"
"weapon_annabelle" : "Annabelle"
"weapon_pistol" : "Pistol"
"weapon_ar2" : "AR2"
"weapon_smg1" : "SMG1"
"weapon_alyxgun" : "Alyx Gun"
]
model(studio) : "World Model" : "models/monster/subject.mdl" : "You may specify any model for this NPC. However, the NPC was written with standard human animations in mind. Missing animations may cause errors. Be aware the animations of the model you choose will affect the NPC's behavior."
WeaponModel(studio) : "Weapon Model Override" : "models/props_canal/mattpipe.mdl" : "If the Shadow Walker is set to use 'Custom Melee Weapon', you may supply a world model to use. I would recommend using the pipe model, the crowbar model, or the stunstick model."
Health(integer) : "Health" : 75 : "Starting health of the NPC."
FearSound(sound) : "Fear Sound" : "NPC_ShadowWalker.Fear" : "The NPC will play this sound when retreating."
DeathSound(sound) : "Death Sound" : "NPC_ShadowWalker.Death" : "The NPC will play this sound on death."
IdleSound(sound) : "Idle Sound" : "NPC_ShadowWalker.Idle" : "The NPC will play this sound while wandering idly."
PainSound(sound) : "Pain Sound" : "NPC_ShadowWalker.Pain" : "The NPC will play this sound when damaged."
AlertSound(sound) : "Alert Sound" : "NPC_ShadowWalker.Alert" : "The NPC will play this sound while in an alert state."
LostEnemySound(sound) : "Lost Enemy Sound" : "NPC_ShadowWalker.LostEnemy" : "The NPC will play this sound if it loses sight of its enemy."
FoundEnemySound(sound) : "Found Enemy Sound" : "NPC_ShadowWalker.FoundEnemy" : "The NPC will play this sound if it finds an enemy again after previously losing it."
UseBothSquadSlots(choices) : "Use Both Squad Slots" : 0 : "When in a squad, how many shadow walkers should be able to chase the player at one time? Additional squadmates will run away from the player in the hopes of setting up an ambush." =
[
0 : "Use One Squad Slot"
1 : "Use Both Squad Slots"
]
]
@NPCClass base(npc_manhack) studio("models/skeleton/skeleton_torso3.mdl") = npc_lost_soul : "Lost Soul"
[
]

View File

@ -0,0 +1,126 @@
game_sounds_manifest
{
"precache_file" "scripts/game_sounds.txt"
"precache_file" "scripts/game_sounds_ui.txt"
"precache_file" "scripts/game_sounds_player.txt"
// Weapon sounds
"precache_file" "scripts/game_sounds_weapons.txt"
"precache_file" "scripts/game_sounds_weapons_ep2.txt"
// HL2 World, Ambient Generic, Items, Physics, Vehicles
"precache_file" "scripts/game_sounds_world.txt"
"precache_file" "scripts/game_sounds_ambient_generic.txt"
"precache_file" "scripts/game_sounds_items.txt"
"precache_file" "scripts/game_sounds_physics.txt"
"precache_file" "scripts/game_sounds_vehicles.txt"
// E3 level sounds
"precache_file" "scripts/level_sounds_e3_c17.txt"
"precache_file" "scripts/level_sounds_e3_town.txt"
"precache_file" "scripts/level_sounds_e3_bugbait.txt"
// Episodic content sounds
"precache_file" "scripts/npc_sounds_alyx_episodic.txt"
"precache_file" "scripts/npc_sounds_strider_episodic.txt"
"precache_file" "scripts/npc_sounds_strider_episodic2.txt"
"precache_file" "scripts/npc_sounds_turret_episodic.txt"
"precache_file" "scripts/npc_sounds_soldier_episodic.txt"
"precache_file" "scripts/npc_sounds_ministrider_episodic.txt"
"precache_file" "scripts/npc_sounds_roller_episodic.txt"
"precache_file" "scripts/npc_sounds_combine_ball_episodic.txt"
"precache_file" "scripts/npc_sounds_citizen_episodic.txt"
"precache_file" "scripts/npc_sounds_citizen_episodic2.txt"
"precache_file" "scripts/npc_sounds_citizen_ep1.txt"
"precache_file" "scripts/npc_sounds_zombine.txt"
"precache_file" "scripts/npc_sounds_dog_episodic.txt"
"precache_file" "scripts/npc_sounds_antlion_episodic.txt"
"precache_file" "scripts/npc_sounds_antlionguard_episodic.txt"
"precache_file" "scripts/npc_sounds_antlionguard_episodic2.txt"
"precache_file" "scripts/npc_sounds_advisor.txt"
"precache_file" "scripts/npc_sounds_advisor_episodic2.txt"
"precache_file" "scripts/npc_sounds_hunter.txt"
"precache_file" "scripts/npc_sounds_antlion_grub_episodic.txt"
"precache_file" "scripts/npc_sounds_attackheli_episodic2.txt"
"precache_file" "scripts/npc_sounds_fastzombie_episodic2.txt"
"precache_file" "scripts/game_sounds_addendum_ep2.txt"
"precache_file" "scripts/level_sounds_music_episodic.txt"
"precache_file" "scripts/level_sounds_music_episodic2.txt"
"precache_file" "scripts/game_sounds_weapons_episodic.txt"
"precache_file" "scripts/level_voices_episode_01.txt"
"precache_file" "scripts/level_sounds_aftermath_episodic.txt"
"precache_file" "scripts/level_sounds_outland_episodic.txt"
"precache_file" "scripts/level_sounds_c17_02a.txt"
"precache_file" "scripts/level_voices_episode_02.txt"
"precache_file" "scripts/game_sounds_vehicles_ep2.txt"
// EP2 content sounds
"precache_file" "scripts/game_sounds_physics_ep2.txt"
// Game level sounds
"precache_file" "scripts/level_sounds_eli_lab.txt"
"precache_file" "scripts/level_sounds_trainyard.txt"
"precache_file" "scripts/level_sounds_k_lab.txt"
"precache_file" "scripts/level_sounds_k_lab2.txt"
"precache_file" "scripts/level_sounds_coast.txt"
"precache_file" "scripts/level_sounds_novaprospekt.txt"
"precache_file" "scripts/level_sounds_streetwar.txt"
"precache_file" "scripts/level_sounds_streetwar2.txt"
"precache_file" "scripts/level_sounds_breencast.txt"
"precache_file" "scripts/level_sounds_citadel.txt"
"precache_file" "scripts/level_sounds_canals.txt"
"precache_file" "scripts/level_sounds_ravenholm.txt"
"precache_file" "scripts/level_sounds_ravenholm2.txt"
"precache_file" "scripts/level_sounds_canals2.txt"
//Music Tracks
"precache_file" "scripts/level_sounds_music.txt"
// NPC Sounds
"precache_file" "scripts/npc_sounds_eli.txt"
"precache_file" "scripts/npc_sounds_alyx.txt"
"precache_file" "scripts/npc_sounds_dog.txt"
"precache_file" "scripts/npc_sounds_citizen.txt"
"precache_file" "scripts/npc_sounds_barney.txt"
"precache_file" "scripts/npc_sounds_soldier.txt"
"precache_file" "scripts/npc_sounds_strider.txt"
"precache_file" "scripts/npc_sounds_zombie.txt"
"precache_file" "scripts/npc_sounds_vortigaunt.txt"
"precache_file" "scripts/npc_sounds_turret.txt"
"precache_file" "scripts/npc_sounds_scanner.txt"
"precache_file" "scripts/npc_sounds_rollermine.txt"
"precache_file" "scripts/npc_sounds_poisonzombie.txt"
"precache_file" "scripts/npc_sounds_metropolice.txt"
"precache_file" "scripts/npc_sounds_combinecamera.txt"
"precache_file" "scripts/npc_sounds_manhack.txt"
"precache_file" "scripts/npc_sounds_ichthyosaur.txt"
"precache_file" "scripts/npc_sounds_blackheadcrab.txt"
"precache_file" "scripts/npc_sounds_fastheadcrab.txt"
"precache_file" "scripts/npc_sounds_headcrab.txt"
"precache_file" "scripts/npc_sounds_fastzombie.txt"
"precache_file" "scripts/npc_sounds_birds.txt"
"precache_file" "scripts/npc_sounds_gunship.txt"
"precache_file" "scripts/npc_sounds_dropship.txt"
"precache_file" "scripts/npc_sounds_barnacle.txt"
"precache_file" "scripts/npc_sounds_attackheli.txt"
"precache_file" "scripts/npc_sounds_antlionguard.txt"
"precache_file" "scripts/npc_sounds_antlion.txt"
"precache_file" "scripts/npc_sounds_env_headcrabcanister.txt"
"precache_file" "scripts/npc_sounds_combine_ball.txt"
"precache_file" "scripts/npc_sounds_combine_mine.txt"
"precache_file" "scripts/npc_sounds_sniper.txt"
"precache_file" "scripts/npc_sounds_stalker.txt"
"precache_file" "scripts/npc_sounds_gman.txt"
"precache_file" "scripts/npc_sounds_combine_cannon.txt"
"precache_file" "scripts/npc_sounds_alyx_episodic2.txt"
"precache_file" "scripts/npc_sounds_turret_episodic2.txt"
// 1upD's Halloween NPCs
"precache_file" "scripts/npc_sounds_shadow_walker.txt"
"precache_file" "scripts/npc_sounds_lost_soul.txt"
}

View File

@ -0,0 +1,86 @@
// Default sound scripts for npc_lost_soul
// Author: 1upD
"NPC_LostSoul.Die"
{
"channel" "CHAN_BODY"
"volume" "VOL_NORM"
"pitch" "PITCH_NORM"
"soundlevel" "SNDLVL_NORM"
"wave" "physics/flesh/flesh_bloody_break.wav"
}
"NPC_LostSoul.Burn"
{
"channel" "CHAN_ITEM"
"volume" "VOL_NORM"
"pitch" "PITCH_NORM"
"soundlevel" "SNDLVL_70dB"
"rndwave"
{
"wave" "player/pl_burnpain1.wav"
"wave" "player/pl_burnpain2.wav"
"wave" "player/pl_burnpain3.wav"
}
}
"NPC_LostSoul.Float"
{
"channel" "CHAN_ITEM"
"volume" "VOL_NORM"
"pitch" "PITCH_NORM"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "ambient\levels\citadel\datatransfmalevx01.wav"
"wave" "ambient\levels\citadel\datatransmalevx01.wav"
"wave" "ambient\levels\citadel\datatransmalevx02.wav"
"wave" "ambient\levels\citadel\datatransmalevx02.wav"
"wave" "ambient\levels\citadel\datatransrandom03.wav"
}
}
"NPC_LostSoul.ChargeAnnounce"
{
"channel" "CHAN_WEAPON"
"volume" "VOL_NORM"
"pitch" "120, 125"
"soundlevel" "SNDLVL_70dB"
"rndwave"
{
"wave" "npc/zombie_poison/pz_alert1.wav"
"wave" "npc/zombie_poison/pz_alert1.wav"
}
}
"NPC_LostSoul.ChargeEnd"
{
"channel" "CHAN_WEAPON"
"volume" "VOL_NORM"
"pitch" "120, 125"
"soundlevel" "SNDLVL_70dB"
"rndwave"
{
"wave" "npc/zombie_poison/pz_warn1.wav"
"wave" "npc/zombie_poison/pz_warn1.wav"
}
}
"NPC_LostSoul.Stunned"
{
"channel" "CHAN_VOICE"
"volume" "0.80"
"pitch" "145,150"
"soundlevel" "SNDLVL_70dB"
"wave" "npc/scanner/scanner_siren2.wav"
}

View File

@ -0,0 +1,200 @@
// Default sound scripts for npc_shadow_walker
// Author: 1upD
"NPC_ShadowWalker.Pain"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "65, 70"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "npc\stalker\stalker_pain1.wav"
"wave" "npc\stalker\stalker_pain2.wav"
"wave" "npc\stalker\stalker_pain3.wav"
}
}
"NPC_ShadowWalker.Idle"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "80, 90"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "npc\stalker\stalker_scream1.wav"
"wave" "npc\stalker\stalker_scream2.wav"
"wave" "npc\stalker\stalker_scream3.wav"
"wave" "npc\stalker\stalker_scream4.wav"
}
}
"NPC_ShadowWalker.Vocal.Idle"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "62, 62"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "vo\episode_1\citadel\al_stalk_pleasestopscreaming01.wav"
"wave" "vo\episode_1\citadel\al_stalk_pleasestopscreaming02.wav"
"wave" "vo\episode_1\citadel\al_stalk_pleasestopscreaming03.wav"
"wave" "vo\episode_1\citadel\al_stalk_pleasestopscreaming04.wav"
"wave" "vo\episode_1\citadel\al_stalk_pleasestopscreaming05.wav"
"wave" "vo\k_lab2\al_optimism.wav"
"wave" "vo\episode_1\c17\al_elev_whereselev.wav"
"wave" "vo\episode_1\intro\al_wheredoeshethink.wav"
"wave" "vo\outland_02\griggs_everywhere.wav"
"wave" "vo\outland_11a\silo\kl_silo_wheredata01.wav"
"wave" "vo\episode_1\intro\al_cantbelieveit.wav"
"wave" "vo\episode_1\npc\alyx\al_deaf_canthearanything.wav"
"wave" "vo\episode_1\npc\alyx\al_deaf_canthearyou.wav"
"wave" "vo\k_lab\ba_cantlook.wav"
}
}
"NPC_ShadowWalker.Fear"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "62, 62"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
// Replace these
"wave" "npc\stalker\stalker_die2.wav"
"wave" "npc\stalker\stalker_pain1.wav"
"wave" "npc\stalker\stalker_pain2.wav"
"wave" "npc\stalker\stalker_pain3.wav"
}
}
"NPC_ShadowWalker.Alert"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "80, 90"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "npc\stalker\stalker_alert1b.wav"
"wave" "npc\stalker\stalker_alert2b.wav"
"wave" "npc\stalker\stalker_alert3b.wav"
}
}
"NPC_ShadowWalker.Vocal.Alert"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "62, 62"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "vo\citadel\al_bitofit.wav"
"wave" "vo\outland_01\intro\al_rbed_notalone.wav"
}
}
"NPC_ShadowWalker.LostEnemy"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "80, 90"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "npc\zombie_poison\pz_alert2.wav"
"wave" "npc\stalker\stalker_die2.wav"
"wave" "npc\stalker\stalker_scream4.wav"
}
}
"NPC_ShadowWalker.Vocal.LostEnemy"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "62, 62"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "vo\citadel\al_notagain02.wav"
"wave" "vo\episode_1\npc\alyx\al_light_lost05.wav"
"wave" "vo\episode_1\npc\alyx\al_light_lost11.wav"
"wave" "vo\citadel\al_wonderwhere.wav"
"wave" "vo\outland_12a\launch\al_launch_damnit.wav"
"wave" "vo\streetwar\alyx_gate\al_no.wav"
"wave" "vo\k_lab\kl_thenwhere.wav"
}
}
"NPC_ShadowWalker.FoundEnemy"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "75, 85"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "npc\stalker\go_alert2.wav"
"wave" "npc\stalker\go_alert2a.wav"
}
}
"NPC_ShadowWalker.Vocal.FoundEnemy"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "62, 62"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "npc\stalker\stalker_scream1.wav"
"wave" "vo\citadel\al_thatshim.wav"
"wave" "vo\citadel\al_thereheis.wav"
"wave" "vo\episode_1\npc\alyx\al_rejoin02.wav"
"wave" "vo\episode_1\npc\alyx\al_seemanyfoe03.wav"
"wave" "vo\episode_1\npc\alyx\al_zombie_liveone02.wav"
}
}
"NPC_ShadowWalker.Death"
{
"channel" "CHAN_VOICE"
"volume" "0.95"
"pitch" "60, 65"
"soundlevel" "SNDLVL_NORM"
"rndwave"
{
"wave" "ambient\creatures\town_child_scream1.wav"
}
}

View File

@ -102,9 +102,9 @@ public:
virtual float GetHeadTurnRate( void ) { return 45.0f; } // Degrees per second virtual float GetHeadTurnRate( void ) { return 45.0f; } // Degrees per second
void CheckCollisions(float flInterval); virtual void CheckCollisions(float flInterval);
virtual void GatherEnemyConditions( CBaseEntity *pEnemy ); virtual void GatherEnemyConditions( CBaseEntity *pEnemy );
void PlayFlySound(void); virtual void PlayFlySound(void);
virtual void StopLoopingSounds(void); virtual void StopLoopingSounds(void);
void Precache(void); void Precache(void);
@ -113,9 +113,9 @@ public:
void Activate(); void Activate();
void StartTask( const Task_t *pTask ); void StartTask( const Task_t *pTask );
void BladesInit(); virtual void BladesInit();
void SoundInit( void ); virtual void SoundInit( void );
void StartEye( void ); virtual void StartEye( void );
bool HandleInteraction(int interactionType, void* data, CBaseCombatCharacter* sourceEnt); bool HandleInteraction(int interactionType, void* data, CBaseCombatCharacter* sourceEnt);
@ -126,7 +126,7 @@ public:
void SpinBlades(float flInterval); void SpinBlades(float flInterval);
void Slice( CBaseEntity *pHitEntity, float flInterval, trace_t &tr ); virtual void Slice( CBaseEntity *pHitEntity, float flInterval, trace_t &tr );
void Bump( CBaseEntity *pHitEntity, float flInterval, trace_t &tr ); void Bump( CBaseEntity *pHitEntity, float flInterval, trace_t &tr );
void Splash( const Vector &vecSplashPos ); void Splash( const Vector &vecSplashPos );

View File

@ -0,0 +1,517 @@
//=//=============================================================================//
//
// Purpose: A frightening flying creature with the appearance of a burning skull.
// May be familiar to first person shooter fans.
//
// npc_lost_soul is a modified version of a manhack inspired by Mallikas'
// HalloweenVilleFour entry, Satanophobia, as well as Doom.
//
// Author: 1upD
//
//=============================================================================//
#include "cbase.h"
#include "soundenvelope.h"
#include "npc_manhack.h"
#include "ai_default.h"
#include "ai_node.h"
#include "ai_navigator.h"
#include "ai_pathfinder.h"
#include "ai_moveprobe.h"
#include "ai_memory.h"
#include "ai_squad.h"
#include "ai_route.h"
#include "explode.h"
#include "basegrenade_shared.h"
#include "ndebugoverlay.h"
#include "decals.h"
#include "gib.h"
#include "game.h"
#include "ai_interactions.h"
#include "IEffects.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "movevars_shared.h"
#include "npcevent.h"
#include "props.h"
#include "te_effect_dispatch.h"
#include "ai_squadslot.h"
#include "world.h"
#include "smoke_trail.h"
#include "func_break.h"
#include "physics_impact_damage.h"
#include "weapon_physcannon.h"
#include "physics_prop_ragdoll.h"
#include "soundent.h"
#include "ammodef.h"
#include "EntityFlame.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar sk_lostsoul_health("sk_lostsoul_health", "0");
ConVar sk_lostsoul_melee_dmg("sk_lostsoul_melee_dmg", "0");
#define MANHACK_NOISEMOD_HIDE 5000
//=========================================================
//=========================================================
class CNPC_LostSoul : public CNPC_Manhack
{
DECLARE_CLASS( CNPC_LostSoul, CNPC_Manhack);
public:
void Precache( void );
void Spawn( void );
Class_T Classify( void );
virtual void DeathSound(const CTakeDamageInfo &info);
virtual bool ShouldGib(const CTakeDamageInfo &info);
void BladesInit();
void SoundInit(void);
void PlayFlySound(void);
void StartEye(void);
void CheckCollisions(float flInterval);
void Slice(CBaseEntity *pHitEntity, float flInterval, trace_t &tr);
virtual int OnTakeDamage_Alive(const CTakeDamageInfo &info);
void Ignite(float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner) { return; }
// INPCInteractive Functions
virtual bool CanInteractWith(CAI_BaseNPC *pUser) { return false; } // Disabled for now (sjb)
virtual bool HasBeenInteractedWith() { return false; }
virtual void NotifyInteraction(CAI_BaseNPC *pUser){}
virtual void InputPowerdown(inputdata_t &inputdata)
{
m_iHealth = 0;
}
void MoveToTarget(float flInterval, const Vector &MoveTarget);
void ShowHostile(bool hostile = true);
DECLARE_DATADESC();
DEFINE_CUSTOM_AI;
private:
float m_flNextEngineSoundTime;
};
LINK_ENTITY_TO_CLASS( npc_lost_soul, CNPC_LostSoul );
IMPLEMENT_CUSTOM_AI( npc_manhack,CNPC_LostSoul );
//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CNPC_LostSoul )
DEFINE_KEYFIELD(m_iHealth, FIELD_INTEGER, "Health"),
DEFINE_FIELD(m_flNextEngineSoundTime, FIELD_TIME)
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Initialize the custom schedules
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_LostSoul::InitCustomSchedules(void)
{
INIT_CUSTOM_AI(CNPC_LostSoul);
}
//-----------------------------------------------------------------------------
// Purpose:
//
//
//-----------------------------------------------------------------------------
void CNPC_LostSoul::Precache( void )
{
PrecacheModel( "models/skeleton/skeleton_torso3.mdl" ); // Replace this with setting from Hammer
PrecacheScriptSound("NPC_LostSoul.Die");
PrecacheScriptSound("NPC_LostSoul.Burn");
PrecacheScriptSound("NPC_LostSoul.Float");
PrecacheScriptSound("NPC_LostSoul.ChargeAnnounce");
PrecacheScriptSound("NPC_LostSoul.ChargeEnd");
PrecacheScriptSound("NPC_LostSoul.Stunned");
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
//
//
//-----------------------------------------------------------------------------
void CNPC_LostSoul::Spawn( void )
{
BaseClass::Spawn();
Precache();
SetModel("models/skeleton/skeleton_torso3.mdl");
SetHullType(HULL_TINY_CENTERED); // There was an error being thrown about collision model being smaller than nav hull - need to look into
SetHullSizeNormal();
SetSolid(SOLID_BBOX);
AddSolidFlags(FSOLID_NOT_STANDABLE);
if (HasSpawnFlags(SF_MANHACK_CARRIED))
{
AddSolidFlags(FSOLID_NOT_SOLID);
SetMoveType(MOVETYPE_NONE);
}
else
{
SetMoveType(MOVETYPE_VPHYSICS);
}
// Use the convar if health is less than 1
if (m_iHealth < 1)
{
m_iHealth = sk_lostsoul_health.GetFloat();
}
SetViewOffset(Vector(0, 0, 10)); // Position of the eyes relative to NPC's origin.
m_flFieldOfView = VIEW_FIELD_FULL;
m_NPCState = NPC_STATE_NONE;
if (m_spawnflags & SF_MANHACK_USE_AIR_NODES)
{
SetNavType(NAV_FLY);
}
else
{
SetNavType(NAV_GROUND);
}
AddEFlags(EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL);
AddEffects(EF_NOSHADOW);
SetBloodColor(BLOOD_COLOR_RED);
SetCurrentVelocity(vec3_origin);
CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_MOVE_FLY | bits_CAP_SQUAD);
// Set the noise mod to huge numbers right now, in case this manhack starts out waiting for a script
// for instance, we don't want him to bob whilst he's waiting for a script. This allows designers
// to 'hide' manhacks in small places. (sjb)
SetNoiseMod(MANHACK_NOISEMOD_HIDE, MANHACK_NOISEMOD_HIDE, MANHACK_NOISEMOD_HIDE);
m_fHeadYaw = 0;
NPCInit();
// Manhacks are designed to slam into things, so don't take much damage from it!
SetImpactEnergyScale(0.001);
// Manhacks get 30 seconds worth of free knowledge.
GetEnemies()->SetFreeKnowledgeDuration(30.0);
// don't be an NPC, we want to collide with debris stuff
SetCollisionGroup(COLLISION_GROUP_NONE);
CEntityFlame *pFlame = CEntityFlame::Create(this);
if (pFlame)
{
pFlame->SetLifetime(HUGE_VAL);
SetEffectEntity(pFlame);
pFlame->SetSize(8);
pFlame->SetDamage(0.0f);
}
m_flNextEngineSoundTime = gpGlobals->curtime;
}
int CNPC_LostSoul::OnTakeDamage_Alive(const CTakeDamageInfo &info)
{
// Don't take burning damage!
if (info.GetDamageType() & 8) {
return 0;
}
return BaseClass::OnTakeDamage_Alive(info);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_LostSoul::DeathSound(const CTakeDamageInfo &info)
{
CPASAttenuationFilter filter2(this, "NPC_LostSoul.Die");
EmitSound(filter2, entindex(), "NPC_LostSoul.Die");
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_LostSoul::ShouldGib(const CTakeDamageInfo &info)
{
// TODO: Add gibs to lost soul
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Overloads Manhack method. Lost Souls have no blades
//-----------------------------------------------------------------------------
void CNPC_LostSoul::BladesInit()
{
SetActivity(ACT_FLY);
}
//-----------------------------------------------------------------------------
// Purpose: Overloads manhack engine sound.
//-----------------------------------------------------------------------------
void CNPC_LostSoul::SoundInit(void)
{
// Just don't even worry about it.
}
void CNPC_LostSoul::PlayFlySound(void)
{
float flEnemyDist;
if (GetEnemy())
{
flEnemyDist = (GetAbsOrigin() - GetEnemy()->GetAbsOrigin()).Length();
}
else
{
flEnemyDist = FLT_MAX;
}
// Play special engine every once in a while
if (gpGlobals->curtime > m_flNextEngineSoundTime && flEnemyDist < 48)
{
m_flNextEngineSoundTime = gpGlobals->curtime + random->RandomFloat(0.5, 2.0);
EmitSound("NPC_LostSoul.Float");
}
}
void CNPC_LostSoul::StartEye(void)
{
// No eyes
}
//-----------------------------------------------------------------------------
// Purpose: We've touched something that we can hurt. Slice it!
// Input :
// Output :
//-----------------------------------------------------------------------------
#define MANHACK_SMASH_TIME 0.35 // How long after being thrown from a physcannon that a manhack is eligible to die from impact
void CNPC_LostSoul::Slice(CBaseEntity *pHitEntity, float flInterval, trace_t &tr)
{
// Don't hurt the player if I'm in water
if (GetWaterLevel() > 0 && pHitEntity->IsPlayer())
return;
if (pHitEntity->m_takedamage == DAMAGE_NO)
return;
// Damage must be scaled by flInterval so framerate independent
float flDamage = sk_lostsoul_melee_dmg.GetFloat() * flInterval;
if (pHitEntity->IsPlayer())
{
flDamage *= 1.0f;
}
if (dynamic_cast<CBreakableProp*>(pHitEntity)) {
dynamic_cast<CBreakableProp*>(pHitEntity)->Ignite(10.0f, false);
}
else if (pHitEntity->IsNPC())
{
dynamic_cast<CAI_BaseNPC*>(pHitEntity)->Ignite(2.0f);
}
if (flDamage < 1.0f)
{
flDamage = 1.0f;
}
CTakeDamageInfo info(this, this, flDamage, DMG_SLASH);
// check for actual "ownership" of damage
CBasePlayer *pPlayer = HasPhysicsAttacker(MANHACK_SMASH_TIME);
if (pPlayer)
{
info.SetAttacker(pPlayer);
}
Vector dir = (tr.endpos - tr.startpos);
if (dir == vec3_origin)
{
dir = tr.m_pEnt->GetAbsOrigin() - GetAbsOrigin();
}
CalculateMeleeDamageForce(&info, dir, tr.endpos);
pHitEntity->TakeDamage(info);
// Play burning sound
EmitSound("NPC_LostSoul.Burn");
// Pop back a little bit after hitting the player
// ComputeSliceBounceVelocity(pHitEntity, tr);
// Save off when we last hit something
m_flLastDamageTime = gpGlobals->curtime;
// Reset our state and give the player time to react
// StopBurst(true);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_LostSoul::MoveToTarget(float flInterval, const Vector &vMoveTarget)
{
if (flInterval <= 0)
{
return;
}
// -----------------------------------------
// Don't steer if engine's have stalled
// -----------------------------------------
if (m_iHealth <= 0)
return;
if (GetEnemy() != NULL)
{
TurnHeadToTarget(flInterval, GetEnemy()->EyePosition());
}
else
{
TurnHeadToTarget(flInterval, vMoveTarget);
}
// -------------------------------------
// Move towards our target
// -------------------------------------
float myAccel;
float myZAccel = 300.0f;
float myDecay = 0.3f;
Vector targetDir;
float flDist;
Vector vecCurrentDir = GetCurrentVelocity();
VectorNormalize(vecCurrentDir);
targetDir = vMoveTarget - GetAbsOrigin();
flDist = VectorNormalize(targetDir);
float flDot = DotProduct(targetDir, vecCurrentDir);
// Otherwise we should steer towards our goal
if (flDot > 0.25)
{
// If my target is in front of me, my flight model is a bit more accurate.
myAccel = 300;
}
else
{
// Have a harder time correcting my course if I'm currently flying away from my target.
myAccel = 200;
}
// Clamp lateral acceleration
if (myAccel > (flDist / flInterval))
{
myAccel = flDist / flInterval;
}
// Clamp vertical movement
if (myZAccel > flDist / flInterval)
{
myZAccel = flDist / flInterval;
}
MoveInDirection(flInterval, targetDir, myAccel, myZAccel, myDecay);
// calc relative banking targets
Vector forward, right;
GetVectors(&forward, &right, NULL);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_LostSoul::CheckCollisions(float flInterval)
{
// Trace forward to see if I hit anything. But trace forward along the
// owner's view direction if you're being carried.
Vector vecTraceDir, vecCheckPos;
VPhysicsGetObject()->GetVelocity(&vecTraceDir, NULL);
vecTraceDir *= flInterval;
VectorAdd(GetAbsOrigin(), vecTraceDir, vecCheckPos);
trace_t tr;
CBaseEntity* pHitEntity = NULL;
AI_TraceHull(GetAbsOrigin(),
vecCheckPos,
GetHullMins(),
GetHullMaxs(),
MoveCollisionMask(),
this,
COLLISION_GROUP_NONE,
&tr);
if ((tr.fraction != 1.0 || tr.startsolid) && tr.m_pEnt)
{
PhysicsMarkEntitiesAsTouching(tr.m_pEnt, tr);
pHitEntity = tr.m_pEnt;
if (pHitEntity != NULL &&
pHitEntity->m_takedamage == DAMAGE_YES &&
pHitEntity->Classify() != CLASS_MANHACK)
{
// Slice this thing
Slice(pHitEntity, flInterval, tr);
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : hostile -
//-----------------------------------------------------------------------------
void CNPC_LostSoul::ShowHostile(bool hostile /*= true*/)
{
if (hostile)
{
EmitSound("NPC_LostSoul.ChargeAnnounce");
}
else
{
EmitSound("NPC_LostSoul.ChargeEnd");
}
}
//-----------------------------------------------------------------------------
// Purpose:
//
//
// Output :
//-----------------------------------------------------------------------------
Class_T CNPC_LostSoul::Classify( void )
{
return CLASS_HEADCRAB;
}

View File

@ -0,0 +1,570 @@
//=//=============================================================================//
//
// Purpose: A malevolent being from a parallel universe which at one point
// may have been human.
//
// npc_shadow_walker is designed to be reusable as a generic horror
// game style npc. Its model and sound files may be configured through
// the hammer editor using keyfields.
//
// Author: 1upD
//
//=============================================================================//
#include "cbase.h"
#include "ai_default.h"
#include "ai_task.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "soundent.h"
#include "game.h"
#include "npcevent.h"
#include "entitylist.h"
#include "activitylist.h"
#include "ai_basenpc.h"
#include "engine/IEngineSound.h"
#include "basehlcombatweapon_shared.h"
#include "ai_squadslot.h"
#include "weapon_crowbar.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//=========================================================
//=========================================================
class CNPC_ShadowWalker : public CAI_BaseNPC
{
DECLARE_CLASS( CNPC_ShadowWalker, CAI_BaseNPC );
public:
void Precache( void );
void Spawn( void );
Class_T Classify( void );
virtual int SelectFailSchedule(int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode);
virtual int SelectScheduleRetrieveItem();
virtual int SelectSchedule();
virtual int SelectIdleSchedule();
virtual int SelectAlertSchedule();
virtual int SelectCombatSchedule();
virtual bool CanPickkUpWeapons() { return true; }
Activity NPC_TranslateActivity(Activity eNewActivity);
// Sounds
virtual void PlaySound(string_t soundname, bool optional);
virtual void DeathSound(const CTakeDamageInfo &info) { PlaySound(m_iszDeathSound, true); }
virtual void AlertSound(void) { PlaySound(m_iszAlertSound, false); };
virtual void IdleSound(void) { PlaySound(m_iszIdleSound, false); };
virtual void PainSound(const CTakeDamageInfo &info) { PlaySound(m_iszPainSound, true); };
virtual void FearSound(void) { PlaySound(m_iszFearSound, false); };
virtual void LostEnemySound(void) { PlaySound(m_iszLostEnemySound, false); };
virtual void FoundEnemySound(void) { PlaySound(m_iszFoundEnemySound, false); };
void Activate();
void FixupWeapon();
DECLARE_DATADESC();
string_t m_iszWeaponModelName; // Path/filename of model to override weapon model.
string_t m_iszFearSound; // Path/filename of WAV file to play.
string_t m_iszDeathSound; // Path/filename of WAV file to play.
string_t m_iszIdleSound; // Path/filename of WAV file to play.
string_t m_iszPainSound; // Path/filename of WAV file to play.
string_t m_iszAlertSound; // Path/filename of WAV file to play.
string_t m_iszLostEnemySound; // Path/filename of WAV file to play.
string_t m_iszFoundEnemySound; // Path/filename of WAV file to play.
DEFINE_CUSTOM_AI;
private:
bool HasRangedWeapon();
void PrecacheNPCSoundScript(string_t* SoundName, string_t defaultSoundName);
bool m_bUseBothSquadSlots; // If true use two squad slots, if false use one squad slot
bool m_bWanderToggle; // Boolean to toggle wandering / standing every think cycle
float m_flNextSoundTime; // Next time at which this NPC is allowed to play an NPC sound
};
LINK_ENTITY_TO_CLASS( npc_shadow_walker, CNPC_ShadowWalker );
IMPLEMENT_CUSTOM_AI( npc_citizen,CNPC_ShadowWalker );
//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC(CNPC_ShadowWalker)
DEFINE_KEYFIELD(m_iszWeaponModelName, FIELD_STRING, "WeaponModel"),
DEFINE_KEYFIELD(m_iHealth, FIELD_INTEGER, "Health"),
DEFINE_KEYFIELD(m_iszFearSound, FIELD_SOUNDNAME, "FearSound"),
DEFINE_KEYFIELD(m_iszDeathSound, FIELD_SOUNDNAME, "DeathSound"),
DEFINE_KEYFIELD(m_iszIdleSound, FIELD_SOUNDNAME, "IdleSound"),
DEFINE_KEYFIELD(m_iszPainSound, FIELD_SOUNDNAME, "PainSound"),
DEFINE_KEYFIELD(m_iszAlertSound, FIELD_SOUNDNAME, "AlertSound"),
DEFINE_KEYFIELD(m_iszLostEnemySound, FIELD_SOUNDNAME, "LostEnemySound"),
DEFINE_KEYFIELD(m_iszFoundEnemySound, FIELD_SOUNDNAME, "FoundEnemySound"),
DEFINE_KEYFIELD(m_bUseBothSquadSlots, FIELD_BOOLEAN, "UseBothSquadSlots"),
DEFINE_FIELD(m_bWanderToggle, FIELD_BOOLEAN),
DEFINE_FIELD(m_flNextSoundTime, FIELD_TIME)
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Initialize the custom schedules
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::InitCustomSchedules(void)
{
INIT_CUSTOM_AI(CNPC_ShadowWalker);
}
//-----------------------------------------------------------------------------
// Purpose: Inner class for default weapon
// TODO: Merge this with the Matt weapon in npc_citizen
//-----------------------------------------------------------------------------
class CWeaponCustomMelee : public CWeaponCrowbar
{
DECLARE_CLASS(CWeaponCustomMelee, CWeaponCrowbar);
const char *GetWorldModel() const { return GetModelName().ToCStr(); }
void SetPickupTouch(void) { /* do nothing */ }
};
//-----------------------------------------------------------------------------
// Purpose:
//
//
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::Precache( void )
{
// If no model name is supplied, use the default Shadow Walker model
if (!GetModelName())
{
SetModelName(MAKE_STRING("models/monster/subject.mdl"));
}
if (&m_iszWeaponModelName && m_iszWeaponModelName != MAKE_STRING("")) {
PrecacheModel(STRING(m_iszWeaponModelName));
}
PrecacheModel(STRING(GetModelName()));
PrecacheNPCSoundScript(&m_iszFearSound, MAKE_STRING("NPC_ShadowWalker.Fear"));
PrecacheNPCSoundScript(&m_iszIdleSound, MAKE_STRING("NPC_ShadowWalker.Idle"));
PrecacheNPCSoundScript(&m_iszAlertSound, MAKE_STRING("NPC_ShadowWalker.Alert"));
PrecacheNPCSoundScript(&m_iszPainSound, MAKE_STRING("NPC_ShadowWalker.Pain"));
PrecacheNPCSoundScript(&m_iszLostEnemySound, MAKE_STRING("NPC_ShadowWalker.LostEnemy"));
PrecacheNPCSoundScript(&m_iszFoundEnemySound, MAKE_STRING("NPC_ShadowWalker.FoundEnemy"));
PrecacheNPCSoundScript(&m_iszDeathSound, MAKE_STRING("NPC_ShadowWalker.Death"));
m_bWanderToggle = false;
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
//
//
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::Spawn( void )
{
Precache();
SetModel(STRING(GetModelName()));
SetHullType(HULL_HUMAN);
SetHullSizeNormal();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_STEP );
SetBloodColor( BLOOD_COLOR_RED );
// If the health has not been set through Hammer, use a default health value of 50
if (m_iHealth < 1)
{
m_iHealth = 50;
}
m_flFieldOfView = 0.5;
m_flNextSoundTime = gpGlobals->curtime;
m_NPCState = NPC_STATE_NONE;
CapabilitiesClear();
if (!HasSpawnFlags(SF_NPC_START_EFFICIENT))
{
CapabilitiesAdd(bits_CAP_TURN_HEAD);
CapabilitiesAdd(bits_CAP_SQUAD);
CapabilitiesAdd(bits_CAP_USE_WEAPONS | bits_CAP_AIM_GUN | bits_CAP_MOVE_SHOOT);
CapabilitiesAdd(bits_CAP_WEAPON_MELEE_ATTACK1 || bits_CAP_WEAPON_MELEE_ATTACK2);
CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1 || bits_CAP_INNATE_MELEE_ATTACK2);
CapabilitiesAdd(bits_CAP_DUCK | bits_CAP_DOORS_GROUP);
CapabilitiesAdd(bits_CAP_USE_SHOT_REGULATOR);
}
CapabilitiesAdd(bits_CAP_MOVE_GROUND);
SetMoveType(MOVETYPE_STEP);
NPCInit();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::FixupWeapon()
{
// If no weapons supplied, give a crowbar
CBaseCombatWeapon *pWeapon = GetActiveWeapon();
if (pWeapon == NULL) {
pWeapon = (CBaseCombatWeapon *)CREATE_UNSAVED_ENTITY(CWeaponCustomMelee, "weapon_crowbar");
// Apply weapon model override
if (&m_iszWeaponModelName && m_iszWeaponModelName != MAKE_STRING("")) {
pWeapon->SetModel(STRING(m_iszWeaponModelName));
}
else {
pWeapon->SetModel("models/props_canal/mattpipe.mdl");
}
DispatchSpawn(pWeapon);
Weapon_Equip(pWeapon);
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::Activate()
{
BaseClass::Activate();
FixupWeapon();
}
//-----------------------------------------------------------------------------
// Purpose: Choose a schedule after schedule failed
//-----------------------------------------------------------------------------
int CNPC_ShadowWalker::SelectFailSchedule(int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode)
{
switch (failedSchedule)
{
case SCHED_NEW_WEAPON:
// If failed trying to pick up a weapon, try again in one second. This is because other AI code
// has put this off for 10 seconds under the assumption that the citizen would be able to
// pick up the weapon that they found.
m_flNextWeaponSearchTime = gpGlobals->curtime + 1.0f;
break;
case SCHED_TAKE_COVER_FROM_ENEMY:
// I can't take cover, so I need to run away!
return SCHED_RUN_FROM_ENEMY;
case SCHED_CHASE_ENEMY:
// I can't run towards the enemy, so I will just run randomly!
return SCHED_CHASE_ENEMY_FAILED;
case SCHED_RUN_FROM_ENEMY:
// I can't run away, so I will just run randomly!
return SCHED_RUN_RANDOM;
}
return BaseClass::SelectFailSchedule(failedSchedule, failedTask, taskFailCode);
}
//-----------------------------------------------------------------------------
// Purpose: Select a schedule to retrieve better weapons if they are available.
//-----------------------------------------------------------------------------
int CNPC_ShadowWalker::SelectScheduleRetrieveItem()
{
if (HasCondition(COND_BETTER_WEAPON_AVAILABLE))
{
CBaseHLCombatWeapon *pWeapon = dynamic_cast<CBaseHLCombatWeapon *>(Weapon_FindUsable(WEAPON_SEARCH_DELTA));
if (pWeapon)
{
m_flNextWeaponSearchTime = gpGlobals->curtime + 10.0;
// Now lock the weapon for several seconds while we go to pick it up.
pWeapon->Lock(10.0, this);
SetTarget(pWeapon);
return SCHED_NEW_WEAPON;
}
}
return SCHED_NONE;
}
//-----------------------------------------------------------------------------
// Purpose: Select a schedule to execute based on conditions.
// This is the most critical AI method.
//-----------------------------------------------------------------------------
int CNPC_ShadowWalker::SelectSchedule()
{
switch (m_NPCState)
{
case NPC_STATE_IDLE:
AssertMsgOnce(GetEnemy() == NULL, "NPC has enemy but is not in combat state?");
return SelectIdleSchedule();
case NPC_STATE_ALERT:
AssertMsgOnce(GetEnemy() == NULL, "NPC has enemy but is not in combat state?");
return SelectAlertSchedule();
case NPC_STATE_COMBAT:
return SelectCombatSchedule();
default:
return BaseClass::SelectSchedule();
}
}
//-----------------------------------------------------------------------------
// Idle schedule selection
//-----------------------------------------------------------------------------
int CNPC_ShadowWalker::SelectIdleSchedule()
{
int nSched = SelectFlinchSchedule();
if (nSched != SCHED_NONE)
return nSched;
if (HasCondition(COND_HEAR_DANGER) ||
HasCondition(COND_HEAR_COMBAT) ||
HasCondition(COND_HEAR_WORLD) ||
HasCondition(COND_HEAR_BULLET_IMPACT) ||
HasCondition(COND_HEAR_PLAYER))
{
// Investigate sound source
return SCHED_INVESTIGATE_SOUND;
}
if (CanPickkUpWeapons() && HasCondition(COND_BETTER_WEAPON_AVAILABLE)) {
nSched = SelectScheduleRetrieveItem();
if (nSched != SCHED_NONE)
return nSched;
}
// no valid route! Wander instead
if (GetNavigator()->GetGoalType() == GOALTYPE_NONE) {
m_bWanderToggle = !m_bWanderToggle;
if (m_bWanderToggle) {
return SCHED_IDLE_WANDER;
}
else {
return SCHED_IDLE_STAND;
}
}
// valid route. Get moving
return SCHED_IDLE_WALK;
}
//-----------------------------------------------------------------------------
// Alert schedule selection
//-----------------------------------------------------------------------------
int CNPC_ShadowWalker::SelectAlertSchedule()
{
// Per default base NPC, check flinch schedule first
int nSched = SelectFlinchSchedule();
if (nSched != SCHED_NONE)
return nSched;
// Scan around for new enemies
if (HasCondition(COND_ENEMY_DEAD) && SelectWeightedSequence(ACT_VICTORY_DANCE) != ACTIVITY_NOT_AVAILABLE)
return SCHED_ALERT_SCAN;
if (HasCondition(COND_HEAR_DANGER) ||
HasCondition(COND_HEAR_PLAYER) ||
HasCondition(COND_HEAR_WORLD) ||
HasCondition(COND_HEAR_BULLET_IMPACT) ||
HasCondition(COND_HEAR_COMBAT))
{
// Investigate sound source
AlertSound();
return SCHED_INVESTIGATE_SOUND;
}
if (CanPickkUpWeapons() && HasCondition(COND_BETTER_WEAPON_AVAILABLE)) {
nSched = SelectScheduleRetrieveItem();
if (nSched != SCHED_NONE)
return nSched;
}
// no valid route! Wander instead
if (GetNavigator()->GetGoalType() == GOALTYPE_NONE) {
m_bWanderToggle = !m_bWanderToggle;
if (m_bWanderToggle) {
return SCHED_IDLE_WANDER;
}
else {
return SCHED_ALERT_STAND;
}
}
// valid route. Get moving
return SCHED_ALERT_WALK;
}
//-----------------------------------------------------------------------------
// Combat schedule selection
//-----------------------------------------------------------------------------
int CNPC_ShadowWalker::SelectCombatSchedule()
{
// Check flinch first
int nSched = SelectFlinchSchedule();
if (nSched != SCHED_NONE)
return nSched;
// Check enemy death
if (HasCondition(COND_ENEMY_DEAD))
{
// clear the current (dead) enemy and try to find another.
SetEnemy(NULL);
if (ChooseEnemy())
{
FoundEnemySound();
ClearCondition(COND_ENEMY_DEAD);
return SelectSchedule();
}
SetState(NPC_STATE_ALERT);
return SelectSchedule();
}
// If in a squad, only one or two shadow walkers can chase the player. This is configurable through Hammer.
bool bCanChase = false;
if (m_bUseBothSquadSlots) {
bCanChase = OccupyStrategySlotRange(SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2);
}
else {
bCanChase = OccupyStrategySlot(SQUAD_SLOT_ATTACK1);
}
// If I'm scared of this enemy and he's looking at me, run away
if ((IRelationType(GetEnemy()) == D_FR || !bCanChase) && (HasCondition(COND_SEE_ENEMY) && HasCondition(COND_ENEMY_FACING_ME) && HasCondition(COND_HAVE_ENEMY_LOS)))
{
// TODO: Check if silent
FearSound();
return SCHED_RUN_FROM_ENEMY;
}
// Reloading conditions are necessary just in case for some reason somebody gives the Shadow Walker a gun
if (HasCondition(COND_LOW_PRIMARY_AMMO) || HasCondition(COND_NO_PRIMARY_AMMO))
{
return SCHED_HIDE_AND_RELOAD;
}
// Can we see the enemy?
if (!HasCondition(COND_SEE_ENEMY))
{
// Chase!
return SCHED_CHASE_ENEMY;
}
if (HasCondition(COND_TOO_CLOSE_TO_ATTACK))
return SCHED_BACK_AWAY_FROM_ENEMY;
// we can see the enemy
if (HasCondition(COND_CAN_MELEE_ATTACK1)) {
return SCHED_MELEE_ATTACK1;
}
if (HasCondition(COND_CAN_MELEE_ATTACK2)) {
return SCHED_MELEE_ATTACK2;
}
if (HasRangedWeapon() && GetShotRegulator()->IsInRestInterval())
{
if (HasCondition(COND_CAN_RANGE_ATTACK1))
return SCHED_COMBAT_FACE;
}
if (HasRangedWeapon() && HasCondition(COND_CAN_RANGE_ATTACK1))
{
if (OccupyStrategySlotRange(SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2))
return SCHED_RANGE_ATTACK1;
return SCHED_RUN_FROM_ENEMY;
}
if (HasRangedWeapon() && HasCondition(COND_CAN_RANGE_ATTACK2))
return SCHED_RANGE_ATTACK2;
if (HasCondition(COND_NOT_FACING_ATTACK))
return SCHED_COMBAT_FACE;
if (!HasCondition(COND_CAN_RANGE_ATTACK1) && !HasCondition(COND_CAN_MELEE_ATTACK1))
{
// if we can see enemy but can't use either attack type, we must need to get closer to enemy
if (HasRangedWeapon())
return SCHED_MOVE_TO_WEAPON_RANGE;
return SCHED_CHASE_ENEMY;
}
DevWarning(2, "No suitable combat schedule!\n");
return SCHED_FAIL;
}
bool CNPC_ShadowWalker::HasRangedWeapon()
{
CBaseCombatWeapon *pWeapon = GetActiveWeapon();
if (pWeapon)
return !(FClassnameIs(pWeapon, "weapon_crowbar") || FClassnameIs(pWeapon, "weapon_stunstick"));
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Override base class activiites
//-----------------------------------------------------------------------------
Activity CNPC_ShadowWalker::NPC_TranslateActivity(Activity activity)
{
switch (activity) {
case ACT_RUN_AIM_SHOTGUN:
return ACT_RUN_AIM_RIFLE;
case ACT_WALK_AIM_SHOTGUN:
return ACT_WALK_AIM_RIFLE;
case ACT_IDLE_ANGRY_SHOTGUN:
return ACT_IDLE_ANGRY_SMG1;
case ACT_RANGE_ATTACK_SHOTGUN_LOW:
return ACT_RANGE_ATTACK_SMG1_LOW;
default:
return BaseClass::NPC_TranslateActivity(activity);
}
}
//-----------------------------------------------------------------------------
// Purpose: Play NPC soundscript
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::PlaySound(string_t soundname, bool required /*= false */)
{
if (required || gpGlobals->curtime > m_flNextSoundTime)
{
m_flNextSoundTime = gpGlobals->curtime + random->RandomFloat(0.5, 1.0);
//CPASAttenuationFilter filter2(this, STRING(soundname));
EmitSound(STRING(soundname));
}
}
//-----------------------------------------------------------------------------
// Purpose: Assign a default soundscript if none is provided, then precache
//-----------------------------------------------------------------------------
void CNPC_ShadowWalker::PrecacheNPCSoundScript(string_t * SoundName, string_t defaultSoundName)
{
if (!SoundName) {
*SoundName = defaultSoundName;
}
PrecacheScriptSound(STRING(*SoundName));
}
//-----------------------------------------------------------------------------
// Purpose:
//
//
// Output :
//-----------------------------------------------------------------------------
Class_T CNPC_ShadowWalker::Classify( void )
{
return CLASS_ZOMBIE;
}

View File

@ -50,6 +50,11 @@ $Project "Server (Episodic)"
$Folder "HL2 DLL" $Folder "HL2 DLL"
{ {
$Folder "Mod"
{
$File "mod\npc_lost_soul.cpp"
$File "mod\npc_shadow_walker.cpp"
}
$File "$SRCDIR\game\shared\episodic\achievements_ep1.cpp" $File "$SRCDIR\game\shared\episodic\achievements_ep1.cpp"
$File "$SRCDIR\game\shared\episodic\achievements_ep2.cpp" $File "$SRCDIR\game\shared\episodic\achievements_ep2.cpp"
$File "$SRCDIR\game\shared\episodic\achievements_epx.cpp" $File "$SRCDIR\game\shared\episodic\achievements_epx.cpp"