ObjectDepot.cpp #5

  • //
  • guest/
  • sam_stafford/
  • p4hl/
  • src/
  • dlls/
  • ObjectDepot.cpp
  • View
  • Commits
  • Open Download .zip Download (14 KB)
#include "p4objects.h"

/* This is the implementation file for the CObjectDepot class.  Much of what is done here
 * is nearly mirrored in other P4HL objects, and many Half-Life entities as well, so pay
 * attention. */

/* Connect this class with the "object_depot" entity defined in the Worldcraft FGD file. */
LINK_ENTITY_TO_CLASS( object_depot, CObjectDepot);

/* The Spawn() function is called by the HL engine whenever an entity is spawned.  It
 * acts very much like a constructor. */
void CObjectDepot :: Spawn( ) 
{ 
	Precache( ); //the Precache function loads the media necessary for this entity.

	SET_MODEL( ENT(pev), "models/depot/w_depot.mdl" ); //Use the w_depot.mdl model.

	/* The "pev" structure is a list of special entity variables.  One of these is
	 * "origin," which defines the entity's absolute position.  UTIL_SetOrigin is
	 * a wrapper for the SET_ORIGIN engine function, which apparently registers the
	 * origin with the engine in some important way.*/
	UTIL_SetOrigin( pev, pev->origin );

	/* The "target" vector is one added for P4HL.  It represents the origin that this
	 * object wants to have, and will move gradually towards. */
	target = (Vector)pev->origin;  //by default, target=origin.

	/* The UTIL_SetSize function allows you to manually set the collision box as a pair
	 * of vectors relative to the origin. */
	UTIL_SetSize( pev, Vector(-13, -32, 38), Vector(13, 32, 68) );  //set collision box!

	pev->movetype = MOVETYPE_FLY; //This is a flying object, not a falling one.

	/* Uncomment this code if you want the ObjectDepot to rotate.
	pev->sequence = LookupActivity (ACT_IDLE);
	pev->frame = 0;
	pev->framerate = 1;
	*/

	pev->gravity = 0; //This item is weightless.
	pev->solid = SOLID_BBOX; //This sets the item as type "solid" for collision purposes.

	/* The "think" function will be called by the engine whenever the entity's
	 * pev->nextthink variable is less than or equal to the current time.  The
	 * SetThink function tells the engine which function to use. */
	SetThink( Think ); //use the Think() method of this object as its "think" function.
	
	/* The "touch" function is called whenever the object touches something.*/
	SetTouch( Touch ); //use the Touch() method as the "touch" function.

	/* The "level" represents what level of the expansion hierarchy this P4HL object is at.
	 * Zero means unexpanded. */
	level = 0;

	pev->nextthink = gpGlobals->time + 0.6; //Think in 0.6 seconds.
} 

/* The Precache() method is called to make sure that all of the sounds, sprites,
 * and models this object will need are loaded into memory. */
void CObjectDepot :: Precache( ) 
{ 
	PRECACHE_MODEL( "models/depot/w_depot.mdl" ); //precache this model.
} 

/* This is the Think function - it's called at least twice a second.  The object
 * uses it to ensure that it's in the correct state. */
