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
Railwarz / TMG Version 0.2.43 Release
« on: September 08, 2018, 09:17:09 PM »
This archive includes Windows and Linux DLLs for TMG Railwarz 0.2.43.

I recommend upgrading all servers to this version.

The Linux versions were built on Debian using GCC 4.3.2 for 32 and 64 bit platforms. If they don't work on your system they can be rebuilt from source obtained at

Q2 Training Camp / How does Quake2 handle the client configuration files?
« on: December 04, 2017, 03:47:47 PM »
How does Quake2 handle the client configuration files?

There are three files Quake II uses on startup of the client
and on shutdown, they are:

default.cfg, config.cfg, autoexec.cfg

Quake II creates it's own "virtual file system" on startup and
once it's initialized it tries to execute each of these from
within the "current directory". These files are loaded in the
order listed above so anything you write into them overrides
any equivalent setting in the previous file. This virtual
file system is a security feature of the game to prevent
malicious directory traversal attacks and it also includes
the pak files shipped with the game or included with any
mods. This allows mod authors to pak configuration files
into their mods and conveniently ship them with their mod's
custom files.

The Quake II file system is rooted in the Quake2 directory
where the Quake II executable is installed. The maximum path
that Quake II can handle is 64 characters, so you don't want
to use long directory names or long file names and you shouldn't
go very deep. This 64 character limit is hard coded into the game.

How to manage your configuration files.

The original default.cfg is used by the engine when selecting
the "reset defaults" menu item in the Options menu of the game
console. This file may or may not exist on your system as a
"normal" disk file but the default.cfg is defined in pak0.pak
of the original game from idSoftware. Never alter this file.
WARNING: altering idSoftware's pak0.pak should not be attempted.
You can make your game inoperable or incompatible.

NEVER EDIT config.cfg. Why? Because the game writes a new one
each and every time you quit the client. It's used to save the
last state of your binds. Likewise, never make it read-only.
This file is normally located in C:\quake2\baseq2\. You can
copy-paste anything from this file into your autoexec.cfg file
and it makes a handy starting point for your customizations.
One technique you can use is to launch the game, alter the settings
or add binds, then exit the client which forces your current
settings and binds to be written to config.cfg, then copy the
contents into autoexec.cfg and begin your hand edits. This file
is written to the current mod folder when you quit the game from
within a mod, this can be a multiplayer or single player mod.

The autoexec.cfg file is what you or the mod authors want it to
be. THIS is the file you should edit for your default binds and
for your mod-specific binds or parameter settings. There can be
a unique autoexec.cfg file in every mod folder and even in baseq2.

This file is ALWAYS loaded from the current folder for the client
binary you have launched. If you launched the game directly from a
shortcut or from the executable in c:\quake2\ for example, then the
autoexec.cfg will come from c:\quake2\ and not from any subdirectory.

If you launch your game from a game launcher with a target Quake2
server then the configuration files are loaded from the current
mod folder given by the gamedir cvar specified by the server.
Once you are in-game on a modded server the autoexec.cfg and
the config.cfg will be read from and written into the mod folder
for that particular mod.

Enhanced Quake II Clients

R1Q2 and Q2Pro clients also load "postinit.cfg" after loading

Q2E clients use q2econfig.cfg instead of config.cfg, preserving
the old quake2 configuration file for backward compatibility.

