Translate
EnglishFrenchGermanItalianPortugueseRussianSpanish

The XL Engine is and will remain free, donations are purely optional but greatly appreciated.

Recent Comments

Archive for October 2013

As I discussed in previous updates, I have the cell loading code in place. Prior to that I already had the save/load menu fully functional, except for the actual saving and loading – selecting saves, sounds, double-clicking, etc.. So now its time to put those things together and fully support the Daggerfall save games.

Here is a screenshot of the menu, I’ve shown this screen before but ;) You’ll see my current save games, which are different then last time – and setup to make testing different kinds of areas easier. And, of course, this is taken from my DaggerfallDOS executable – where I test the reverse engineered code as-is before moving (with refactoring and/or rewritting) into DaggerXL.

DaggerfallDOS_Load2

Currently I’m working on parsing the faction data in faction.c and then loading the appropriate faction data from the save. I’ve already gone through most of the other setup – clearing out object links, deleting existing *.atf and *.amf files from the arena2/ folder, copying those files from the current save folder to arena2/, as well as copying rumor.datbio.dat, mapsave.sav. I’ve also loaded in all the data from savevars.dat.

Once that is complete I’ll start by making sure I can load various dungeons – I’ll be sure to show some in-dungeon screenshots when I that is done, then work on exterior and interiors. Once one type of area is fully functional, all areas of that type should be. Then I can start connecting completed pieces together (and completing pieces as needed) to get a fully functional game – quests, faction data, etc.

As I was working on finishing the loading code, see the Missing Code topic for more information, I was able to verify a few things about the coordinate systems used in Daggerfall.

Each location has a unique position on the world map – which, as you already know, is 1000×500 pixels in size. Each “pixel” on this map is exactly 32768 Daggerfall units on each side. Given the height of the character, 75 Daggerfall units, the original estimate of 1 inch per unit seems accurate – making the character exactly 6 1/4 feet tall. Thus a pixel is 32768 inches x 32768 inches, making it 51.7% of a mile (832.3072 meters) on each side. Finally “map pixels” are split into 256 unit sub-tiles – meaning each pixel has 128×128, 256×256 unit subtiles – each being 21 1/3 feet on each size. When outside, these sub-tiles are the terrain chunks.

Daggerfall uses an interesting way to determine which locations are near the player. Given the world space coordinates of the player (X and Z), the map coordinates can be calculated as follows:

const int maxWorldSpaceZCoord = 16384000;

mapPosX = playerObj->xPosition / 32768;
mapPosZ = (maxWorldSpaceZCoord - playerObj->zPosition) / 32768;

 

Then, given the position on the map, an index is computed:

const int mapWidth = 1000;

mapIndex = mapPosZ * mapWidth + mapPosX;

 

This index corresponds directly to the location ID’s. In other words the location ID is actually it’s map position encoded as shown above. This also means that its map position could also be derived directly from its ID.

Note the code was taken from reverse engineered functions, I just decided to show code snippets this time for brevity. Also Daggerfall used shifts instead of divides, but the divides make the code much clearer for the purpose of showing here.

In the spirit of providing small but more frequent updates as progress is made, here is another – smaller update.

It turns out that I missed some code for region loading, so I’ve been filling in the blanks for the last few days.

The core missing functionality was the “map table” which contains simple data for all the locations inside a specific region – loading using the following function:

LoadMapTable(int region) – which loads data from “MAPTABLE.XXX”, where XXX = region number.

Its part of functionality that loads a new region of the map – such as the Daggerfall province (region 17). First it frees all the data from the old region then loads the maptable, then applies flags which are supplied from MAPSAVE.XXX, again XXX = region number. Interestingly MAPSAVE files are contained inside “mapsave.sav” which turns out to have the same archive format as the BSA files. Why not just call it mapsave.BSA then? I have no idea, lol. Anyway its a bit more complicated then that – but you get the idea. :)

Now that the map table is in place, several missing variables can be filled out allowing me to completely finish the Cell and Region loading code, which was only partially complete before. Which means that tomorrow I can go back to working on the actual use of that data.

Here’s some useful code for you:

/*************************************************
** func_13DF8F(newRegion)   //eax
   LoadMapTable(int region) //eax
*************************************************/
push ebp
mov ebp,esp
push ebx,ecx,edx,esi,edi
sub esp,00000004
mov [ebp-0018],eax   <- Loads the "region" into a local variable
mov eax,[ebp-0018]   <- Copies the local variable back to the register
call 00133DD49       <- Calls the useful function: _LoadMapTable(region)
lea esp,[ebp-0014]
pop edi,esi,edx,ecx,ebx,ebp
ret

Things like this in the Daggerfall code just make me shake my head. :lol:

I’ve been working feverishly on the Beta lately and have decided to take a step back and look and what’s been accomplished. While it’s hard to say, linearly, how far along things are – going through the code just doesn’t work that way – I can show a “map” of the code as seen so far.

For many of the files (21 of them) I know the actual name used, which I will provide. For other files, I don’t know the real name but do know about the functionality provided (to varying levels).

Numerical values shown are the first 3 numbers of the in-memory hex addresses – for example 0x16F4C7 “LoadCell()” would be shown as 16F. This is a high level map, so only file names are shown.

MAP:
Start Address | Code File or Description
————————————————————–
130 | main game loop (3D view) – file name unknown (yet)
132 | archive.c
134 | engsupp.c
13D | maploads.c
144 | career.c
147 | automap.c
155 | fs2df.c
15A | intro.c
15D | init.c
15D | text.c
166 | parse.c
16B | quests.c
16E | LoadCell and support – file name unknown (yet)
178 | char.c
18C | disk.c
192 | weapon.c
19B | loadsave.c
19C | support.c
1A0 | interface.c
1A5 | objlib.c
1A6 | maplogic.c
——————————–
Files names below this point are unknown but functionality is.
——————————–
1B0 | c-lib (malloc, free, fopen, fread, fclose, memset, rand,etc.)
1E0 | 3D rendering functionality
240 | mouse and font functionality

This includes sound support but I’ve re-written most of that already to support OpenAL.

I should also mention that most c-lib functions are not being written based off of the Daggerfall code – instead the modern equivalents are used (DOS based file system support, for example, isn’t very useful on Windows).

The major exception to this is rand() – since it must act exactly like vanilla Daggerfall, otherwise dungeon textures will be wrong. Yes, you read that right – here’s why:

word textureTableSrc[5]=	//0x28617C
{ 119, 120, 122, 123, 124 };
word textureTableCur[5];	//0x286186

void InitializeTextureTable()
{
	int r0 = rand();
	srand( curLocationRec->locationID>>16 );
	byte r = randByte();
	int r2 = _texRandTable[ r ];

	memcpy(textureTableCur, textureTableSrc, 10);
	for (int i=0; i<5; i++)
	{
		byte n = random_range(0, 4);
		if ( n == 2 )
		{
			n += 2;
		}
		n += textureTableSrc[r2];
		textureTableCur[i] = n;
	}

	srand(r0);
}

 

textureTableCur[] now contains texture file indices which are used to texture the various dungeon sections (modulo 5). As you can tell, if the random number generator is even slightly different, the dungeons will be textured completely differently. As it is, it works because the random number seed is based off the location ID – but note that the generator is re-seeded at the end so things like loot, random encounters, etc. are not affected by the locationID as well. :)

The XL Engine is and will remain free, donations are purely optional but greatly appreciated.