Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Topics - QwazyWabbit

Pages: 1 [2]
16
0x1337c0de / Toward a Better MOTD
« on: September 21, 2013, 07:37:41 PM »
LOX has a feature that sends a motto of the day (MOTD) to players when they connect. This code was originally in-line in the ClientBeginDeathmatch function and it was an UGLY part of an otherwise simple ClientBeginDeathmatch function.

Today I decided to clean it up a bit, make it a callable function and publish it for use by other mod developers who might be interested in using a MOTD in their mod. I don't know where the original code came from, it was a part of LOX from a very long time ago and also exists in WODX and presumably WOD from where LOX and WODX derive their pedigree.

ClientBeginDeathmatch isn't called frequently so there is no advantage to writing the code in-line and avoiding a function call. It also goes against discipline to write something in line where a function will be more advantageous and the resulting code much more clear.

The ClientPrintMOTD function prototype belongs at the top of p_client.c or in g_local.h if you intend to access it globally:
void ClientPrintMOTD (edict_t *ent);
The function body belongs in p_client.c:
Code: [Select]
// Store the message of the day in memory.
char *gMOTD = ((char *)-1); // initialized at startup as bad pointer
cvar_t *motdfile;

void ClientPrintMOTD (edict_t *ent)
{
FILE *in;
char motdPath[MAX_QPATH + 1];
int c;
int motdBytes;
char *here;

// If the MOTD hasn't been loaded, do so.
if (gMOTD == ((char *)-1))
{

// Generate the path to the MOTD file.
if (gamedir == NULL || motdfile == NULL
|| !gamedir->string[0] || !motdfile->string[0])
{
gMOTD = NULL; // null pointer means we'll never try again
return;
}

sprintf (motdPath, "./%s/%s", gamedir->string, motdfile->string);

// Open the file.
in = fopen (motdPath, "rt");
if (in == NULL)
{
gi.dprintf("Opening MOTD file failed, error: %i.\n", errno);
gMOTD = NULL;
return;
}

// Count the number of bytes in the file.
motdBytes = 0;
while ((c = fgetc (in)), c != EOF)
motdBytes++;

// Make space for that many bytes.
gMOTD = gi.TagMalloc (motdBytes + 1, TAG_GAME);
gi.dprintf("Allocating %i bytes for MOTD\n", motdBytes +1);

// Now read the MOTD in for real.  Null-terminate the string.
fclose (in);
in = fopen (motdPath, "rt");
here = gMOTD; //extra pointer for writing into gMOTD
while ((c = fgetc (in)), c != EOF)
{
*here = c;
here++;
motdBytes--;
}
*here = '\0';

// If anything went wrong, warn the console.
if (motdBytes != 0)
gi.dprintf ("MOTD error: off by %d bytes", motdBytes);

}
if (gMOTD != NULL) // If a MOTD was successfully loaded, print it.
gi.centerprintf (ent, "%s", gMOTD);
return;
}

Define the necessary CVAR in GameInit:
Code: [Select]
motdfile = gi.cvar ("motdfile", "motd.txt", 0);

Add the CVAR to g_local.h:
Code: [Select]
extern cvar_t *motdfile;

Restore the ClientBeginDeathmatch to it's original beauty.
Code: [Select]
/*=====================
ClientBeginDeathmatch

A client has just connected to the server in deathmatch
mode, so clear everything out before starting them.
=====================*/
void ClientBeginDeathmatch (edict_t *ent)
{
G_InitEdict (ent);
InitClientResp (ent->client);

// locate ent at a spawn point
PutClientInServer (ent);

if (level.intermissiontime)
MoveClientToIntermission (ent);
else
{
// send effect
gi.WriteByte (svc_muzzleflash);
gi.WriteShort (ent-g_edicts);
gi.WriteByte (MZ_LOGIN);
gi.multicast (ent->s.origin, MULTICAST_PVS);
gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
}

ClientPrintMOTD(ent);

// make sure all view stuff is valid
ClientEndServerFrame (ent);
return;
}

You will notice the ClientPrintMOTD function has a trapdoor in the gMOTD pointer. It's initialized at definition to -1 and the function sets it to NULL if there's a problem initializing the cache it is supposed point to that contains the MOTD text. I'm not sure I would have done it exactly that way but you may see a modified version later, I decided to publish this as-is and may polish it up another time. The trapdoor closes the first time the function is called and the gMOTD pointer either points to a valid MOTD string or it is NULL. The trapdoor prevents the function from taking the time to open/read/close the motd.txt file every time someone connects. The problem with this technique is that if you change the MOTD file you must restart the server to get it to read it again and update the cache.

Add a motd.txt file to your server's mod folder and enjoy your center-printed MOTD.

