#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. |
||