Railwarz / Library of 3ZB2 Chaining files for TMG Railwarz
« on: November 06, 2017, 03:42:39 PM »
This is as complete a library of bot chain files as I have found on the net. This is what 3ZB2 bots use to navigate semi-intelligently around various maps. There was an inherent bug in TMG that stifled use of the full library which I have fixed in the 0.2.40 TMG. It is now possible to select between the original *.chf/*.chn files for CTF and DM the *.nav files others have made under the old system. (*.nav files are really just *.chn files and weren't usable in CTF mode. Simply extract this zip archive into your server ctf/cfg/ folder and you're done.

Railwarz / Railwarz TMG 0.2.40 Release
« on: November 06, 2017, 03:35:12 PM »
This is the latest release of Railwarz TMG. It adds MoTD text file instead of fixed text, removes the annoying command blockage when connecting to the server and spawns players correctly as spectators and fixes the initial spectator mode. It also adds player stats logging for those admins interested in creating a statistics page for their website. More details in the changelog.txt file. Binaries for Windows and Linux are included. If you don't run q2admin simply extract the *.real.* game mod binaries and rename them per standard.

Source code is available at:

Quake FAQs, HOWTOs, and Articles / Centerprinted Text
« on: October 04, 2017, 08:04:51 AM »
Tired of exercising your speed reading skills trying to read game banners that were centerprinted when you connect to a server or when presented in-game?

set scr_centertime 5 will make center-printed text banners 5 seconds of dwell in r1q2 and q2pro clients.

Maximum allowed value in q2pro is 10 seconds.


0x1337c0de / Building r1q2ded-old from Source
« on: April 29, 2017, 03:35:45 PM »
The last archive of r1q2 source code is here:

I have been asked a couple of times now this year how to build r1q2ded-old but I don't have the answer. It's probably staring me in the face and I can build r1q2ded b8012 without much trouble but I'm not sure how to do it with the existing source. All the links in the readme.txt file are dead.

If it's possible to do it I'd like to document it here or in the TS repository for historical purposes.


0x1337c0de / Unsequenced modification and access to 'n'
« on: May 12, 2016, 01:28:14 AM »
While hacking TMG mod I found this little gem:

Code: [Select]
// From QDevels, posted by outlaw
void convert_string(char *src, char start, char end, char add, char *dest)
    int n = -1;
    while ((dest[++n] = src[n]))
        if ((dest[n] >= start) && (dest[n] <= end) && (dest[n] != '\n'))
            dest[n] += add;

/* Examples of convert_string usage
 char  text[] = "abcdefgABCDEFG1234567\n\0";
 convert_string(text, 'a', 'z', ('A'-'a'), text); // a -> A
my_bprintf (PRINT_CHAT, "text = %s\n", text);
 convert_string(text, 'A', 'Z', ('a'-'A'), text); // A -> a
my_bprintf (PRINT_CHAT, "text = %s\n", text);
 convert_string(text, 0, 127, 128, text); // white -> green
my_bprintf (PRINT_CHAT, "text = %s\n", text);
 convert_string(text, 128, 255, -128, text); // green -> white
my_bprintf (PRINT_CHAT, "text = %s\n", text);

What first caught my eye was the warning from the compiler per the subject line but second was the fun and exciting int n = -1; initialization. OK, so we're being really 1337 coder and were starting the index off below the array bounds of dest and the ++n is going to make it 0 before it puts the value of src[n] into the destination.... or does it?

The warning is about that dest[++n] thing. This is a reliance on undefined behavior in C and it can be a nasty bug. It will work fine in one platform and fail in another and it can take some time to fix if it's buried deep enough. As it was, Microsoft Visual Studio 2010 likes it and it works. It also works on Ubuntu and GCC 4.8.4 but it doesn't work on OS X, using clang/LLVM. Since Microsoft is migrating to using clang on their compiler suite you can expect this function will break when compiled on the new tools or in their Azure cloud. It may also fail on newer versions of GCC on other Linux distributions.

OK, so how to fix it? Answer: pointers instead of array indexes.

Code: [Select]
 Replace characters in destination string.
 Parameter 'add' is added to each character found in source and result is placed in dest.
 Parameters 'start' and 'end' specify character range to replace.
 Source text must be a valid C string.
//QwazyWabbit// A pointer version to eliminate undefined behavior.
void convert_string(char *src, char start, char end, char add, char *dest)
while ((*dest = *src)) {
if ((*dest >= start) && (*dest <= end) && (*dest != '\n'))
*dest += add;
src++, dest++;

In keeping with the existing 1337ness of the code, I made good use of the comma operator. :)

If your projects contain this original Qdevels/outlaw function I urge you replace it ASAP. You'll know the old one is broken when your Q2 strings and chats suddenly become blank when you port your mod instead of green or white.

In addition, I offer the following wrapper functions to use in place of the rather complicated direct uses of the 'convert_string' function:

Code: [Select]
 Set msb in specified string characters, copying them to destination.
 Text must be a valid C string.
 Source and destination can be the same.
 If dest == NULL the action occurs in-place.
void highlight_text (char *src, char *dest)
if (dest == NULL)
dest = src;
convert_string(src, 0, 127, 128, dest); // white -> green

 Clear msb in specified string characters, copying them to destination.
 Text must be a valid C string.
 Source and destination can be the same.
 If dest == NULL the action occurs in-place.
void white_text (char *src, char *dest)
if (dest == NULL)
dest = src;
convert_string(src, 128, 255, -128, dest); // green -> white

 Make text uppercase.
 Text must be a valid C string.
 Source and destination can be the same.
 If dest == NULL the action occurs in-place.
void toupper_text(char *src, char *dest)
if (dest == NULL)
dest = src;
convert_string(src, 'a', 'z', ('A'-'a'), dest); // a -> A

 Make text lowercase.
 Text must be a valid C string.
 Source and destination can be the same string.
 If dest == NULL the action occurs in-place.
void tolower_text(char *src, char *dest)
if (dest == NULL)
dest = src;
convert_string(src, 'A', 'Z', ('a'-'A'), dest); // A -> a

Testbed code:

Code: [Select]
#include <stdio.h>

int main(int argc, const char * argv[])
char otext[] = "abcdefgABCDEFG1234567";
char text[]  = "abcdefgABCDEFG1234567\n";
char target[sizeof text];

toupper_text(otext, target); // a -> A
printf ("otext   = %s\n", otext);
printf ("target  = %s\n", target);
toupper_text(text, NULL); // a -> A
printf ("text    = %s\n", text);

tolower_text(otext, target); // A -> a
printf ("otext   = %s\n", otext);
printf ("target  = %s\n", target);
tolower_text(text, NULL); // A -> a
printf ("text    = %s\n", text);

highlight_text(otext, target); // white -> green
printf ("otext   = %s\n", otext);
printf ("target  = %s\n", target);
highlight_text(text, NULL); // white -> green
printf ("text    = %s\n", text);

white_text(target, otext); // green -> white
printf ("target  = %s\n", target);
printf ("otext   = %s\n", otext);
white_text(text, NULL); // green -> white
printf ("text    = %s\n", text);

return 0;

Science / Interactive Images of Lunar North Pole
« on: March 19, 2014, 03:17:27 PM »
March 18, 2014
NASA Releases First Interactive Mosaic of Lunar North Pole

Scientists, using cameras aboard NASA's Lunar Reconnaissance Orbiter (LRO), 
have created the largest high resolution mosaic of our moon's north polar 
region. The six-and-a-half feet (two-meters)-per-pixel images cover an area 
equal to more than one-quarter of the United States.

Web viewers can zoom in and out, and pan around an area. Constructed from 
10,581 pictures, the mosaic provides enough detail to see textures and subtle 
shading of the lunar terrain. Consistent lighting throughout the images makes 
it easy to compare different regions.

"This unique image is a tremendous resource for scientists and the public 
alike," said John Keller, LRO project scientist at NASA's Goddard Space 
Flight Center, Greenbelt, Md. "It's the latest example of the exciting 
insights and data products LRO has been providing for nearly five years."

The images making up the mosaic were taken by the two LRO Narrow Angle 
Cameras, which are part of the instrument suite known as the Lunar 
Reconnaissance Orbiter Camera (LROC). The cameras can record a tremendous 
dynamic range of lit and shadowed areas.

"Creation of this giant mosaic took four years and a huge team effort across 
the LRO project," said Mark Robinson, principal investigator for the LROC at 
Arizona State University in Tempe. "We now have a nearly uniform map to 
unravel key science questions and find the best landing spots for future 

The entire image measures 931,070 pixels square - nearly 867 billion pixels 
total. A complete printout at 300 dots per inch - considered crisp 
resolution for printed publications - would require a square sheet of paper 
wider than a professional U.S. football field and almost as long. If the 
complete mosaic were processed as a single file, it would require 
approximately 3.3 terabytes of storage space. Instead, the processed mosaic 
was divided into millions of small, compressed files, making it manageable 
for users to view and navigate around the image using a web browser.

LRO entered lunar orbit in June 2009 equipped with seven instrument suites to 
map the surface, probe the radiation environment, investigate water and key 
mineral resources, and gather geological clues about the moon's evolution.

Researchers used additional information about the moon's topography from 
LRO's Lunar Orbiter Laser Altimeter, as well as gravity information from 
NASA's Gravity Recovery and Interior Laboratory (GRAIL) mission, to assemble 
the mosaic. Launched in September 2011, the GRAIL mission, employing twin 
spacecraft named Ebb and Flow, generated a gravity field map of the moon -- 
the highest resolution gravity field map of any celestial body.

LRO is managed by Goddard for the Science Mission Directorate (SMD) at NASA 
Headquarters in Washington. LROC was designed and built by Malin Space 
Science Systems and is operated by the University of Arizona. NASA's Jet 
Propulsion Laboratory in Pasadena, Calif., managed the GRAIL mission for SMD.

For more information about LRO, visit:

To access the complete collection of LROC images, visit:

To view the image with zoom and pan capability, visit:


Dwayne Brown
Headquarters, Washington

Nancy Neal-Jones/Elizabeth Zubritsky
Goddard Space Flight Center, Greenbelt, Md.

Skins, Models and Maps / Faulty WAL files on Servers
« on: March 11, 2014, 02:02:56 AM »
Maps affected:

The WAL files textures/mia/xsinsign.wal and textures/nobeer/sinsign.wal as downloaded from the TS have invalid offsets and cause R1q2 clients to terminate abruptly when the maps listed above are active. The fix is to hack the wals and update them per attached files. They are identical files with different names but once hacked, they load and display correctly.

Updates to servers and the TS HTTP server are recommended.

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

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);

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

// 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 = '\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);

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]

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);
// 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);


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

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.

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;


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)

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

// 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");

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


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");

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;
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);

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.

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

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.

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.

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);
      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);

      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);
      ent->client->ps.pmove.pm_type = PM_NORMAL;

   ent->viewheight = 0;
   ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;

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