#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "soundent.h"
#include "gamerules.h"
#include "animation.h"
#include "../engine/studio.h"
|
These are the includes that we always use. Some may not be used,
but it doesn't really matter.
|
class CMadScientist : public CBaseMonster
{
public:
|
CScientist inherits from CBaseMonster in order that we can use
all of the nice functions that Valve have provided us with to help
deal with monster entities. |
void Spawn(); |
This function is called when the engine creates the
entity. |
void Precache(); |
This is called by Spawn to allow us to ensure that all
models / sounds etc are available to us. See
the precache page for more
information on some of the peculiarities and misunderstandings
in precaching. |
int Classify(); |
All entities should implement the Classify function. This
virtual function (declared in CBaseEntity) is used by many other
entities in the SDK to determine what type of entity they are
dealing with. The SDK contains lookup tables to see what
entities with one classification think of entities with another
classification. This is what is used to stop snarks attacking
other snarks. |
void SetActivity(int activity);
int GetActivity() { return m_Activity; } |
The idea to use these functions was taken from xen.cpp.
Basically, this is used to make setting activities / animation
sequences easier. See the activities
page for more information. |
void SetCollisionBox();
|
This is a function written by us to attempt to extract the
collision box from the actual model. This will be described in
more detail later. |
void EXPORT IdleThink();
|
This function is the entities 'think' function. Although
there is only one here you'll find that more complex monsters
will have several think functions. This also will be described
later. |
int BloodColor() { return BLOOD_COLOR_RED; }
|
This is another function that comes from CBaseEntity. It is
used throughout the SDK to get things to bleed and gib properly.
Here we set it to be red, since it is a human.
|
int m_Activity;
|
This is the variable used by GetActivity and SetActivity. It
stores the current activity that the monster is performing.
|
};
|
|
LINK_ENTITY_TO_CLASS(mad_scientist_entity, CMadScientist);
|
This basically 'registers' the entity type with the engine.
The first argument is the name of the entity. This is what is
understood by Worldcraft.
|
void CMadScientist::Spawn()
{
Precache();
|
This is the implementation of Spawn. Remember that this is
called when a mad_scientist_entity is created. The function
DispatchSpawn (in cbase.cpp) is responsible for this. The first
thing Spawn does is to call Precache to ensure that all the stuff
it needs is available.
|
pev->movetype = MOVETYPE_STEP;
pev->solid = SOLID_BBOX;
pev->takedamage = DAMAGE_YES;
pev->flags |= FL_MONSTER;
pev->health = 80;
pev->gravity = 1.0;
|
The pev is being set up here. 'pev' is short for 'pointer to
entity variables'. These variables are what the engine uses
when dealing with the entity. See
entity variables
for detailed information on the pev. Here we are saying that
this entity moves like a monster, is solid, takes damage, is
a monster, has 80 health and normal gravity.
|
SET_MODEL(ENT(pev), "models/scientist.mdl");
|
This sets the model for the entity to the one named here.
Note that this model must have been correctly precached
otherwise this Half-Life will crash. See
precaching for more information.
|
SetActivity(ACT_IDLE);
|
This calls our 'SetActivity' function to make the entity
perform the idle animation. |
SetSequenceBox();
|
This calls our 'SetSequenceBox' function to set the bounding
box of the entity to the bounding box provided by the model's
current sequence. |
SetThink(IdleThink);
pev->nextthink = gpGlobals->time + 1;
}
|
This tells the engine that this entity's think function is
'IdleThink'. Then we tell the engine that this function should
be called one second from now. |
void CMadScientist::Precache()
{
PRECACHE_MODEL("models/scientist.mdl");
}
|
This function is called from Spawn. It tells the
engine that this entity is going to require this model. If it
were to emit any sounds, or use any sprites, then they should be
precached here. |
int CMadScientist::Classify()
{
return CLASS_HUMAN_PASSIVE;
}
|
This function is called by other classes in the SDK when
they want to find out what type the entity is. We return
CLASS_HUMAN_PASSIVE, since that is what a mad scientist is! |
void CMadScientist::SetActivity(int act)
{
int sequence = LookupActivity(act);
if(sequence != ACTIVITY_NOT_AVAILABLE)
{
pev->sequence = sequence;
m_Activity = act;
pev->frame = 0;
ResetSequenceInfo();
}
}
|
This function sets the activity of the model. See the
activites page for detailed
information. In brief, this function looks up a sequence for
the given activity and sets the entity to perform this sequence. |
void CScientist::SetCollisionBox()
{
studiohdr_t *pstudiohdr;
pstudiohdr = (studiohdr_t*)GET_MODEL_PTR( ENT(pev) );
mstudioseqdesc_t *pseqdesc;
pseqdesc =
(mstudioseqdesc_t *)((byte *)pstudiohdr
+ pstudiohdr->seqindex);
Vector min, max;
min = pseqdesc[ pev->sequence ].bbmin;
max = pseqdesc[ pev->sequence ].bbmax;
UTIL_SetSize(pev,min,max);
}
|
This bit of code is not very pretty! I suspect that it
isn't actually required -- the bounding box would probably be
hard coded. However, this queries the sequence information to
get the bounding box associated with the current sequence. The
entity's bounding box is then set to match. |
void CMadScientist::IdleThink()
{
|
This is the only 'think' function that this entity has.
Others may have more 'think' functions. A 'think' function is
used to enable your entity to do things it is a bit of code that
you can ask the engine to call for you. |
float flInterval = StudioFrameAdvance();
DispatchAnimEvents(flInterval);
|
These two lines are copied from other entities in the SDK.
My current understanding is that they allow animation events to
be dispatched and for flags to be set (such as the flag that
says when a sequence has finished.) |
pev->nextthink = gpGlobals->time + 1;
|
This tells the engine to next call our think routine in one
seconds time. |
if(!IsInWorld())
{
SetTouch(NULL);
UTIL_Remove(this);
return;
}
|
This is a simple check that Valve, and therfore I, do in all
think functions. It checks that this entity is still in the
world (ie map). If it isn't then this entity is removed. |
if(pev->deadflag != DEAD_DEAD)
{
|
This think function caters for dealing with both dead and
alive scientists. The first part of the 'if' statement will
deal with the alive scientist, since they have to do more. |
SetCollisionBox();
|
Do our best to ensure the bounding box is correct. |
int i;
CBaseEntity *pNearestPlayer = NULL;
float nearestdistance=1000;
for(i=1; i<=gpGlobals->maxClients; i++)
{
CBaseEntity *pPlayer = UTIL_PlayerByIndex(i);
if(!pPlayer)
continue;
float distance =
(pPlayer->pev->origin - pev->origin).Length();
if(distance < nearestdistance)
{
nearestdistance=distance;
pNearestPlayer=pPlayer;
}
}
|
Here, we loop through all the players that the engine knows
about and set pNearestPlayer to point to that player. If this
doesn't make sense to you then please look at it a little
closer. If it still doesn't make sense then email
Damyan and I'll add some more
explanation to this section. |
if(pNearestPlayer)
{
|
This check is here in case we couldn't find a player. |
Vector toplayer =
pNearestPlayer->pev->origin - pev->origin;
|
toplayer is a vector that gives us the direction from the
scientist to the player. |
pev->angles=UTIL_VecToAngles(toplayer);
pev->angles.x=0;
pev->angles.z=0;
}
}
|
This makes the scientist rotate so that he is 'looking' at
the player. We set the x and z values of the angles to 0 so
that he only rotates around his y axis. For some interesting,
if a little odd, effects try commenting out the last two lines. |
else // The scientist is dead
{
UTIL_SetSize(pev,Vector(0,0,0),Vector(0,0,0));
}
}
|
If the scientist is dead then we just set his bounding box
to be 0 so that he doesn't block things. |