Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
Maes

Classic Doom in Doom 3: BFG Edition

Recommended Posts

Seeing how the source code of an "officially endorsed" and actively used (?) source port is available to the public, I said why not take a dip, maybe we could learn a thing or two or even be blown away, or even something that didn't make it to linuxdoom might have seeped in. So, how is "Doom the way id did it" in the year 2012?

As you may already have imagined, it's just a light dusting over old good linuxdoom. No Boom features, not even proper limit removal (e.g. MAXVISSPRITES is still 128, MAXVISPLANES - 384, MAXDRAWSEGS = 1280). So that should answer why there are not super-duper features in this one, like some people wished. It was just cobbled together with the least amount of effort possible, and it's mostly a no-frills deal.

The only interesting thing (from a programmer's point of view) is that everything has been forcibly converted to a "classless C++" form.

A few things like cheat or pickup handling or are written in a cleaner way, but I don't know if they are original to this port or lifted off elsewhere (e.g. there are ChoopersChear and GiveAmmo functions, instead of fugly inlined stuff). Of course, there's built-in scaling for fixed graphics (menus, status bar etc.) hardcoded into V_DrawPatch.

The handling of certain limits is also interesting e.g. instead of crashing on VPOs, it just returns the first one in the list of active ones, so that should lead to some very interesting visuals...

Other things worthy of note are that it practically rips off mus2midi sources (!) for playing back music.....id, which made the game, ending up using third party code to be able to reuse their game...heh.

Oh also:

	{
		P_XYMovement (mobj);

		// FIXME: decent NOP/NULL/Nil function pointer please.
		if (mobj->thinker.function.acv == (actionf_v) (-1))
			return;		// mobj was removed
	}
So it's not decent? :-(

Also, limited to a 15 MB head size:
void *I_ZoneBase( int *size )
{
	enum
	{
		HEAP_SIZE = 15 * 1024 * 1024			// SMF - was 10 * 1024 * 1024
	};
	*size = HEAP_SIZE;
	return malloc( HEAP_SIZE );
}
Final Doom-oriented (from EV_Teleport)?
thing->z = thing->floorz;  //fixme: not needed?
So there. If this doesn't kill off any dreams of id ever making a "super port" of classic Doom, I don't know what will.

Share this post


Link to post
Maes said:

Other things worthy of note are that it practically rips off mus2midi sources (!) for playing back music.....id, which made the game, ending up using third party code to be able to reuse their game...heh.

And it's not even a version with the TNT02 fix...

Maes said:

So there. If this doesn't kill off any dreams of id ever making a "super port" of classic Doom, I don't know what will.

Honestly, the hypothetical super port wouldn't be better than Doomsday, Eternity, PrBoom+ or ZDoom anyway. We have excellent ports which go much further beyond what Id would go.

Share this post


Link to post
Gez said:

We have excellent ports which go much further beyond what Id would go.


...which isn't very far, it seems. They have moved on to greener pa$ture$ since they first coded it, and such rushed ports are a job that even a junior C/C++ programmer or intern could handle in a one-off deal, with most of the code already laid out in front of him. Id have no economical motivation to reinvent "their way to Boom and beyond" wheel.

I'm surprised that id themselves didn't use anything better than linuxdoom as a base, though. If it wasn't for the forced C++ form, this could be considered as a "better official linuxdoom" (even if not by much), as at least it fixes stuff like the finale and episode 4 switches.

Share this post


Link to post
Maes said:

Id have no economical motivation to reinvent "their way to Boom and beyond" wheel.

Exactly. I don't blame them for approaching this from the angle of "if it ain't broke, don't fix it". The game is nearly twenty years old; now isn't the time to enhance it and add fancy new features.

It's already exceptional that they keep it alive. Look at their sister companies in ZeniMax -- Skyrim wasn't bundled with a slightly updated Arena and Daggerfall, plus a small new expansion pack. And no, making them freely downloadable isn't the same thing as far as support and exposure go.

(Of course, Beth is rumored to have lost the source code to the Xngine and older games; whereas Id open-source Doom's code. This creates a big difference.)

Share this post


Link to post
Maes said:

// FIXME: decent NOP/NULL/Nil function pointer please.
if (mobj->thinker.function.acv == (actionf_v) (-1))
return;		// mobj was removed
So it's not decent? :-(

No, not really (note the comment exists in the original linux doom src too). This mangling of the mobj function pointer essentially forces the developer to consider the validity of the mobj's thinker at all times (and I'm quite sure there are several places where id themselves failed to do so, which no doubt Maes knows better than most). One cannot simply assume that because the code has a reference to a mobj which is present in the thinker lists - it is OK to interact with it according to the rules of the playsim.

However I can understand why they never got around to addressing it, given the limitations of the C language (compared to say C++).

Share this post


Link to post

Hm...

How about just providing an empty function for that purpose? The pointer would still be safe to call but have a defined value to check for.

Share this post


Link to post

That wouldn't really solve the issue. Yes you replace the mangling with a nicer and safer value but otherwise the design "fault" remains.

Share this post


Link to post
Graf Zahl said:

How about just providing an empty function for that purpose? The pointer would still be safe to call but have a defined value to check for.


Because the actual null function pointer is reserved for a still active thinker, which however might have nothing to do (very common in stopped plats or for archiving/dearchiving to/from a savegame).

Check for instances where function.acv is checked for a NULL value, you'll see that this has a very different usage scenario from the dummy "-1" NOP value, which instead signals a "DO NOT EXECUTE, REMOVE" condition. Both cases are however pretty rare in the code, and checks are performed by semantics equality, so they could have just as well used two dummy functions called REMOVE_ME and DO_NOTHING, and nothing would change, function wise.

Surely there were cleaner ways even in C, e.g. Boom got around this by having an actuaL P_RemoveObject function pointer assigned to "removed" objects, which was then called and perfomed the actual garbage collection.

Share this post


Link to post

Even the method employed in Boom doesn't solve it because the mobj is still present in the thinker list and consequently is treated the same as any other mobj. Thus the cognitive load remains. What is achieved, however, is a safer deferred deletion mechanism (addressing an invalid access error).

Share this post


Link to post

I'm not sure why they chose to implement it this way -deferred removal makes sense if you want to unlink objects without calling dealloc right away, but rather batch a series of dealloc calls together in a favourable moment e.g. low CPU load, dire need, between levels etc. which imply having an automated garbage collector mechanism.

However vanilla Doom does no such thing: it still calls 1 dealloc per removed object turn, since there's no separate list of removed objects to handle together. Boom and other ports try using more clever approaches like counting references etc., while Mocha was blessed with a built-in garbage collector (even in maps like nuts.wad, it can really defer actual mobj destruction until you reload the level or even across levels, thus saving a lot of time during actual gameplay, if memory permits).

Share this post


Link to post

Deferred removal is necessary because of the representation of the thinker list (plural in Boom) and the mechanics for iteration, which the list management cannot change because they are external.

If you ask me they took a wrong turn early on in the design and never got around to addressing it.

Share this post


Link to post

Interesting change:

// DHM - NERVE :: MAXBOB reduced 25%
//#define MAXBOB 0x100000
#define MAXBOB 0xC0000

Share this post


Link to post

Some programmer handles to look for: SMF, ALAN.

Some of the changes are unnerving:

int
cht_CheckCheat
( cheatseq_t*	cht,
  char		key )
{
	return 0; // ALAN : Checking the cheats CRASHES??
        ....
	// ALAN NETWORKING -- this fails a lot on 4 player split debug!!
	// TODO: Networking
#ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING
	if ( !gameLocal->IsSplitscreen() && NetbufferChecksum() != (::g->netbuffer->checksum&NCMD_CHECKSUM) )
	{
		if (::g->debugfile) {
			fprintf (::g->debugfile,"bad packet checksum\n");
		}

		return false;
	}
#endif
Hmm...OK, so now there are official names for mission packs??? It will be interesting to see what changes they control...
// Mission packs - might be useful for TC stuff?
typedef enum
{
  doom,			// DOOM 1
  doom2,		// DOOM 2
  pack_tnt,		// TNT mission pack
  pack_plut,	// Plutonia pack
  pack_master,	// Master levels
  pack_nerve,	// Nerve levels
 
  none

} GameMission_t;
Now episode 4 has par times!
// DHM - Nerve :: Added episode 4 par times
// DOOM Par Times
const int pars[5][10] = 
{ 
	{0}, 
	{0,30,75,120,90,165,180,180,30,165},
	{0,90,90,90,120,90,360,240,30,170},
	{0,90,45,90,150,90,90,165,30,135},
	{0,165,255,135,150,180,390,135,360,180}
}; 
There are other changes as well -some important ones in the demo recording format, especially in the way angles are saved, and the way some visual renderer calculations are performed.

Well, after all there are a few changes that might be considered "canon" -especially if source port authors want to support Nerve "properly".

Share this post


Link to post

Interesting:

void M_LoadExpansion(int choice)
{
	::g->exp = choice;

	if( choice == 0 ) {
		DoomLib::SetIdealExpansion( doom2 );
	}else {
		DoomLib::SetIdealExpansion( pack_nerve );
	}

	M_SetupNextMenu(&::g->LoadDef);
	M_ReadSaveStrings();
}

Also interesting:
	static const char * TNT_MapNames[] = {
		"1: System Control", "2: Human BBQ", "3: Power Control", "4: Wormhole", "5: Hangar", <snip>
	};
They've fixed that map's name so it is no longer something on which to put your coat when you're not using it.

Share this post


Link to post

Interesting stuff - I took a short look at the zone stuff yesterday and the forced C++ approach struck me.
Is there a way I can download the source - I tried it with gittortoise but it didn't work.
Maybe dropping an archive here in this thread via speedyshare would be even better.

Share this post


Link to post

There's a ZIP link in that GIT web page which should give you a "tarball" of everything, that's what I used, and then made a code::blocks project out of the classic doom stuff alone.

Share this post


Link to post
Gez said:

Honestly, the hypothetical super port wouldn't be better than Doomsday, Eternity, PrBoom+ or ZDoom anyway. We have excellent ports which go much further beyond what Id would go.

Yeah, but do you know what none of those ports are? Being made by id Software themselves.

How come I didn't notice this release (maybe because I thought it only contained Doom 3)… It seems like an excellent codebase for purists. Think of the Boomisms that are averted by not going from Boom. Also, unlike Choco, the C++ part is done for you.

It's portability that might be affected, and where Choco is better. But Choco was made to emulate vanilla as much as possible. This, on the other hand, IS vanilla (v2).

All this might go out the window if an important reason to make a port off it is compatibility with existing demos, in case the demos are different this time. Is BFG edition compatible with Ultimate Doom / Doom 2 v1.9 demos?

Share this post


Link to post
printz said:

Is BFG edition compatible with Ultimate Doom / Doom 2 v1.9 demos?


The code suggests that there are some changes with the handling of angles during demo recording/playback, but that there is an "oldschool" compatibility mode in. Somebody gotta fire up Doom 3: BFG edition and tell us if they do play back....however the EV_Teleport floorz bit suggests that it's in Final Doom mode....

Share this post


Link to post
printz said:

Yeah, but do you know what none of those ports are? Being made by id Software themselves.


If uncaring could be converted into energy, my feelings about this particular point would be enough to turn humanity into a Kardashev Type III civilization.

Share this post


Link to post

Yeah but at the same time, this gives some people exactly what they wanted: Vanilla (or near vanilla) in higher resolutions, without having to fuss around with command line parameters or anything.

I'm not sure why anyone really expected id to do any more than necessary on this. I guess they could've used PrBoom or another port for the PC version, but they needed their own engine for the console versions anyway, so why not base it on their own source? And why do more work than needed? The extended limits were probably just a quick hack by Nerve to get No Rest for the Living playable.

Share this post


Link to post

I sure as Hell hope this doesn't write fucked up demos. Has anyone checked to see if it writes something in the demo format indicating that it's anything but a v1.9 demo? Did they even change the version number?

Share this post


Link to post
kb1 said:

I sure as Hell hope this doesn't write fucked up demos. Has anyone checked to see if it writes something in the demo format indicating that it's anything but a v1.9 demo? Did they even change the version number?


Seems they kinda did:

enum { VERSION =  111 };
in doomdef.h. Yeah folks, this is Doom v1.110, deal with it.

When loading demos, it does handle the old format, and does play at least the classic IWAD demos (along with some other port we know ;-)
	if ( demoversion == VERSION ) {
		short *temp = (short *)(::g->demo_p);
		cmd->angleturn = *temp;
		::g->demo_p += 2;
	}
	else {
		// DHM - Nerve :: Old format
		cmd->angleturn = ((unsigned char)*::g->demo_p++)<<8;
	}
However, when recording NEW demos, it uses a modified format:
void G_WriteDemoTiccmd (ticcmd_t* cmd) 
{ 
	*::g->demo_p++ = cmd->forwardmove; 
	*::g->demo_p++ = cmd->sidemove;

	// NEW VERSION
	short *temp = (short *)(::g->demo_p);
	*temp = cmd->angleturn;
	::g->demo_p += 2;

	// OLD VERSION
	//*::g->demo_p++ = (cmd->angleturn+128)>>8; 

	*::g->demo_p++ = cmd->buttons;

	int cmdSize = 5;

#ifdef DEBUG_DEMOS_WRITE
	// TESTING
	*::g->demo_p++ = ::g->prndindex;
	cmdSize++;
#endif
Hmm....does that writing prndindex when debugging remind you of anything? ;-)

Essentially the difference is that it uses a short for storing angles, resulting in a modified demo format similar to Doom v1.91. I don't know if the two are perfectly interchangeable though...

Edit: probably not: there's a lot of extra crap being stored (in G_DoPlayDemo):
// DHM - Nerve :: We now read in the player state from the demo
	if ( demoversion == VERSION ) {
		for ( i=0 ; i<MAXPLAYERS ; i++ ) {
			if ( ::g->playeringame[i] ) {
				int* src = (int *)::g->demo_p;
				::g->players[i].health = *src++;
				::g->players[i].mo->health = ::g->players[i].health;
				::g->players[i].armorpoints = *src++;
				::g->players[i].armortype = *src++;
				::g->players[i].readyweapon = (weapontype_t)*src++;
				for ( int j = 0; j < NUMWEAPONS; j++ ) {
					::g->players[i].weaponowned[j] = *src++;
				}
				for ( int j = 0; j < NUMAMMO; j++ ) {
					::g->players[i].ammo[j] = *src++;
					::g->players[i].maxammo[j] = *src++;
				}
				::g->demo_p = (byte *)src;

				P_SetupPsprites( &::g->players[i] );
			}
		}
	}
Then again it should be trivial to support "111" demos.

Share this post


Link to post

Heh, they and LinuxDoom have been versioned wrongly. It shouldn't be 1.10 and 1.11, but something like 2.00 and 2.01

Share this post


Link to post
printz said:

Heh, they and LinuxDoom have been versioned wrongly. It shouldn't be 1.10 and 1.11, but something like 2.00 and 2.01


Uh, no. That's not how versioning works.
You do not increase the version number if it goes beyond 9.

1.11 is the proper number here.

2.0 would imply some major internal changes and that's clearly not the case.


See it this way: If Boom was official, it would justify a major version bump. This here does not.

Share this post


Link to post

Well, that's cool that they changed the version number. It's ugly enough trying to determine if a demo is for UD or Final :)

Kinda wish the prndindex was not #ifdef'ed. Really wish it had been there all along...currently, the only way (I know of) to determine that a game is out of sync, is by visually determining that DoomGuy is being retarded (facing and shooting walls, etc). A per-tic matching prndindex wouldn't guarantee demo sync, but a non-matching one *would* guarantee desync.

Oh well. It's something to consider for ports that have their own demo formats.

Share this post


Link to post
DaniJ said:

Even the method employed in Boom doesn't solve it because the mobj is still present in the thinker list and consequently is treated the same as any other mobj. Thus the cognitive load remains. What is achieved, however, is a safer deferred deletion mechanism (addressing an invalid access error).

Not true.

When a thing is in deferred removal state, its thinker function is set to P_RemoveThinkerDelayed. This compares as not equal to P_MobjThinker, so, everywhere looking for an mobj, or any other type of thinker, skips a thinker in this state:

for(th = thinkercap.next; th != &thinkercap; th = th->next) {
   if(th->function == P_MobjThinker) {
      mobj_t *mo = (mobj_t *)th;
      ...
   }
}
It *does* continue to add some overhead to the list iteration, but other than that, no time is spent processing such objects.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×