17
0x1337c0de / Entity List Management in Q2 Mods
« on: March 20, 2012, 10:20:04 PM »
Several extensive attempts were made to crash the Loxophilia server via the entity list crash pathway. These attempts did not succeed. The crash is produced when the active entity list is filled and a subseqent call to G_Spawn in the game mod can't find space in the entity list to instantiate a new entity. The function fails with a call to gi.error ("ED_Alloc: no free edicts"); which halts the game server resulting in a temporary denial of service.

There are two factors influencing this relative immunity to DoS.

1. Entity list size: old game default is 1024 entities. Loxo uses 2048 by default.
2. List management code in LOX was modified long ago and is now an historic artifact in LOX.

Three functions were modified in g_utils.c to speed up entity initialization and freeing.
The functions are listed below. These functions are common to all Quake2 mods.
NOTE: The entity member "classnum" is used in LOX to speed up class comparisons instead of using string matching based on "classname". The CN_* values are simply #defines or enumated constants for all entity classes in the relevant mod. You may freely discard the code dealing with this data member.

Code: [Select]
void G_InitEdict (edict_t *e)
{
e->inuse = QTRUE;
e->classname = "noclass";
e->classnum = CN_NOCLASS;
e->gravity = 1.0;
e->s.number = e - g_edicts;

// Clear what the free-edict list may have set.
e->chain = NULL;

// This is another headache.
e->think = NULL;
e->nextthink = 0;
}

// The free-edict list.  Meant to vastly speed up G_Spawn().
edict_t *g_freeEdictsH = NULL;
edict_t *g_freeEdictsT = NULL;

/*
=================
G_Spawn

Either finds a free edict, or allocates a new one.
Try to avoid reusing an entity that was recently freed, because it
can cause the client to think the entity morphed into something else
instead of being removed and recreated, which can cause interpolated
angles and bad trails.
=================
*/

edict_t *G_Spawn (void)
{
int i;
edict_t *e;
// char string[64];

// If the free-edict queue can help, let it.
while (g_freeEdictsH != NULL)
{
// Remove the first item.
e = g_freeEdictsH;
g_freeEdictsH = g_freeEdictsH->chain;
if (g_freeEdictsH == NULL)
g_freeEdictsT = NULL;

// If it's in use, get another one.
if (e->inuse)
continue;

// If it's safe to use it, do so.
if (e->freetime < 2 || level.time - e->freetime > 0.5)
{
G_InitEdict (e);
return e;
}

// If we can't use it, we won't be able to use any of these -- anything
// after it in the queue was freed even later.
else
break;
}

// The old way to find a free edict.
e = &g_edicts[(int)maxclients->value+1];
for ( i = (int) maxclients->value + 1 ; i < globals.num_edicts ; i++, e++)
{
// the first couple seconds of server time can involve a lot of
// freeing and allocating, so relax the replacement policy
if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
{
G_InitEdict (e);
return e;
}
}

if (i == game.maxentities)
gi.error ("ED_Alloc: no free edicts");

globals.num_edicts++;
G_InitEdict (e);
// sprintf(string, "num_edicts is %i\n", globals.num_edicts);
// OutputDebugString(string);
return e;
}

/*
=================
G_FreeEdict

Marks the edict as free
=================
*/
void G_FreeEdict (edict_t *ed)
{
gi.unlinkentity (ed); // unlink from world

if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
{
// gi.dprintf("tried to free special edict\n");
return;
}

memset (ed, 0, sizeof(*ed));
ed->classname = "freed";
ed->classnum = CN_NOCLASS;
ed->freetime = level.time;
ed->inuse = QFALSE;

// Put this edict into the free-edict queue.
if (g_freeEdictsH == NULL)
g_freeEdictsH = ed;
else
g_freeEdictsT->chain = ed;
g_freeEdictsT = ed;
ed->chain = NULL;
}

In server configuration files you can set maxentities 2048 or you can modify g_save.c in the game source:

Code: [Select]
maxentities = gi.cvar ("maxentities", "2048", CVAR_LATCH);

18
Release b8012 is confirmed not to load SP mode in a system with the full commercial version of Q2 installed. The program insists that single player data is missing. This appears to be a malfunction in the StartGame function in the new version. It checks to see if base1 will load. There is a bug introduced in this version as a result of StartGame calling the CM_MapWillLoad function which looks for two files, base1.bsp and base1.override. This function gets called twice, first by StartGame looking in the Quake2 root folder for both of these files and the second time by SV_GameMap_f looking for them in the maps folder. BOTH tests must pass, indicating that one or the other file exists or the game will fail to start. Since base.bsp is properly stored in the maps folder of pak0.pak it never exists in the quake2 root. The result is that the first call, by GameStart doesn't find base1.bsp or base1.override in the root and announces the failure, preventing SP mode.

You can work around this bug by putting an empty base1.override file into your Quake2\baseq2 folder.

Once that file exists and you restart r1q2, the empty override file will be found in the root, passing the first test and base1.bsp will be found in the maps folder of pak0.pak on the second pass and you will be able to play single player.