void CObjectDepot :: Think( void )
{
	/* If an object should somehow end up in a void space, ie leave the world, it
	 * should be deleted. */
	if (!IsInWorld()) //If it's not in the world:
	{
		UTIL_Remove( this ); //Remove it.
		return;
	}
	
	/* The UTIL_VecUpdate function is one written for P4HL.  It takes a pointer to
	 * an origin vector, and a target vector.  If they aren't equal, it incrementally 
	 * nudges the origin toward the target, and returns true.  If they are, it returns
	 * false.  In this instance, we want to move the object  if it needs moving, and if
	 * it does, check again in .05 seconds. */
	if (UTIL_VecUpdate( &(pev->origin), target )) { //Did it have to be moved?
		pev->nextthink = gpGlobals->time + 0.05; //Set next think time to .05 seconds from now.
		UTIL_SetSize( pev, Vector(-13, -32, 38), Vector(13, 32, 68) );  //Reset collision box.
	}
	else pev->nextthink = gpGlobals->time + 0.5; //Otherwise, next think in 0.5 seconds.

	if (level == 1)
	{
		StrBuf dir = dui.dirs.SPop();
		if (dir.Length() > 0) //as long as "dir" contains something:
		{
			CObjectDir* newdir = GetClassPtr( (CObjectDir *)NULL ); //create a new ObjectDir
			newdir->pev->classname = MAKE_STRING("object_dir"); //set its classname
			newdir->pev->origin = pev->origin; //set its origin as this depot's origin
			newdir->target = target - Vector(0,0,100); //with a target directly under its target
			newdir->target.x -= xcoord*70; //and xcoord*70 steps to the right
			newdir->pev->origin = newdir->target;
			newdir->path = dir; //set its path so it knows what it is
			newdir->parent = this; //set its parent
			newdir->Spawn(); //tell it to spawn in the world
			newdir->pev->nextthink = gpGlobals->time; //tell it to start thinking now.
			dirs.Append(newdir); //add this dir to this depot's list of known children.
			xcoord++; //make the next dir one space over.
		}

		StrBuf file = fui.files.SPop();
		StrBuf ftype = fui.ftypes.SPop();
		if (file.Length() > 0)
		{
			CObjectFile* newfile = GetClassPtr( (CObjectFile *)NULL );
			files.Append(newfile);
			newfile->pev->classname = MAKE_STRING("object_file");
			switch (*(ftype.Text()))
			{
			default:
			case 't':
				newfile->ftype = FTYPE_TEX;
				break;
			case 'b':
				newfile->ftype = FTYPE_BIN;
				break;
			case 'a':
				newfile->ftype = FTYPE_APP;
				break;
			case 's':
				newfile->ftype = FTYPE_SYM;
				break;
			}		
			newfile->pev->origin = pev->origin;
			newfile->target = target - Vector(0,0,100);
			newfile->target.y -= ycoord*70;
			newfile->pev->origin = newfile->target;
			newfile->path = file;
			newfile->parent = this;
			newfile->Spawn();
			newfile->pev->nextthink = gpGlobals->time;
			ycoord++;
		}
		if (file.Length() || dir.Length()) pev->nextthink = gpGlobals->time + 0.1;
		else if (pev->origin == target) pev->nextthink = gpGlobals->time + 1.0;
	}
}

/* This method is called whenever another entity touches this one.  The argument to this
 * method is a pointer to that other entity. */
void CObjectDepot :: Touch( CBaseEntity* Thing )
{
	/* We're chiefly interested in this event if Thing is a player, or a weapon
	 * projectile owned by a player.  In either case, the variable pl will end up
	 * pointing to that player. */
	CBasePlayer *pl;
	/* If Thing has an owner, *be is it. */
	CBaseEntity *be = CBaseEntity::Instance(Thing->pev->owner);

	if (Thing->IsPlayer()) pl = (CBasePlayer*) Thing; //Is Thing a player?
	else if (be->IsPlayer()) pl = (CBasePlayer*) be; //Or is Thing's owner a player?
	else return; //If neither, just return.

	/* The NextP4Time player variable helps keep a player from flooding P4HL with
	 * requests for information.  If it is not yet time for this player to issue
	 * another command, just return. */
	if (pl->m_flNextP4Time > gpGlobals->time) return;

	if (Thing->IsExpand()) { //Is this a red beam?
		Expand( pl ); //Call this object's Expand() method.
		return; //All done.
	}

	if (Thing->IsLong()) { //Is this a green beam?
		Long( pl ); //Call this object's Long() method.
		return; //All done.
	}
	/* If it wasn't red or green, it was either blue or the player.  Either way, we just
	 * print the name of the depot. */
	UTIL_PrintHud(pl, path.Text());

	pl->m_flNextP4Time = gpGlobals->time + 0.25; // 1/4 of a second until next command.
}

/* The Long method is called in response to a green beam.  The argument is the player who
 * shot the green beam.
 * A CObjectDepot responds to a green beam by displaying the depot spec, minus any comments.
 */
