#include "clientapi.h" #include "listnode.h" #include "clientdepotuser.h" #include "clientdiruser.h" #include "clientfileuser.h" #include "filehead.h" #include "changesorter.h" #include "filelogcache.h" #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 | |
---|---|---|---|---|---|
#1 | 7292 | Andrew McDonald | initial submittal | ||
//guest/sam_stafford/p4hl/src/dlls/ObjectDepot.cpp | |||||
#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. |