2016-06-04 15:24:23 +02:00
|
|
|
/***
|
|
|
|
*
|
|
|
|
* Copyright (c) 1996-2002, 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.
|
|
|
|
*
|
|
|
|
****/
|
|
|
|
//=========================================================
|
|
|
|
// Monster Maker - this is an entity that creates monsters
|
|
|
|
// in the game.
|
|
|
|
//=========================================================
|
|
|
|
|
|
|
|
#include "extdll.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "cbase.h"
|
|
|
|
#include "monsters.h"
|
|
|
|
#include "saverestore.h"
|
2024-04-03 20:32:11 +02:00
|
|
|
#include "monstermaker.h"
|
|
|
|
#include "effects.h"
|
2016-06-04 15:24:23 +02:00
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// MonsterMaker - this ent creates monsters during the game.
|
|
|
|
//=========================================================
|
|
|
|
|
2016-06-25 18:33:39 +02:00
|
|
|
LINK_ENTITY_TO_CLASS( monstermaker, CMonsterMaker )
|
2024-04-03 20:32:11 +02:00
|
|
|
LINK_ENTITY_TO_CLASS( env_warpball, CMonsterMaker )
|
2016-06-04 15:24:23 +02:00
|
|
|
|
2016-06-25 18:33:39 +02:00
|
|
|
TYPEDESCRIPTION CMonsterMaker::m_SaveData[] =
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
DEFINE_FIELD( CMonsterMaker, m_iszMonsterClassname, FIELD_STRING ),
|
|
|
|
DEFINE_FIELD( CMonsterMaker, m_cNumMonsters, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( CMonsterMaker, m_cLiveChildren, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( CMonsterMaker, m_flGround, FIELD_FLOAT ),
|
|
|
|
DEFINE_FIELD( CMonsterMaker, m_iMaxLiveChildren, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( CMonsterMaker, m_fActive, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_FIELD( CMonsterMaker, m_fFadeChildren, FIELD_BOOLEAN ),
|
2024-04-03 06:02:57 +02:00
|
|
|
DEFINE_FIELD( CMonsterMaker, m_fIsWarpBall, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_FIELD( CMonsterMaker, m_cTotalMonstersCount, FIELD_INTEGER ),
|
2016-06-04 15:24:23 +02:00
|
|
|
};
|
|
|
|
|
2016-06-25 18:33:39 +02:00
|
|
|
IMPLEMENT_SAVERESTORE( CMonsterMaker, CBaseMonster )
|
2016-06-04 15:24:23 +02:00
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
void CMonsterMaker::KeyValue( KeyValueData *pkvd )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
2016-07-31 15:48:50 +02:00
|
|
|
if( FStrEq( pkvd->szKeyName, "monstercount" ) )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
2016-07-31 15:48:50 +02:00
|
|
|
m_cNumMonsters = atoi( pkvd->szValue );
|
2024-04-03 20:32:11 +02:00
|
|
|
m_cTotalMonstersCount = m_cNumMonsters;
|
2016-06-04 15:24:23 +02:00
|
|
|
pkvd->fHandled = TRUE;
|
|
|
|
}
|
2024-04-03 20:32:11 +02:00
|
|
|
else if ( FStrEq(pkvd->szKeyName, "m_imaxlivechildren") || FStrEq(pkvd->szKeyName, "maxlivechildren"))
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
2016-07-31 15:48:50 +02:00
|
|
|
m_iMaxLiveChildren = atoi( pkvd->szValue );
|
2016-06-04 15:24:23 +02:00
|
|
|
pkvd->fHandled = TRUE;
|
|
|
|
}
|
2016-07-31 15:48:50 +02:00
|
|
|
else if( FStrEq( pkvd->szKeyName, "monstertype" ) )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
m_iszMonsterClassname = ALLOC_STRING( pkvd->szValue );
|
|
|
|
pkvd->fHandled = TRUE;
|
|
|
|
}
|
2024-04-03 06:02:57 +02:00
|
|
|
else if ( FStrEq(pkvd->szKeyName, "warptarget") || FStrEq(pkvd->szKeyName, "makertarget") || FStrEq(pkvd->szKeyName, "warp_target") )
|
|
|
|
{
|
|
|
|
m_iszWarpTarget = ALLOC_STRING( pkvd->szValue );
|
|
|
|
pkvd->fHandled = TRUE;
|
|
|
|
}
|
|
|
|
else if ( FStrEq(pkvd->szKeyName, "monsterspawnflags") ) // monsterspawnflags
|
|
|
|
{
|
|
|
|
m_iChildrenSpawnflags = atoi( pkvd->szValue );
|
|
|
|
pkvd->fHandled = TRUE;
|
|
|
|
}
|
|
|
|
// radius
|
|
|
|
// damage_delay
|
2016-06-04 15:24:23 +02:00
|
|
|
else
|
|
|
|
CBaseMonster::KeyValue( pkvd );
|
|
|
|
}
|
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
void CMonsterMaker::Spawn()
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
2024-04-03 06:02:57 +02:00
|
|
|
//ALERT( at_console, "CMonsterMaker::Spawn\n");
|
|
|
|
m_fIsWarpBall = !strcmp(STRING(pev->classname), "env_warpball");
|
|
|
|
|
2016-06-04 15:24:23 +02:00
|
|
|
pev->solid = SOLID_NOT;
|
|
|
|
|
2024-04-03 06:02:57 +02:00
|
|
|
// for WarpBall to function correctly - it's spawnflag is another then same in monstermaker
|
|
|
|
if ( m_fIsWarpBall && FBitSet ( pev->spawnflags, SF_WARPBALL_ONCE )) // flag bit 1
|
|
|
|
SetBits ( pev->spawnflags, SF_MONSTERMAKER_FIREONCE ); // flag bit 16
|
|
|
|
|
2016-06-04 15:24:23 +02:00
|
|
|
m_cLiveChildren = 0;
|
|
|
|
Precache();
|
2016-07-31 15:48:50 +02:00
|
|
|
if( !FStringNull( pev->targetname ) )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
2016-07-31 15:48:50 +02:00
|
|
|
if( pev->spawnflags & SF_MONSTERMAKER_CYCLIC )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
SetUse( &CMonsterMaker::CyclicUse );// drop one monster each time we fire
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SetUse( &CMonsterMaker::ToggleUse );// so can be turned on/off
|
|
|
|
}
|
|
|
|
|
2024-04-03 20:32:11 +02:00
|
|
|
if( !m_fIsWarpBall && FBitSet( pev->spawnflags, SF_MONSTERMAKER_START_ON ))
|
2016-06-25 18:33:39 +02:00
|
|
|
{
|
|
|
|
// start making monsters as soon as monstermaker spawns
|
2016-06-04 15:24:23 +02:00
|
|
|
m_fActive = TRUE;
|
|
|
|
SetThink( &CMonsterMaker::MakerThink );
|
|
|
|
}
|
|
|
|
else
|
2016-06-25 18:33:39 +02:00
|
|
|
{
|
|
|
|
// wait to be activated.
|
2016-06-04 15:24:23 +02:00
|
|
|
m_fActive = FALSE;
|
|
|
|
SetThink( &CBaseEntity::SUB_DoNothing );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2016-06-25 18:33:39 +02:00
|
|
|
{
|
|
|
|
// no targetname, just start.
|
|
|
|
pev->nextthink = gpGlobals->time + m_flDelay;
|
|
|
|
m_fActive = TRUE;
|
|
|
|
SetThink( &CMonsterMaker::MakerThink );
|
2016-06-04 15:24:23 +02:00
|
|
|
}
|
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
if( m_cNumMonsters == 1 )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
m_fFadeChildren = FALSE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_fFadeChildren = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_flGround = 0;
|
|
|
|
}
|
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
void CMonsterMaker::Precache( void )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
2024-04-03 06:02:57 +02:00
|
|
|
//ALERT( at_console, "%s::Precache\n", STRING(pev->classname));
|
|
|
|
|
2016-06-04 15:24:23 +02:00
|
|
|
CBaseMonster::Precache();
|
|
|
|
|
2024-04-03 06:02:57 +02:00
|
|
|
if (m_fIsWarpBall)
|
|
|
|
{
|
|
|
|
m_flDelay = 5;
|
|
|
|
UTIL_PrecacheOther( "effect_warpball" );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (FStringNull( m_iszMonsterClassname ) != true)
|
2016-06-04 15:24:23 +02:00
|
|
|
UTIL_PrecacheOther( STRING( m_iszMonsterClassname ) );
|
2024-04-03 06:02:57 +02:00
|
|
|
else
|
|
|
|
ALERT( at_console, "CMonsterMaker without a children name!\n");
|
2016-06-04 15:24:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// MakeMonster- this is the code that drops the monster
|
|
|
|
//=========================================================
|
|
|
|
void CMonsterMaker::MakeMonster( void )
|
|
|
|
{
|
|
|
|
edict_t *pent;
|
2016-07-31 15:48:50 +02:00
|
|
|
entvars_t *pevCreate;
|
2016-06-04 15:24:23 +02:00
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
if( m_iMaxLiveChildren > 0 && m_cLiveChildren >= m_iMaxLiveChildren )
|
2016-06-25 18:33:39 +02:00
|
|
|
{
|
|
|
|
// not allowed to make a new one yet. Too many live ones out right now.
|
2016-06-04 15:24:23 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-04-03 20:32:11 +02:00
|
|
|
bool bFoundTarget = false;
|
|
|
|
Vector DesiredOrigin;
|
|
|
|
Vector DesiredAngles;
|
|
|
|
if (!FStringNull( m_iszWarpTarget ))
|
|
|
|
{
|
|
|
|
m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( m_iszWarpTarget ) );
|
|
|
|
if (m_pGoalEnt)
|
|
|
|
{
|
|
|
|
DesiredOrigin = m_pGoalEnt->pev->origin;
|
|
|
|
DesiredAngles = m_pGoalEnt->pev->angles;
|
|
|
|
bFoundTarget = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!bFoundTarget)
|
|
|
|
{
|
|
|
|
DesiredOrigin = pev->origin;
|
|
|
|
DesiredAngles = pev->angles;
|
|
|
|
}
|
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
if( !m_flGround )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
// set altitude. Now that I'm activated, any breakables, etc should be out from under me.
|
|
|
|
TraceResult tr;
|
|
|
|
|
2024-04-03 20:32:11 +02:00
|
|
|
UTIL_TraceLine( DesiredOrigin, DesiredOrigin - Vector ( 0, 0, 2048 ), ignore_monsters, ENT(pev), &tr );
|
2016-06-04 15:24:23 +02:00
|
|
|
m_flGround = tr.vecEndPos.z;
|
|
|
|
}
|
|
|
|
|
2024-04-03 20:32:11 +02:00
|
|
|
Vector mins = DesiredOrigin - Vector( 34, 34, 0 );
|
|
|
|
Vector maxs = DesiredOrigin + Vector( 34, 34, 0 );
|
|
|
|
maxs.z = DesiredOrigin.z;
|
2016-06-04 15:24:23 +02:00
|
|
|
mins.z = m_flGround;
|
|
|
|
|
|
|
|
CBaseEntity *pList[2];
|
2016-07-31 15:48:50 +02:00
|
|
|
int count = UTIL_EntitiesInBox( pList, 2, mins, maxs, FL_CLIENT | FL_MONSTER );
|
|
|
|
if( count )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
// don't build a stack of monsters!
|
2024-04-03 20:32:11 +02:00
|
|
|
bool bAllDead = true;
|
|
|
|
for ( int i = 0; i < count; i++ )
|
|
|
|
{
|
|
|
|
if ( pList[i]->IsAlive() ) // Don't count dead monsters
|
|
|
|
bAllDead = false;
|
|
|
|
}
|
|
|
|
// don't build a stack of monsters if there are alive monsters nearby!
|
|
|
|
if (!bAllDead)
|
|
|
|
return;
|
2016-06-04 15:24:23 +02:00
|
|
|
}
|
|
|
|
|
2024-04-03 20:32:11 +02:00
|
|
|
// If env_warpball then create teleport effect
|
|
|
|
if ( m_fIsWarpBall == true)
|
|
|
|
{
|
|
|
|
CEnvWarpBall *pWarpBall = CEnvWarpBall::WarpBallCreate();
|
|
|
|
pWarpBall->pev->origin = DesiredOrigin;
|
|
|
|
pWarpBall->pev->angles = DesiredAngles;
|
|
|
|
SetBits( pWarpBall->pev->spawnflags, SF_AUTO_FIREONCE );
|
|
|
|
pWarpBall->Use( this, this, USE_ON, 1);
|
|
|
|
|
|
|
|
// if monstermaker is a warpball and doesn't have children class specified, play effect only
|
|
|
|
if (FStringNull(m_iszMonsterClassname))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-06-04 15:24:23 +02:00
|
|
|
pent = CREATE_NAMED_ENTITY( m_iszMonsterClassname );
|
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
if( FNullEnt( pent ) )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
ALERT ( at_console, "NULL Ent in MonsterMaker!\n" );
|
|
|
|
return;
|
|
|
|
}
|
2016-07-31 15:48:50 +02:00
|
|
|
|
2016-06-04 15:24:23 +02:00
|
|
|
// If I have a target, fire!
|
2016-07-31 15:48:50 +02:00
|
|
|
if( !FStringNull( pev->target ) )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
// delay already overloaded for this entity, so can't call SUB_UseTargets()
|
2016-07-31 15:48:50 +02:00
|
|
|
FireTargets( STRING( pev->target ), this, this, USE_TOGGLE, 0 );
|
2016-06-04 15:24:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pevCreate = VARS( pent );
|
2024-04-03 06:02:57 +02:00
|
|
|
pevCreate->origin = DesiredOrigin;
|
|
|
|
pevCreate->angles = DesiredAngles;
|
|
|
|
pevCreate->spawnflags = m_iChildrenSpawnflags;
|
2016-06-04 15:24:23 +02:00
|
|
|
SetBits( pevCreate->spawnflags, SF_MONSTER_FALL_TO_GROUND );
|
|
|
|
|
|
|
|
// Children hit monsterclip brushes
|
2016-07-31 15:48:50 +02:00
|
|
|
if( pev->spawnflags & SF_MONSTERMAKER_MONSTERCLIP )
|
2016-06-04 15:24:23 +02:00
|
|
|
SetBits( pevCreate->spawnflags, SF_MONSTER_HITMONSTERCLIP );
|
|
|
|
|
|
|
|
DispatchSpawn( ENT( pevCreate ) );
|
|
|
|
pevCreate->owner = edict();
|
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
if( !FStringNull( pev->netname ) )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
// if I have a netname (overloaded), give the child monster that name as a targetname
|
|
|
|
pevCreate->targetname = pev->netname;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_cLiveChildren++;// count this monster
|
|
|
|
m_cNumMonsters--;
|
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
if( m_cNumMonsters == 0 )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
2024-04-03 06:02:57 +02:00
|
|
|
// FIXME: do we need this comment? "Disable this forever. Don't kill it because it still gets death notices"
|
|
|
|
//m_cNumMonsters = m_cTotalMonstersCount;
|
|
|
|
//m_fActive = FALSE;
|
2016-06-04 15:24:23 +02:00
|
|
|
SetThink( NULL );
|
|
|
|
SetUse( NULL );
|
|
|
|
}
|
2024-04-03 06:02:57 +02:00
|
|
|
|
|
|
|
if ( !FBitSet ( pev->spawnflags, SF_MONSTERMAKER_CYCLIC ) )
|
|
|
|
{
|
|
|
|
if ( FBitSet ( pev->spawnflags, SF_MONSTERMAKER_FIREONCE ) )
|
|
|
|
{
|
|
|
|
//ALERT( at_console, "Removing MakeMonster\n");
|
|
|
|
UTIL_Remove( this );
|
|
|
|
}
|
|
|
|
}
|
2016-06-04 15:24:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CyclicUse - drops one monster from the monstermaker
|
|
|
|
// each time we call this.
|
|
|
|
//=========================================================
|
2016-07-31 15:48:50 +02:00
|
|
|
void CMonsterMaker::CyclicUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
MakeMonster();
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// ToggleUse - activates/deactivates the monster maker
|
|
|
|
//=========================================================
|
2016-07-31 15:48:50 +02:00
|
|
|
void CMonsterMaker::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
2016-07-31 15:48:50 +02:00
|
|
|
if( !ShouldToggle( useType, m_fActive ) )
|
2016-06-04 15:24:23 +02:00
|
|
|
return;
|
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
if( m_fActive )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
m_fActive = FALSE;
|
|
|
|
SetThink( NULL );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_fActive = TRUE;
|
|
|
|
SetThink( &CMonsterMaker::MakerThink );
|
|
|
|
}
|
|
|
|
|
|
|
|
pev->nextthink = gpGlobals->time;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// MakerThink - creates a new monster every so often
|
|
|
|
//=========================================================
|
2016-07-31 15:48:50 +02:00
|
|
|
void CMonsterMaker::MakerThink( void )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
2024-04-03 06:02:57 +02:00
|
|
|
//ALERT( at_console, "CMonsterMaker::MakerThink\n");
|
|
|
|
|
2016-06-04 15:24:23 +02:00
|
|
|
pev->nextthink = gpGlobals->time + m_flDelay;
|
|
|
|
|
|
|
|
MakeMonster();
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
//=========================================================
|
2016-07-31 15:48:50 +02:00
|
|
|
void CMonsterMaker::DeathNotice( entvars_t *pevChild )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
// ok, we've gotten the deathnotice from our child, now clear out its owner if we don't want it to fade.
|
|
|
|
m_cLiveChildren--;
|
|
|
|
|
2016-07-31 15:48:50 +02:00
|
|
|
if( !m_fFadeChildren )
|
2016-06-04 15:24:23 +02:00
|
|
|
{
|
|
|
|
pevChild->owner = NULL;
|
|
|
|
}
|
|
|
|
}
|
2024-04-03 20:32:11 +02:00
|
|
|
|
|
|
|
void CMonsterMaker :: MonsterMakerInit( const char* ChildName, int MaxLiveChildren, int NumMonsters )
|
|
|
|
{
|
|
|
|
m_cNumMonsters = NumMonsters;
|
|
|
|
m_iMaxLiveChildren = MaxLiveChildren;
|
|
|
|
m_iszMonsterClassname = MAKE_STRING( ChildName );
|
|
|
|
Spawn();
|
|
|
|
}
|
|
|
|
|
|
|
|
CMonsterMaker *CMonsterMaker::MonsterMakerCreate( const char* ChildName, int MaxLiveChildren, int NumMonsters )
|
|
|
|
{
|
|
|
|
CMonsterMaker *pMonsterMaker = GetClassPtr( (CMonsterMaker *)NULL );
|
|
|
|
pMonsterMaker->MonsterMakerInit( ChildName, MaxLiveChildren, NumMonsters );
|
|
|
|
return pMonsterMaker;
|
|
|
|
}
|