void CObjectDepot :: Long( CBasePlayer* pl )
{
	/* To get the spec, we need to run "p4 depot -o DEPOTNAME". */
	ClientDepotUser ui; //Use a ClientDepotUser as the I/O class.
	ui.specflag = TRUE; //Tell ui that it's going to be handling a spec.
	ClientApi client; //A normal ClientApi object to run the command.
	if (P4PORT.Length()) client.SetPort(P4PORT.Text());
	Error e; //A normal Error object to collect errors.
	StrBuf msg = StrBuf(); //This will hold formatted errors, if any.

	client.Init( &e ); //Open the client-server connection.
	if (e.GetSeverity()) { //If there are any errors at this time:
		e.Fmt(&msg); //Dump the error into the msg StrBuf.
		UTIL_FatalHud(pl, msg.Text()); //It's a connect error, so display it as fatal
		pl->m_flNextP4Time = gpGlobals->time + 20.0; //and impose a 20 second delay.
		return; //All done here.  Try again in twenty seconds.
	}
	char* args[2]; //Two arguments to the "depot" command:
	args[0] = "-o"; // "-o" for "output spec"
	args[1] = path.Text(); //the name of the depot

	client.SetArgv(2, args); //SetArgv these two arguments
	client.Run( "depot", &ui ); //Run the command "p4 depot -o DEPOTNAME"
	/* At this point, the ui object's "spec" StrBuf contains the depot's
	 * spec, minus comments - see ClientDepotUser for details. */

	client.Final( &e ); //Close the connection.
	if (e.GetSeverity()) { //Any new errors?
		e.Fmt(&msg);
		UTIL_WarningHud(pl, msg.Text());
	}
	UTIL_PrintHud ( pl, ui.spec.Text()); //Print the spec to the HUD.
	pl->m_flNextP4Time = gpGlobals->time + 0.5; //0.5 second delay
}

/* The Expand() method is the response to a red beam.  It causes a depot to
 * expand the folders and files it contains. */
void CObjectDepot :: Expand ( CBasePlayer* pl )
{
	if (level == 1) return; //If the level is 1, this depot is already expanded.

	short sofar = 0; //Counter to track how many objects we've spawned.

	StrBuf hudmsg = StrBuf(); //Message to print to the HUD.
	hudmsg.Append("Expanding depot ");
	hudmsg.Append(path.Text());
	hudmsg.Append("...");
	UTIL_PrintHud( pl, hudmsg.Text());
	/* Message printed: "Expanding depot DEPOTNAME..." */

	/* If this depot is currently at level 2 or higher, it has one child
	 * below it in the "tower".  We should remove this before proceeding
	 * since we'll be rebuilding a complete list of children anyway. */
	killKids(this); //this just removes all children of this object.

	/* The newLevel function will give this depot its new target location,
	 * and pass the message to its parents that they should move up. */
	newLevel(1);

	/* If the depot is at level 0, its parent has other children which now
	 * need to be cleared away.  Passing "this" as an argument protects this
	 * object from deletion. */
	parent->killKids(this);

	dui = ClientDirUser(); //Use a ClientDirUser to run the "dirs" command.
	dui.changeflag = FALSE; //We're running "p4 dirs," not "p4 changes".
	dui.maxresults = MAXRESULTS;
	ClientApi client;
	if (P4PORT.Length()) client.SetPort(P4PORT.Text());
	Error e;
	client.Init( &e );
	/* standard routine for detecting and handling connection errors. */
	if (e.GetSeverity()) 
	{
		StrBuf msg = StrBuf();
		e.Fmt(&msg);
		UTIL_FatalHud(pl, msg.Text());
		pl->m_flNextP4Time = gpGlobals->time + 20.0;
		return;
	}

	/* Run the "p4 dirs //DEPOTNAME/*" command. */
	StrBuf dirpath = StrBuf();
	dirpath.Append("//"); // "//"
	dirpath.Append(path.Text()); // "DEPOTNAME"
	dirpath.Append("/*"); // "/*"
	char* args[1];
	args[0] = dirpath.Text();
	
	client.SetArgv(1, args); //set arg "//DEPOTNAME/*"
	client.Run( "dirs", &dui );; // run the command.
	xcoord = 1; //This will be the counter for how far over to place the next folder.
	
	/* Now let's make files!  Pretty much the same procedure. */

	fui = ClientFileUser();
	fui.logflag = FALSE; //Getting a file list, not a truncated filelog.
	fui.maxresults = MAXRESULTS;
	
	args[0] = dirpath.Text(); //We can use the same argument as we did for dirs.
	client.SetArgv(1, args);
	client.Run( "files", &fui ); //"p4 files //DEPOTNAME/*"

	client.Final( &e ); // close this client/server connection.
	if (e.GetSeverity()) { //any errors?
		StrBuf warn = StrBuf();
		e.Fmt(&warn);
		UTIL_WarningHud(pl, warn.Text());
	}

	/* From here on it's exactly the same as before. */
	ycoord = 1; //one difference - stack up files in the y direction, not the x.

	pl->m_flNextP4Time = gpGlobals->time + 0.5; //Wait 0.5 secs for next command.

}