19
/dev/random / Spray-on Clothing
« on: August 15, 2011, 08:33:38 PM »

http://www.wimp.com/sprayclothing/

The good doctor did manage to find a reasonably nice looking subject to demonstrate the product.

Anna-Nichole types might have an over-spray problem.
The more hirsute among us may have a removal problem.

20
Tech Junkie Lounge / Lion
« on: July 20, 2011, 05:39:37 PM »
Just installed OS X 10.7 Lion, thirty bucks via App Store. A redundancy, it should be called OS X.7 :) Watched my CPU get to 157 degrees F while Spotlight reindexed the hard drive. You definitely want to do this upgrade with your laptop on line power. Currently downloading the Xcode 4.1 update because the Xcode they shipped last month for 10.6.8 Snow Leopard won't run on 10.7. The problem is, the App Store malfunctions if you try to update Xcode through it or via the update tool. You have to go through the Safari browser to get it to invoke the app store and download the update.

Parallels Desktop 6 also needed updating but Parallels notified me via email the moment I opened the Mail application. Incidentally, OS X Mail now looks a whole lot like Microsoft Outlook 2003, with folder list, topic list and preview panes, complete with drab tool bar. Next, they will probably be putting those lame ribbons on their apps.

Best new feature of Lion so far: Draggable borders. You can finally drag a window by its border in OS X! User interface heaven. No more stupid corner drag.

21
0x1337c0de / Need Help Coding Chase in-eyes View
« on: March 13, 2011, 09:52:21 PM »
OK, it looks like no-one has ever solved the problem of player clipping in the in-eyes view mode. The original Q2 code had chasecam from behind the chased player. Setting in-eyes would seem to be just a matter of copying the target player's look-angle into the chasing view but this renders the chaser's POV inside the head of the target player with all the nice artifacts dancing in the view. I've got semi-working chasecam code but I get lost in the vector functions and can't tell when to use gi.trace, VectorMA, VectorNormalize, etc.

Maybe we can put our collective heads together and figure this one out.

Here's what I have so far:

void ChaseCamUpdate(edict_t *ent)
{
   vec3_t o, ownerv, goal;
   edict_t *targ;
   vec3_t forward;
   trace_t trace;
   vec3_t angles;

   assert(ent != NULL);
   targ = ent->client->chase_target;

   VectorCopy(targ->s.origin, ownerv);

   if (ent->client->chase_mode == CHASE_EYES)
   {
      VectorCopy (targ->s.origin, goal);
      goal[2] += targ->viewheight + 6;
   }
   
   if (ent->client->chase_mode == CHASE_THIRDPERSON)
   {
      ownerv[2] += targ->viewheight;

      VectorCopy(targ->client->v_angle, angles);
      if (angles[PITCH] > ent->lclient->cam_maxpitch)
         angles[PITCH] = ent->lclient->cam_maxpitch;
      AngleVectors (angles, forward, NULL, NULL);
      VectorNormalize(forward);
      VectorMA(ownerv, -ent->lclient->cam_distance, forward, o);

      if (o[2] < targ->s.origin[2] + ent->lclient->cam_height)
         o[2] = targ->s.origin[2] + ent->lclient->cam_height;

      // jump animation lifts
      if (!targ->groundentity)
         o[2] += ent->lclient->cam_jump;

      trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
      VectorCopy(trace.endpos, goal);
      VectorMA(goal, 2, forward, goal);

      // pad for floors and ceilings
      VectorCopy(goal, o);
      o[2] += 6;
      trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
      if (trace.fraction < 1)
      {
         VectorCopy(trace.endpos, goal);
         goal[2] -= 6;
      }

      VectorCopy(goal, o);
      o[2] -= 6;
      trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
      if (trace.fraction < 1)
      {
         VectorCopy(trace.endpos, goal);
         goal[2] += 6;
      }
   }

   //common to all chase modes
 VectorCopy(goal, ent->s.origin);

   if(!ent->lclient->cam_freelook)
   {
      int i;
      ent->client->ps.pmove.pm_type = PM_FREEZE;
      for(i = 0; i < 3; i++)
         ent->client->ps.pmove.delta_angles = ANGLE2SHORT(targ->client->v_angle - ent->client->resp.cmd_angles);

      VectorCopy(targ->client->v_angle, ent->client->ps.viewangles);
      VectorCopy(targ->client->v_angle, ent->client->v_angle);
   }
   else
      ent->client->ps.pmove.pm_type = PM_NORMAL;

   ent->viewheight = 0;
   ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
   gi.linkentity(ent);
}

I've highlighted the relevant block in yellow, this produces a in-head view, slightly elevated by 6 units, just to make it a little less annoying. I think the solution would be to push the camera POV forward of the targ->s.origin but I don't know how to do that at the moment.

Another solution might be to ID the chased player and then not render him in the chasing client.

Ideas anyone?

Pages: 1 [2]