diff --git a/dlls/apache.cpp b/dlls/apache.cpp index b3cfcf87..28b8ea7d 100644 --- a/dlls/apache.cpp +++ b/dlls/apache.cpp @@ -35,6 +35,7 @@ class CApache : public CBaseMonster void Spawn( void ); void Precache( void ); + int IRelationship( CBaseEntity *pTarget ); int Classify( void ) { return CLASS_HUMAN_MILITARY; }; int BloodColor( void ) { return DONT_BLEED; } void Killed( entvars_t *pevAttacker, int iGib ); @@ -175,6 +176,20 @@ void CApache::Precache( void ) UTIL_PrecacheOther( "hvr_rocket" ); } +//========================================================= +// IRelationship - overridden because Black Ops are +// Human Grunt's nemesis. +//========================================================= +int CApache::IRelationship( CBaseEntity *pTarget ) +{ + if( FClassnameIs( pTarget->pev, "monster_male_assassin" ) || FClassnameIs( pTarget->pev, "monster_blkop_apache" ) || FClassnameIs( pTarget->pev, "monster_human_assassin" ) ) + { + return R_NM; + } + + return CBaseMonster::IRelationship( pTarget ); +} + void CApache::NullThink( void ) { StudioFrameAdvance(); diff --git a/dlls/hassassin.cpp b/dlls/hassassin.cpp index f51ed4cb..cab1d4f1 100644 --- a/dlls/hassassin.cpp +++ b/dlls/hassassin.cpp @@ -68,6 +68,7 @@ public: int Classify( void ); int ISoundMask( void); void Shoot( void ); + int IRelationship( CBaseEntity *pTarget ); void HandleAnimEvent( MonsterEvent_t *pEvent ); Schedule_t *GetSchedule( void ); Schedule_t *GetScheduleOfType( int Type ); @@ -229,6 +230,20 @@ void CHAssassin::Shoot( void ) m_cAmmoLoaded--; } +//========================================================= +// IRelationship - overridden because Human Grunts are +// BlackOps's nemesis. +//========================================================= +int CHAssassin::IRelationship( CBaseEntity *pTarget ) +{ + if( FClassnameIs( pTarget->pev, "monster_human_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_apache" ) ) ) + { + return R_NM; + } + + return CBaseMonster::IRelationship( pTarget ); +} + //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. diff --git a/dlls/hgrunt.cpp b/dlls/hgrunt.cpp index 94e9fcb1..b3edec06 100644 --- a/dlls/hgrunt.cpp +++ b/dlls/hgrunt.cpp @@ -257,12 +257,12 @@ void CHGrunt::SpeakSentence( void ) } //========================================================= -// IRelationship - overridden because Alien Grunts are +// IRelationship - overridden because Black Ops are // Human Grunt's nemesis. //========================================================= int CHGrunt::IRelationship( CBaseEntity *pTarget ) { - if( FClassnameIs( pTarget->pev, "monster_alien_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_gargantua" ) ) ) + if( FClassnameIs( pTarget->pev, "monster_male_assassin" ) || FClassnameIs( pTarget->pev, "monster_blkop_apache" ) || FClassnameIs( pTarget->pev, "monster_human_assassin" ) ) { return R_NM; } diff --git a/dlls/rp/blkop_apache.cpp b/dlls/rp/blkop_apache.cpp new file mode 100644 index 00000000..d40b23ea --- /dev/null +++ b/dlls/rp/blkop_apache.cpp @@ -0,0 +1,112 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" +#include "apache.h" + +#define SF_WAITFORTRIGGER (0x04 | 0x40) // UNDONE: Fix! +#define SF_NOWRECKAGE 0x08 + +class CBlkopApache : public CApache +{ +public: + + void Spawn( void ); + void Precache( void ); + int IRelationship( CBaseEntity *pTarget ); +}; + + +LINK_ENTITY_TO_CLASS(monster_blkop_apache, CBlkopApache); + +void CBlkopApache::Spawn(void) +{ + Precache(); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/blkop_apache.mdl"); + UTIL_SetSize(pev, Vector(-32, -32, -64), Vector(32, 32, 0)); + UTIL_SetOrigin(pev, pev->origin); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + pev->health = gSkillData.apacheHealth; + + m_flFieldOfView = -0.707; // 270 degrees + + pev->sequence = 0; + ResetSequenceInfo(); + pev->frame = RANDOM_LONG(0, 0xFF); + + InitBoneControllers(); + + if (pev->spawnflags & SF_WAITFORTRIGGER) + { + SetUse(&CApache::StartupUse); + } + else + { + SetThink(&CApache::HuntThink); + SetTouch(&CApache::FlyTouch); + pev->nextthink = gpGlobals->time + 1.0; + } + + m_iRockets = 10; +} + +void CBlkopApache::Precache(void) +{ + PRECACHE_MODEL("models/blkop_apache.mdl"); + + PRECACHE_SOUND("apache/ap_rotor1.wav"); + PRECACHE_SOUND("apache/ap_rotor2.wav"); + PRECACHE_SOUND("apache/ap_rotor3.wav"); + PRECACHE_SOUND("apache/ap_whine1.wav"); + + PRECACHE_SOUND("weapons/mortarhit.wav"); + + m_iSpriteTexture = PRECACHE_MODEL("sprites/white.spr"); + + PRECACHE_SOUND("turret/tu_fire1.wav"); + + PRECACHE_MODEL("sprites/lgtning.spr"); + + m_iExplode = PRECACHE_MODEL("sprites/fexplo.spr"); + m_iBodyGibs = PRECACHE_MODEL("models/blkop_bodygibs.mdl"); + + UTIL_PrecacheOther("hvr_rocket"); +} + +//========================================================= +// IRelationship - overridden because Human Grunt are +// Black Ops's nemesis. +//========================================================= +int CBlkopApache::IRelationship( CBaseEntity *pTarget ) +{ + if( FClassnameIs( pTarget->pev, "monster_apache" ) || FClassnameIs( pTarget->pev, "monster_human_grunt" ) ) + { + return R_NM; + } + + return CBaseMonster::IRelationship( pTarget ); +} diff --git a/dlls/rp/blkop_osprey.cpp b/dlls/rp/blkop_osprey.cpp new file mode 100644 index 00000000..4b013a8b --- /dev/null +++ b/dlls/rp/blkop_osprey.cpp @@ -0,0 +1,169 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" +#include "osprey.h" + +#define SF_WAITFORTRIGGER 0x40 + +#define MAX_CARRY OSPREY_MAX_CARRY + +class CBlkopOsprey : public COsprey +{ +public: + + void Spawn( void ); + void Precache( void ); + + void EXPORT FindAllThink(void); + + CBaseMonster *MakeGrunt(Vector vecSrc); +}; + +LINK_ENTITY_TO_CLASS(monster_blkop_osprey, CBlkopOsprey); + +void CBlkopOsprey::Spawn(void) +{ + Precache(); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/blkop_osprey.mdl"); + UTIL_SetSize(pev, Vector(-400, -400, -100), Vector(400, 400, 32)); + UTIL_SetOrigin(pev, pev->origin); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + m_flRightHealth = 200; + m_flLeftHealth = 200; + pev->health = 400; + + m_flFieldOfView = 0; // 180 degrees + + pev->sequence = 0; + ResetSequenceInfo(); + pev->frame = RANDOM_LONG(0, 0xFF); + + InitBoneControllers(); + + SetThink(&CBlkopOsprey::FindAllThink); + SetUse(&COsprey::CommandUse); + + if (!(pev->spawnflags & SF_WAITFORTRIGGER)) + { + pev->nextthink = gpGlobals->time + 1.0; + } + + m_pos2 = pev->origin; + m_ang2 = pev->angles; + m_vel2 = pev->velocity; +} + + +void CBlkopOsprey::Precache(void) +{ + UTIL_PrecacheOther("monster_male_assassin"); + + PRECACHE_MODEL("models/blkop_osprey.mdl"); + PRECACHE_MODEL("models/HVR.mdl"); + + PRECACHE_SOUND("apache/ap_rotor4.wav"); + PRECACHE_SOUND("weapons/mortarhit.wav"); + + m_iSpriteTexture = PRECACHE_MODEL("sprites/rope.spr"); + + m_iExplode = PRECACHE_MODEL("sprites/fexplo.spr"); + m_iTailGibs = PRECACHE_MODEL("models/blkop_tailgibs.mdl"); + m_iBodyGibs = PRECACHE_MODEL("models/blkop_bodygibs.mdl"); + m_iEngineGibs = PRECACHE_MODEL("models/blkop_enginegibs.mdl"); +} + + +void CBlkopOsprey::FindAllThink(void) +{ + CBaseEntity *pEntity = NULL; + + m_iUnits = 0; + while (m_iUnits < MAX_CARRY && (pEntity = UTIL_FindEntityByClassname(pEntity, "monster_male_assassin")) != NULL) + { + if (pEntity->IsAlive()) + { + m_hGrunt[m_iUnits] = pEntity; + m_vecOrigin[m_iUnits] = pEntity->pev->origin; + m_iUnits++; + } + } + + if (m_iUnits == 0) + { + ALERT(at_console, "osprey error: no assassins to resupply\n"); + UTIL_Remove(this); + return; + } + SetThink(&COsprey::FlyThink); + pev->nextthink = gpGlobals->time + 0.1; + m_startTime = gpGlobals->time; +} + + +CBaseMonster *CBlkopOsprey::MakeGrunt(Vector vecSrc) +{ + CBaseEntity *pEntity; + CBaseMonster *pGrunt; + + TraceResult tr; + UTIL_TraceLine(vecSrc, vecSrc + Vector(0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + if (tr.pHit && Instance(tr.pHit)->pev->solid != SOLID_BSP) + return NULL; + + for (int i = 0; i < m_iUnits; i++) + { + if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) + { + if (m_hGrunt[i] != NULL && m_hGrunt[i]->pev->rendermode == kRenderNormal) + { + m_hGrunt[i]->SUB_StartFadeOut(); + } + pEntity = Create("monster_male_assassin", vecSrc, pev->angles); + pGrunt = pEntity->MyMonsterPointer(); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector(0, 0, RANDOM_FLOAT(-196, -128)); + pGrunt->SetActivity(ACT_GLIDE); + + CBeam *pBeam = CBeam::BeamCreate("sprites/rope.spr", 10); + pBeam->PointEntInit(vecSrc + Vector(0, 0, 112), pGrunt->entindex()); + pBeam->SetFlags(BEAM_FSOLID); + pBeam->SetColor(255, 255, 255); + pBeam->SetThink(&CBeam::SUB_Remove); + pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; + + // ALERT( at_console, "%d at %.0f %.0f %.0f\n", i, m_vecOrigin[i].x, m_vecOrigin[i].y, m_vecOrigin[i].z ); + pGrunt->m_vecLastPosition = m_vecOrigin[i]; + m_hGrunt[i] = pGrunt; + return pGrunt; + } + } + // ALERT( at_console, "none dead\n"); + return NULL; +} diff --git a/dlls/rp/massn.cpp b/dlls/rp/massn.cpp new file mode 100644 index 00000000..15e06542 --- /dev/null +++ b/dlls/rp/massn.cpp @@ -0,0 +1,392 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Black Ops - Male Assassin +//========================================================= + +#include "extdll.h" +#include "plane.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "squadmonster.h" +#include "weapons.h" +#include "talkmonster.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" +#include "hgrunt.h" + +//========================================================= +// monster-specific DEFINE's +//========================================================= +#define MASSN_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! + +// Weapon flags +#define MASSN_9MMAR (1 << 0) +#define MASSN_HANDGRENADE (1 << 1) +#define MASSN_GRENADELAUNCHER (1 << 2) +#define MASSN_SNIPERRIFLE (1 << 4) + +// Body groups. +#define HEAD_GROUP 1 +#define GUN_GROUP 2 + +// Head values +#define HEAD_WHITE 0 +#define HEAD_BLACK 1 +#define HEAD_GOGGLES 2 + +// Gun values +#define GUN_MP5 0 +#define GUN_SNIPERRIFLE 1 +#define GUN_NONE 2 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define MASSN_AE_KICK ( 3 ) +#define MASSN_AE_BURST1 ( 4 ) +#define MASSN_AE_CAUGHT_ENEMY ( 10 ) // grunt established sight with an enemy (player only) that had previously eluded the squad. +#define MASSN_AE_DROP_GUN ( 11 ) // grunt (probably dead) is dropping his mp5. + +//========================================================= +// Purpose: +//========================================================= +class CMassn : public CHGrunt +{ +public: + int Classify(void); + int IRelationship( CBaseEntity *pTarget ); + void HandleAnimEvent(MonsterEvent_t *pEvent); + void Sniperrifle(void); + + BOOL FOkToSpeak(void); + + void Spawn( void ); + void Precache( void ); + + void DeathSound(void); + void PainSound(void); + void IdleSound(void); +}; + +LINK_ENTITY_TO_CLASS(monster_male_assassin, CMassn) +LINK_ENTITY_TO_CLASS(monster_male_asssassin, CMassn) + +//========================================================= +// Purpose: +//========================================================= +BOOL CMassn::FOkToSpeak(void) +{ + return FALSE; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMassn::Classify(void) +{ + return CLASS_HUMAN_MILITARY; +} + +//========================================================= +// Purpose: +//========================================================= +void CMassn::IdleSound(void) +{ +} + +//========================================================= +// IRelationship - overridden because Human Grunts are +// BlackOps's nemesis. +//========================================================= +int CMassn::IRelationship( CBaseEntity *pTarget ) +{ + if( FClassnameIs( pTarget->pev, "monster_human_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_apache" ) ) ) + { + return R_NM; + } + + return CSquadMonster::IRelationship( pTarget ); +} + +//========================================================= +// Shoot +//========================================================= +void CMassn::Sniperrifle(void) +{ + if (m_hEnemy == NULL) + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy(vecShootOrigin); + + UTIL_MakeVectors(pev->angles); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40, 90) + gpGlobals->v_up * RANDOM_FLOAT(75, 200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass(vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_1DEGREES, 2048, BULLET_PLAYER_357, 0); // shoot +-7.5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles(vecShootDir); + SetBlending(0, angDir.x); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMassn::HandleAnimEvent(MonsterEvent_t *pEvent) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch (pEvent->event) + { + case MASSN_AE_DROP_GUN: + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment(0, vecGunPos, vecGunAngles); + + // switch to body group with no gun. + SetBodygroup(GUN_GROUP, GUN_NONE); + + // now spawn a gun. + if (FBitSet(pev->weapons, MASSN_SNIPERRIFLE)) + { + //DropItem("weapon_sniperrifle", vecGunPos, vecGunAngles); + } + else + { + DropItem("weapon_9mmAR", vecGunPos, vecGunAngles); + } + + if (FBitSet(pev->weapons, MASSN_GRENADELAUNCHER)) + { + DropItem("ammo_ARgrenades", BodyTarget(pev->origin), vecGunAngles); + } + + } + break; + + + case MASSN_AE_BURST1: + { + if (FBitSet(pev->weapons, MASSN_9MMAR)) + { + Shoot(); + + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if (RANDOM_LONG(0, 1)) + { + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "hgrunt/gr_mgun1.wav", 1, ATTN_NORM); + } + else + { + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "hgrunt/gr_mgun2.wav", 1, ATTN_NORM); + } + } + else if (FBitSet(pev->weapons, MASSN_SNIPERRIFLE)) + { + Sniperrifle(); + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sniper_fire.wav", 1, ATTN_NORM); + } + + CSoundEnt::InsertSound(bits_SOUND_COMBAT, pev->origin, 384, 0.3); + } + break; + + case MASSN_AE_KICK: + { + CBaseEntity *pHurt = Kick(); + + if (pHurt) + { + // SOUND HERE! + UTIL_MakeVectors(pev->angles); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + pHurt->TakeDamage(pev, pev, gSkillData.massnDmgKick, DMG_CLUB); + } + } + break; + + case MASSN_AE_CAUGHT_ENEMY: + break; + + default: + CHGrunt::HandleAnimEvent(pEvent); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMassn::Spawn() +{ + Precache(); + + SET_MODEL(ENT(pev), "models/massn.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + pev->health = gSkillData.massnHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = -1; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector(0, 0, 55); + + if (pev->weapons == 0) + { + // initialize to original values + pev->weapons = MASSN_9MMAR | MASSN_HANDGRENADE; + // pev->weapons = HGRUNT_SHOTGUN; + // pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER; + } + + if (FBitSet(pev->weapons, MASSN_SNIPERRIFLE)) + { + SetBodygroup(GUN_GROUP, GUN_SNIPERRIFLE); + m_cClipSize = 5; + } + else + { + m_cClipSize = MASSN_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; + + if (RANDOM_LONG(0, 99) < 80) + pev->skin = 0; // light skin + else + pev->skin = 1; // dark skin + + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMassn::Precache() +{ + PRECACHE_MODEL("models/massn.mdl"); + + PRECACHE_SOUND("hgrunt/gr_mgun1.wav"); + PRECACHE_SOUND("hgrunt/gr_mgun2.wav"); + + PRECACHE_SOUND("hgrunt/gr_reload1.wav"); + + PRECACHE_SOUND("weapons/glauncher.wav"); + + PRECACHE_SOUND("weapons/sniper_fire.wav"); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // get voice pitch + if (RANDOM_LONG(0, 1)) + m_voicePitch = 109 + RANDOM_LONG(0, 7); + else + m_voicePitch = 100; + + m_iBrassShell = PRECACHE_MODEL("models/shell.mdl");// brass shell +} + +//========================================================= +// PainSound +//========================================================= +void CMassn::PainSound(void) +{ +} + +//========================================================= +// DeathSound +//========================================================= +void CMassn::DeathSound(void) +{ +} + + +//========================================================= +// CAssassinRepel - when triggered, spawns a monster_male_assassin +// repelling down a line. +//========================================================= + +class CAssassinRepel : public CHGruntRepel +{ +public: + void Precache(void); + void EXPORT RepelUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); +}; + +LINK_ENTITY_TO_CLASS(monster_assassin_repel, CAssassinRepel); + +void CAssassinRepel::Precache(void) +{ + UTIL_PrecacheOther("monster_male_assassin"); + m_iSpriteTexture = PRECACHE_MODEL("sprites/rope.spr"); +} + +void CAssassinRepel::RepelUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) +{ + TraceResult tr; + UTIL_TraceLine(pev->origin, pev->origin + Vector(0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + /* + if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) + return NULL; + */ + + CBaseEntity *pEntity = Create("monster_male_assassin", pev->origin, pev->angles); + CBaseMonster *pGrunt = pEntity->MyMonsterPointer(); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector(0, 0, RANDOM_FLOAT(-196, -128)); + pGrunt->SetActivity(ACT_GLIDE); + // UNDONE: position? + pGrunt->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate("sprites/rope.spr", 10); + pBeam->PointEntInit(pev->origin + Vector(0, 0, 112), pGrunt->entindex()); + pBeam->SetFlags(BEAM_FSOLID); + pBeam->SetColor(255, 255, 255); + pBeam->SetThink(&CBeam::SUB_Remove); + pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; + + UTIL_Remove(this); +}