#include #include #include #include #include #include #include #include "db.h" #include "getopt.h" #include "engine.h" #include "error.h" #include "misc.h" #include "iob.h" #include "tile.h" #include #define kBloodMapSig "BLM\x1A" #define kBloodMapVersion 0x0501 #define kBloodMapOldVersion 0x0401 struct FNODE { FNODE *next; char name[1]; }; FNODE head = { &head, "" }; FNODE *tail = &head; // Build globals SECTOR sector[kMaxSectors]; WALL wall[kMaxWalls]; SPRITE sprite[kMaxSprites]; // Q globals XSECTOR xsector[kMaxXSectors]; XWALL xwall[kMaxXWalls]; XSPRITE xsprite[kMaxXSprites]; short pskyoff[kMaxSkyTiles]; PICANM picanm[kMaxTiles]; uchar picsiz[kMaxTiles]; short tilesizx[kMaxTiles]; short tilesizy[kMaxTiles]; short pskybits; ulong gMapCRC; int gSkyCount; short numsectors, numwalls; struct HEADER4 { char signature[4]; short version; }; struct HEADER5 { char signature[4]; short version; }; // version 4.x header struct INFO4 { long x, y, z; ushort angle; ushort sector; ushort pskybits; long visibility; int songId; uchar parallax; int mapRevisions; uchar lenName; uchar lenAuthor; ushort numsectors; ushort numwalls; ushort numsprites; }; struct INFO5 { long x, y, z; ushort angle; ushort sector; ushort pskybits; long visibility; int songId; uchar parallax; int mapRevisions; ushort numsectors; ushort numwalls; ushort numsprites; }; /*********************************************************************** * Old version structures and typedefs **********************************************************************/ struct XSPRITE4 { signed reference : 16; unsigned state : 1; // trigger data unsigned busy : 17; unsigned type : 10; signed data : 16; unsigned txID : 10; unsigned rxID : 10; unsigned command : 3; unsigned triggerOn : 1; unsigned triggerOff : 1; unsigned triggerOnce : 1; unsigned restState : 1; // state to return to on callback unsigned key : 3; unsigned difficulty : 2; unsigned detail : 2; unsigned map : 2; unsigned view : 4; // OBSOLETE unsigned soundKit : 8; unsigned isTriggered : 1; // used for triggerOnce objects unsigned triggerPush : 1; // gates? unsigned triggerImpact : 1; // vector hits unsigned triggerPickup : 1; // secrets unsigned triggerTouch : 1; // sawblades, spikes, zaps? unsigned triggerSight : 1; // dunno, yet. unsigned triggerProximity : 1; // proximity bombs unsigned decoupled : 1; unsigned waitTime : 8; unsigned permanent : 1; // 0=not permanent, 1=permanent (respawn ignored) unsigned respawn : 2; // 0=never, 1=always, 2=optional never, 3=optional always unsigned respawnTime : 8; // 0=instant, >0=time in tenths of a second, unsigned launchMode : 2; // 0=all, 1=bloodbath, 2=ally, 3=ally&bloodbath, unsigned pad1 : 32; unsigned pad2 : 32; }; struct XWALL4 { signed reference : 16; unsigned state : 1; unsigned map : 2; // trigger data unsigned busy : 17; unsigned type : 10; signed data : 16; unsigned txID : 10; unsigned rxID : 10; unsigned command : 3; unsigned triggerOn : 1; unsigned triggerOff : 1; unsigned triggerOnce : 1; unsigned key : 3; // panning data signed panXVel : 8; signed panYVel : 8; unsigned isTriggered : 1; // used for triggerOnce objects unsigned triggerPush : 1; unsigned triggerImpact : 1; unsigned triggerRsvd1 : 1; unsigned triggerRsvd2 : 1; unsigned decoupled : 1; unsigned panAlways : 1; unsigned restState : 1; // state to return to on callback unsigned waitTime : 8; // delay before callback unsigned pad1 : 32; unsigned pad2 : 32; }; struct XSECTOR4 { signed reference : 16; unsigned state : 1; // trigger data unsigned busy : 17; unsigned type : 10; signed data : 16; unsigned txID : 10; unsigned rxID : 10; unsigned command : 3; unsigned triggerOn : 1; unsigned triggerOff : 1; unsigned triggerOnce : 1; unsigned restState : 1; unsigned key : 3; // lighting data signed amplitude : 8; unsigned freq : 8; unsigned wave : 4; unsigned shadeAlways : 1; unsigned phase : 8; unsigned shadeFloor : 1; unsigned shadeCeiling : 1; unsigned shadeWalls : 1; signed shade : 8; // panning data unsigned panFloor : 1; unsigned panCeiling : 1; unsigned panDrag : 1; unsigned waitTime : 8; // delay before callback unsigned panAlways : 1; unsigned busyTime : 8; // time to reach next state // wind/water stuff unsigned underwater : 1; unsigned depth : 2; unsigned panVel : 8; unsigned panAngle : 11; unsigned wind : 1; unsigned isTriggered : 1; unsigned triggerPush : 1; unsigned triggerImpact : 1; unsigned triggerEnter : 1; unsigned triggerExit : 1; unsigned decoupled : 1; unsigned triggerWPush : 1; signed offCeilZ : 32; signed onCeilZ : 32; signed offFloorZ : 32; signed onFloorZ : 32; unsigned pad1 : 32; unsigned pad2 : 32; }; // stuff to get tile sizes from art static void CalcPicsiz( int nTile, int x, int y ) { int i; uchar n = 0; for (i = 2; i <= x; i <<= 1) n += 0x01; for (i = 2; i <= y; i <<= 1) n += 0x10; picsiz[nTile] = n; } void artInit( void ) { char filename[20]; int i; int nTiles; int nFile, hFile; int tileStart, tileEnd; long artversion; long numtiles; memset(tilesizx, 0, sizeof(tilesizx)); memset(tilesizy, 0, sizeof(tilesizy)); memset(picanm, 0, sizeof(picanm)); nFile = 0; while (1) { sprintf(filename, "TILES%03i.ART", nFile); hFile = open(filename, O_BINARY | O_RDWR); if ( hFile == -1 ) break; read(hFile, &artversion, sizeof(artversion)); if (artversion != 1) ThrowError("Bad art file", ES_ERROR); read(hFile, &numtiles, sizeof(numtiles)); read(hFile, &tileStart, sizeof(tileStart)); read(hFile, &tileEnd, sizeof(tileEnd)); nTiles = tileEnd - tileStart + 1; read(hFile, &tilesizx[tileStart], nTiles * sizeof(tilesizx[0])); read(hFile, &tilesizy[tileStart], nTiles * sizeof(tilesizy[0])); read(hFile, &picanm[tileStart], nTiles * sizeof(picanm[0])); nFile++; } for (i = 0; i < kMaxTiles; i++) { // setup the tile size log 2 array for internal engine use CalcPicsiz(i, tilesizx[i], tilesizy[i]); } } void ShowUsage(void) { printf("Usage:\n"); printf(" CONVDB4 map1 map2 (wild cards ok)\n"); printf("\nTECHNICAL INFO:\n sizeof(XSPRITE)=%d\n sizeof(XSECTOR)=%d\n sizeof(XWALL)=%d", sizeof(XSPRITE), sizeof(XSECTOR), sizeof(XWALL)); exit(0); } void QuitMessage(char * fmt, ...) { char msg[80]; va_list argptr; va_start( argptr, fmt ); vsprintf( msg, fmt, argptr ); va_end(argptr); printf(msg); exit(1); } void ProcessFile(char *filespec) { int hFile; int i; HEADER4 header4; HEADER5 header5; INFO4 info4; INFO5 info5; char filename[_MAX_PATH], bakfilename[_MAX_PATH]; short numsprites; int length; BYTE *buffer; strcpy(filename, filespec); ChangeExtension(filename, ".MAP"); printf("%s ", filename); hFile = open(filename, O_RDONLY | O_BINARY); if ( hFile == -1 ) ThrowError("unable to open MAP file", ES_ERROR); length = filelength(hFile); buffer = (BYTE *)malloc(length); if (read(hFile, buffer, length) == length) { close(hFile); IOBuffer iob(buffer, length); iob.Read(&header4, sizeof(HEADER4)); // check signature and major version number if ( (memcmp(header4.signature, kBloodMapSig, sizeof(header4.signature)) != 0) || ((header4.version >> 8) != (kBloodMapOldVersion >> 8)) ) { printf("not expected format, skipping\n"); return; } iob.Read(&info4, sizeof(INFO4)); numsectors = info4.numsectors; numwalls = info4.numwalls; numsprites = info4.numsprites; iob.Skip(info4.lenName + 1); iob.Skip(info4.lenAuthor + 1); pskybits = info4.pskybits; gSkyCount = 1 << pskybits; iob.Read(pskyoff, gSkyCount * sizeof(pskyoff[0])); for (i = 0; i < numsectors; i++) { iob.Read(§or[i], sizeof(SECTOR)); iob.Skip(sizeof(BYTE)); // old surface data iob.Skip(sizeof(BYTE)); // old surface data if (sector[i].extra > 0) { int j = sector[i].extra; XSECTOR4 xsector4; iob.Read(&xsector4, sizeof(XSECTOR4)); memset(&xsector[j], 0, sizeof(XSECTOR)); xsector[j].reference = xsector4.reference; xsector[j].state = xsector4.state; // trigger data xsector[j].busy = 0; xsector[j].data = xsector4.data; xsector[j].txID = xsector4.txID; xsector[j].rxID = xsector4.rxID; xsector[j].command = xsector4.command; xsector[j].triggerOn = xsector4.triggerOn; xsector[j].triggerOff = xsector4.triggerOff; xsector[j].busyTime = xsector4.busyTime; xsector[j].waitTime = xsector4.waitTime; xsector[j].restState = xsector4.restState; // lighting data xsector[j].amplitude = xsector4.amplitude; xsector[j].freq = xsector4.freq; xsector[j].wave = xsector4.wave; xsector[j].shadeAlways = xsector4.shadeAlways; xsector[j].phase = xsector4.phase; xsector[j].shadeFloor = xsector4.shadeFloor; xsector[j].shadeCeiling = xsector4.shadeCeiling; xsector[j].shadeWalls = xsector4.shadeWalls; xsector[j].shade = xsector4.shade; // panning data xsector[j].panFloor = xsector4.panFloor; xsector[j].panCeiling = xsector4.panCeiling; xsector[j].panDrag = xsector4.panDrag; xsector[j].panAlways = xsector4.panAlways; // wind/water stuff xsector[j].underwater = xsector4.underwater; xsector[j].depth = xsector4.depth; xsector[j].panVel = xsector4.panVel; xsector[j].panAngle = xsector4.panAngle; xsector[j].wind = xsector4.wind; // physical triggers xsector[j].decoupled = xsector4.decoupled; xsector[j].triggerOnce = xsector4.triggerOnce; xsector[j].isTriggered = 0; xsector[j].key = xsector4.key; xsector[j].triggerPush = xsector4.triggerPush; xsector[j].triggerImpact = xsector4.triggerImpact; xsector[j].triggerExplode = 0; xsector[j].triggerEnter = xsector4.triggerEnter; xsector[j].triggerExit = xsector4.triggerExit; xsector[j].triggerWPush = xsector4.triggerWPush; xsector[j].triggerReserved1 = 0; xsector[j].triggerReserved2 = 0; // movement data xsector[j].offCeilZ = xsector4.offCeilZ; xsector[j].onCeilZ = xsector4.onCeilZ; xsector[j].offFloorZ = xsector4.offFloorZ; xsector[j].onFloorZ = xsector4.onFloorZ; xsector[j].marker0 = sector[i].type; // former lotag xsector[j].marker1 = sector[i].hitag; sector[i].type = (ushort)xsector4.type; } } for (i = 0; i < numwalls; i++) { iob.Read(&wall[i], sizeof(WALL)); iob.Skip(sizeof(BYTE)); // old surface data if (wall[i].extra > 0) { int j = wall[i].extra; XWALL4 xwall4; iob.Read(&xwall4, sizeof(XWALL4)); memset(&xwall[j], 0, sizeof(XWALL)); xwall[j].reference = xwall4.reference; xwall[j].state = xwall4.state; // trigger data xwall[j].busy = xwall4.busy; xwall[j].data = xwall4.data; xwall[j].txID = xwall4.txID; xwall[j].rxID = xwall4.rxID; xwall[j].command = xwall4.command; xwall[j].triggerOn = xwall4.triggerOn; xwall[j].triggerOff = xwall4.triggerOff; xwall[j].busyTime = 0; xwall[j].waitTime = xwall4.waitTime; xwall[j].restState = xwall4.restState; xwall[j].map = xwall4.map; // panning data xwall[j].panAlways = xwall4.panAlways; xwall[j].panXVel = xwall4.panXVel; xwall[j].panYVel = xwall4.panYVel; // physical triggers xwall[j].decoupled = xwall4.decoupled; xwall[j].triggerOnce = xwall4.triggerOnce; xwall[j].isTriggered = xwall4.isTriggered; xwall[j].key = xwall4.key; xwall[j].triggerPush = xwall4.triggerPush; xwall[j].triggerImpact = xwall4.triggerImpact; xwall[j].triggerExplode = 0; wall[i].type = (ushort)xwall4.type; } } for (i = 0; i < numsprites; i++) { iob.Read(&sprite[i], sizeof(SPRITE)); iob.Skip(sizeof(BYTE)); // old surface data if (sprite[i].extra > 0) { int j = sprite[i].extra; XSPRITE4 xsprite4; iob.Read(&xsprite4, sizeof(XSPRITE4)); memset(&xsprite[j], 0, sizeof(XSPRITE)); xsprite[j].reference = xsprite4.reference; xsprite[j].state = xsprite4.state; // trigger data xsprite[j].busy = 0; xsprite[j].txID = xsprite4.txID; xsprite[j].rxID = xsprite4.rxID; xsprite[j].command = xsprite4.command; xsprite[j].triggerOn = xsprite4.triggerOn; xsprite[j].triggerOff = xsprite4.triggerOff; xsprite[j].restState = xsprite4.restState; xsprite[j].busyTime = 0; xsprite[j].waitTime = xsprite4.waitTime; xsprite[j].difficulty = xsprite4.difficulty; xsprite[j].map = xsprite4.map; xsprite[j].soundKit = xsprite4.soundKit; // physical triggers xsprite[j].decoupled = xsprite4.decoupled; xsprite[j].triggerOnce = xsprite4.triggerOnce; xsprite[j].isTriggered = 0; xsprite[j].key = xsprite4.key; xsprite[j].triggerPush = xsprite4.triggerPush; xsprite[j].triggerImpact = xsprite4.triggerImpact; xsprite[j].triggerExplode = 0; xsprite[j].triggerPickup = xsprite4.triggerPickup; xsprite[j].triggerTouch = xsprite4.triggerTouch; xsprite[j].triggerSight = xsprite4.triggerSight; xsprite[j].triggerProximity = xsprite4.triggerProximity; xsprite[j].data1 = xsprite4.data; xsprite[j].data2 = sprite[i].type; // former lotag xsprite[j].data3 = sprite[i].mass; // format hitag // respawn flags xsprite[j].respawn = xsprite4.respawn; xsprite[j].respawnTime = xsprite4.respawnTime; xsprite[j].launchMode = xsprite4.launchMode; // this stuff needed for dudes (probably initialized dynamically) xsprite[j].moveState = 0; xsprite[j].aiState = 0; xsprite[j].health = 0; xsprite[j].dudeDeaf = 0; xsprite[j].dudeAmbush = 0; xsprite[j].dudeFlag3 = 0; xsprite[j].dudeFlag4 = 0; xsprite[j].target = 0; xsprite[j].targetX = 0; xsprite[j].targetY = 0; xsprite[j].targetZ = 0; xsprite[j].avel = 0; xsprite[j].weaponTimer = 0; sprite[i].type = (ushort)xsprite4.type; // remove the xsprite if not necessary if (sprite[i].type >= kWeaponItemBase && sprite[i].type < kDudeBase) sprite[i].extra = -1; } // offset old marker type if ( sprite[i].statnum == kStatMarker ) sprite[i].type += kMarkerOff; // fix relocated item enums if ( sprite[i].type >= kAmmoMax && sprite[i].type <= kItemMax ) sprite[i].type += kItemBase - kAmmoMax; // make sure we use origin alignment if ( !(sprite[i].cstat & kSpriteOriginAlign) ) { sprite[i].cstat |= kSpriteOriginAlign; // adjust z position for alignment change if ( (sprite[i].cstat & kSpriteRMask) != kSpriteFloor ) { int nTile = sprite[i].picnum; sprite[i].z -= (tilesizy[nTile] - tilesizy[nTile] / 2 - picanm[nTile].ycenter) * sprite[i].yrepeat << 2; } } } } else ThrowError("Error reading map file", ES_ERROR); free(buffer); // add up all the elements in the file to determine the size of the buffer length = 0; length += sizeof(HEADER5); length += sizeof(INFO5); length += gSkyCount * sizeof(pskyoff[0]); length += numsectors * sizeof(SECTOR); for (i = 0; i < numsectors; i++) { if (sector[i].extra > 0) length += sizeof(XSECTOR); } length += numwalls * sizeof(WALL); for (i = 0; i < numwalls; i++) { if (wall[i].extra > 0) length += sizeof(XWALL); } length += numsprites * sizeof(SPRITE); for (i = 0; i < numsprites; i++) { if (sprite[i].extra > 0) length += sizeof(XSPRITE); } length += 4; // CRC buffer = (char *)malloc(length); IOBuffer iob(buffer, length); memcpy(header5.signature, kBloodMapSig, sizeof(header5.signature)); header5.version = kBloodMapVersion; iob.Write(&header5, sizeof(header5)); info5.x = info4.x; info5.y = info4.y; info5.z = info4.z; info5.angle = info4.angle; info5.sector = info4.sector; info5.pskybits = info4.pskybits; info5.visibility = info4.visibility; info5.songId = info4.songId; info5.parallax = info4.parallax; info5.mapRevisions = info4.mapRevisions; info5.numsectors = numsectors; info5.numwalls = numwalls; info5.numsprites = numsprites; iob.Write(&info5, sizeof(INFO5)); iob.Write(pskyoff, gSkyCount * sizeof(pskyoff[0])); for (i = 0; i < numsectors; i++) { iob.Write(§or[i], sizeof(SECTOR)); if (sector[i].extra > 0) iob.Write(&xsector[sector[i].extra], sizeof(XSECTOR)); } for (i = 0; i < numwalls; i++) { iob.Write(&wall[i], sizeof(WALL)); if (wall[i].extra > 0) iob.Write(&xwall[wall[i].extra], sizeof(XWALL)); } for (i = 0; i < numsprites; i++) { iob.Write(&sprite[i], sizeof(SPRITE)); if (sprite[i].extra > 0) iob.Write(&xsprite[sprite[i].extra], sizeof(XSPRITE)); } gMapCRC = CRC32(buffer, length - sizeof(gMapCRC)); iob.Write(&gMapCRC, sizeof(gMapCRC)); // backup the map file strcpy(bakfilename, filename); ChangeExtension(filename, "MAP"); ChangeExtension(bakfilename, "MA4"); rename(filename, bakfilename); ChangeExtension(filename, "MAP"); printf("=> %s ", filename); hFile = open(filename, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, S_IWUSR); if ( hFile == -1 ) ThrowError("Error opening MAP file", ES_ERROR); if (write(hFile, buffer, length) != length) ThrowError("Error writing MAP file", ES_ERROR); close(hFile); free(buffer); printf("OK.\n"); } void InsertFilename( char *fname ) { FNODE *n = (FNODE *)malloc(sizeof(FNODE) + strlen(fname)); strcpy(n->name, fname); // insert the node at the tail, so it stays in order n->next = tail->next; tail->next = n; tail = n; } void ProcessArgument(char *s) { char filespec[_MAX_PATH]; char buffer[_MAX_PATH2]; char path[_MAX_PATH]; strcpy(filespec, s); ChangeExtension(filespec, ".MAP"); char *drive, *dir; // separate the path from the filespec _splitpath2(s, buffer, &drive, &dir, NULL, NULL); _makepath(path, drive, dir, NULL, NULL); struct find_t fileinfo; unsigned r = _dos_findfirst(s, _A_NORMAL, &fileinfo); if (r != 0) printf("%s not found\n", s); while ( r == 0 ) { strcpy(filespec, path); strcat(filespec, fileinfo.name); InsertFilename(filespec); r = _dos_findnext( &fileinfo ); } _dos_findclose(&fileinfo); } /*********************************************************************** * Process command line arguments **********************************************************************/ void ParseOptions( int argc, char *argv[]) { int c; while ( (c = GetOptions(argc, argv, "")) != GO_EOF ) { switch (c) { case GO_INVALID: QuitMessage("Invalid argument: %s", OptArgument); case GO_FULL: ProcessArgument(OptArgument); break; } } } void main(int argc, char *argv[]) { printf("Blood Map 5 Converter Copyright (c) 1995 Q Studios Corporation\n"); if (argc < 2) ShowUsage(); artInit(); // get tile size info ParseOptions(argc, argv); // process the file list for (FNODE *n = head.next; n != &head; n = n->next) ProcessFile(n->name); }