290 lines
7.9 KiB
C++
290 lines
7.9 KiB
C++
/***
|
|
*
|
|
* 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.
|
|
*
|
|
****/
|
|
//=========================================================
|
|
// 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"
|
|
|
|
// Monstermaker spawnflags
|
|
#define SF_MONSTERMAKER_START_ON 1 // start active ( if has targetname )
|
|
#define SF_MONSTERMAKER_CYCLIC 4 // drop one monster every time fired.
|
|
#define SF_MONSTERMAKER_MONSTERCLIP 8 // Children are blocked by monsterclip
|
|
|
|
//=========================================================
|
|
// MonsterMaker - this ent creates monsters during the game.
|
|
//=========================================================
|
|
class CMonsterMaker : public CBaseMonster
|
|
{
|
|
public:
|
|
void Spawn() override;
|
|
void Precache() override;
|
|
bool KeyValue(KeyValueData* pkvd) override;
|
|
void EXPORT ToggleUse(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value);
|
|
void EXPORT CyclicUse(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value);
|
|
void EXPORT MakerThink();
|
|
void DeathNotice(entvars_t* pevChild) override; // monster maker children use this to tell the monster maker that they have died.
|
|
void MakeMonster();
|
|
|
|
bool Save(CSave& save) override;
|
|
bool Restore(CRestore& restore) override;
|
|
|
|
static TYPEDESCRIPTION m_SaveData[];
|
|
|
|
string_t m_iszMonsterClassname; // classname of the monster(s) that will be created.
|
|
|
|
int m_cNumMonsters; // max number of monsters this ent can create
|
|
|
|
|
|
int m_cLiveChildren; // how many monsters made by this monster maker that are currently alive
|
|
int m_iMaxLiveChildren; // max number of monsters that this maker may have out at one time.
|
|
|
|
float m_flGround; // z coord of the ground under me, used to make sure no monsters are under the maker when it drops a new child
|
|
|
|
bool m_fActive;
|
|
bool m_fFadeChildren; // should we make the children fadeout?
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS(monstermaker, CMonsterMaker);
|
|
|
|
TYPEDESCRIPTION CMonsterMaker::m_SaveData[] =
|
|
{
|
|
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),
|
|
};
|
|
|
|
|
|
IMPLEMENT_SAVERESTORE(CMonsterMaker, CBaseMonster);
|
|
|
|
bool CMonsterMaker::KeyValue(KeyValueData* pkvd)
|
|
{
|
|
|
|
if (FStrEq(pkvd->szKeyName, "monstercount"))
|
|
{
|
|
m_cNumMonsters = atoi(pkvd->szValue);
|
|
return true;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "m_imaxlivechildren"))
|
|
{
|
|
m_iMaxLiveChildren = atoi(pkvd->szValue);
|
|
return true;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "monstertype"))
|
|
{
|
|
m_iszMonsterClassname = ALLOC_STRING(pkvd->szValue);
|
|
return true;
|
|
}
|
|
|
|
return CBaseMonster::KeyValue(pkvd);
|
|
}
|
|
|
|
|
|
void CMonsterMaker::Spawn()
|
|
{
|
|
pev->solid = SOLID_NOT;
|
|
|
|
m_cLiveChildren = 0;
|
|
Precache();
|
|
if (!FStringNull(pev->targetname))
|
|
{
|
|
if ((pev->spawnflags & SF_MONSTERMAKER_CYCLIC) != 0)
|
|
{
|
|
SetUse(&CMonsterMaker::CyclicUse); // drop one monster each time we fire
|
|
}
|
|
else
|
|
{
|
|
SetUse(&CMonsterMaker::ToggleUse); // so can be turned on/off
|
|
}
|
|
|
|
if (FBitSet(pev->spawnflags, SF_MONSTERMAKER_START_ON))
|
|
{ // start making monsters as soon as monstermaker spawns
|
|
m_fActive = true;
|
|
SetThink(&CMonsterMaker::MakerThink);
|
|
}
|
|
else
|
|
{ // wait to be activated.
|
|
m_fActive = false;
|
|
SetThink(&CMonsterMaker::SUB_DoNothing);
|
|
}
|
|
}
|
|
else
|
|
{ // no targetname, just start.
|
|
pev->nextthink = gpGlobals->time + m_flDelay;
|
|
m_fActive = true;
|
|
SetThink(&CMonsterMaker::MakerThink);
|
|
}
|
|
|
|
if (m_cNumMonsters == 1)
|
|
{
|
|
m_fFadeChildren = false;
|
|
}
|
|
else
|
|
{
|
|
m_fFadeChildren = true;
|
|
}
|
|
|
|
m_flGround = 0;
|
|
}
|
|
|
|
void CMonsterMaker::Precache()
|
|
{
|
|
CBaseMonster::Precache();
|
|
|
|
UTIL_PrecacheOther(STRING(m_iszMonsterClassname));
|
|
}
|
|
|
|
//=========================================================
|
|
// MakeMonster- this is the code that drops the monster
|
|
//=========================================================
|
|
void CMonsterMaker::MakeMonster()
|
|
{
|
|
edict_t* pent;
|
|
entvars_t* pevCreate;
|
|
|
|
if (m_iMaxLiveChildren > 0 && m_cLiveChildren >= m_iMaxLiveChildren)
|
|
{ // not allowed to make a new one yet. Too many live ones out right now.
|
|
return;
|
|
}
|
|
|
|
if (0 == m_flGround)
|
|
{
|
|
// set altitude. Now that I'm activated, any breakables, etc should be out from under me.
|
|
TraceResult tr;
|
|
|
|
UTIL_TraceLine(pev->origin, pev->origin - Vector(0, 0, 2048), ignore_monsters, ENT(pev), &tr);
|
|
m_flGround = tr.vecEndPos.z;
|
|
}
|
|
|
|
Vector mins = pev->origin - Vector(34, 34, 0);
|
|
Vector maxs = pev->origin + Vector(34, 34, 0);
|
|
maxs.z = pev->origin.z;
|
|
mins.z = m_flGround;
|
|
|
|
CBaseEntity* pList[2];
|
|
int count = UTIL_EntitiesInBox(pList, 2, mins, maxs, FL_CLIENT | FL_MONSTER);
|
|
if (0 != count)
|
|
{
|
|
// don't build a stack of monsters!
|
|
return;
|
|
}
|
|
|
|
pent = CREATE_NAMED_ENTITY(m_iszMonsterClassname);
|
|
|
|
if (FNullEnt(pent))
|
|
{
|
|
ALERT(at_console, "NULL Ent in MonsterMaker!\n");
|
|
return;
|
|
}
|
|
|
|
// If I have a target, fire!
|
|
if (!FStringNull(pev->target))
|
|
{
|
|
// delay already overloaded for this entity, so can't call SUB_UseTargets()
|
|
FireTargets(STRING(pev->target), this, this, USE_TOGGLE, 0);
|
|
}
|
|
|
|
pevCreate = VARS(pent);
|
|
pevCreate->origin = pev->origin;
|
|
pevCreate->angles = pev->angles;
|
|
SetBits(pevCreate->spawnflags, SF_MONSTER_FALL_TO_GROUND);
|
|
|
|
// Children hit monsterclip brushes
|
|
if ((pev->spawnflags & SF_MONSTERMAKER_MONSTERCLIP) != 0)
|
|
SetBits(pevCreate->spawnflags, SF_MONSTER_HITMONSTERCLIP);
|
|
|
|
DispatchSpawn(ENT(pevCreate));
|
|
pevCreate->owner = edict();
|
|
|
|
if (!FStringNull(pev->netname))
|
|
{
|
|
// 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--;
|
|
|
|
if (m_cNumMonsters == 0)
|
|
{
|
|
// Disable this forever. Don't kill it because it still gets death notices
|
|
SetThink(NULL);
|
|
SetUse(NULL);
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// CyclicUse - drops one monster from the monstermaker
|
|
// each time we call this.
|
|
//=========================================================
|
|
void CMonsterMaker::CyclicUse(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value)
|
|
{
|
|
MakeMonster();
|
|
}
|
|
|
|
//=========================================================
|
|
// ToggleUse - activates/deactivates the monster maker
|
|
//=========================================================
|
|
void CMonsterMaker::ToggleUse(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value)
|
|
{
|
|
if (!ShouldToggle(useType, m_fActive))
|
|
return;
|
|
|
|
if (m_fActive)
|
|
{
|
|
m_fActive = false;
|
|
SetThink(NULL);
|
|
}
|
|
else
|
|
{
|
|
m_fActive = true;
|
|
SetThink(&CMonsterMaker::MakerThink);
|
|
}
|
|
|
|
pev->nextthink = gpGlobals->time;
|
|
}
|
|
|
|
//=========================================================
|
|
// MakerThink - creates a new monster every so often
|
|
//=========================================================
|
|
void CMonsterMaker::MakerThink()
|
|
{
|
|
pev->nextthink = gpGlobals->time + m_flDelay;
|
|
|
|
MakeMonster();
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CMonsterMaker::DeathNotice(entvars_t* pevChild)
|
|
{
|
|
// ok, we've gotten the deathnotice from our child, now clear out its owner if we don't want it to fade.
|
|
m_cLiveChildren--;
|
|
|
|
if (!m_fFadeChildren)
|
|
{
|
|
pevChild->owner = NULL;
|
|
}
|
|
}
|