/* The "level" of an object determines its place in the "expansion" hierarchy.
 * A level of 0 indicates that the object is at ground level and has no children.
 * A level of 1 indicates an expanded object with any number of children.
 * Higher levels indicate parents of expanded objects, all the way up to the
 *  info bubble at the highest level.
 * The newLevel method changes the level of its object. */
void CObjectDepot :: newLevel( short newlev )
{
	if (newlev > 0) //Any object at level 1 or higher needs to be centered.
	{
		Vector parvec = parent->targvec(); //Take center x and y values from parent.
		target.x = parvec.x;
		target.y = parvec.y;
	}
	target.z += (newlev - level)*100; //Alter height proportionate with change in level.
	level = newlev; //change level variable.
	parent->newLevel(level+1); //tell parent to move to the level above this one.
}

/* The disown function is called on a child object that is about to destroy itself, so
 * we avoid trying to find it later.  This was one of the most aggravating bugs during
 * the development of P4HL, and it only took this three-line function to fix. */
void CObjectDepot :: disown ( CBaseEntity *Target ) //this does the OPPOSITE of killKids!!!
{
	dirs.EKill(Target); //Is it in the dir list?
	files.EKill(Target); //Is it in the file list?
	return;
}

/* It's occasionally necessary to destroy all but one of an object's children.
 * This is the method that does it; the caller is the one that is spared.  If
 * the caller is not a child of this object, then all children are destroyed.
 */
void CObjectDepot :: killKids( CBaseEntity *Caller )
{
	BOOL callerFlag = FALSE; //This flag tracks whether the caller was a child.
	CBaseEntity* kid = dirs.EPop(); //Pop off an ObjectDir pointer.
	while (kid != NULL) //and for each one...
	{
		if (kid == Caller) //Did we find the caller among the children?
		{
			callerFlag = TRUE; //Take note that we found it
			kid = dirs.EPop(); //Pop the next child from the list.
			continue;
		} //Otherwise, remove this child.
		kid -> killKids(this); //Before deleting it, have it delete children it might have.
		UTIL_Remove(kid); //Remove it.
		kid = dirs.EPop(); //Pop the next child.
	}
	if (callerFlag) dirs.Append(Caller); //If the caller was a child, re-attach it to the list.

	callerFlag = false; //Now we look among the files; reset the flag.
	/*Same procedure as above. */
	kid = files.EPop(); 
	while (kid != NULL)
	{
		if (kid == Caller)
		{
			callerFlag = TRUE;
			kid = files.EPop();
			continue;
		}
		kid -> killKids(this);
		UTIL_Remove(kid);
		kid = files.EPop();
	}
	if (callerFlag) files.Append(Caller);
}
# Change User Description Committed
#6 1689 Sam Stafford Integrate 02.1 API and code cleanup to P4HL.
 Lots of work.  Phew.
#5 1024 Sam Stafford Reworked entity creation in most cases - it's done one at a time now rather
than all at once.  This allows us to have more entities than the previous limit
of 130, and also looks a little nicer.

Folders and files now pop into existence instantly instead of sliding - makes
navigation easier.  Depots still slide because there typically aren't as many
of them (okay, I might eventually make them pop too, but I'm tired now).
Revisions slide because it looks really cool - like a waterfall pouring out of
the file.

The upper limit to entities is now due to the "visible entity packet" thing,
which I'm certain I have no control over - it's as high as it can possibly be
right now.
#4 1007 Sam Stafford A stab at making P4HL a bit more accessible to Perforce novices.
During the Precache() phase of the P4HL objects, a "p4 info" is
executed.  If this returns any errors, odds are there's no server
there (I can't think of what other error "p4 info" would return).
If this happens, use "public.perforce.com:1666" as the new port
value for all future Perforce transactions during this session,
and send a message to the console explaining this.
#3 1005 Sam Stafford Whoops, I lied - NOW text is the default in all cases.
#2 1003 Sam Stafford Different file types now displayed differently.
 Also flipped
around the file model so that its text doesn't look backwards
any more.
#1 937 Sam Stafford Renaming my guest directory to the more conventional
sam_stafford.
//guest/samwise/p4hl/src/dlls/ObjectDepot.cpp
#1 936 Sam Stafford Adding P4HL to the public depot.
 See relnotes.txt for
installation instructions; all relevant files are under
p4hl/dist.

Source code is under p4hl/src in the form of a VC++ project.