#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 "trig.h" #include #define kBloodMapSig "BLM\x1A" #define kBloodMapVersion 0x0600 #define kBloodMapOldVersion 0x0503 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]; ulong gMapCRC; int gSkyCount; short numsectors, numwalls; struct HEADER5 { char signature[4]; short version; }; struct HEADER6 { char signature[4]; short version; }; // version 5.x header 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; }; struct INFO6 { 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 SPRITE5 { long x; long y; long z; ushort cstat; schar shade; uchar pal; uchar clipdist; uchar xrepeat; uchar yrepeat; schar xoffset; schar yoffset; sshort picnum; sshort ang; sshort xvel; sshort yvel; sshort zvel; sshort owner; sshort sectnum; sshort statnum; ushort type; ushort mass; sshort extra; }; struct WALL5 { long x; long y; ushort point2; short nextsector; short nextwall; short picnum; short overpicnum; signed char shade; char pal; short cstat; uchar xrepeat; uchar yrepeat; uchar xpanning; uchar ypanning; ushort type; short hitag; short extra; }; struct SECTOR5 { unsigned short wallptr; unsigned short wallnum; short ceilingpicnum; short floorpicnum; short ceilingslope; short floorslope; long ceilingz; long floorz; schar ceilingshade; schar floorshade; uchar ceilingxpanning; uchar floorxpanning; uchar ceilingypanning; uchar floorypanning; uchar ceilingstat; uchar floorstat; uchar ceilingpal; uchar floorpal; uchar visibility; ushort type; short hitag; short extra; }; struct XSPRITE5 { signed reference : 14; unsigned state : 1; // trigger data unsigned busy : 17; unsigned txID : 10; unsigned rxID : 10; unsigned command : 8; unsigned triggerOn : 1; unsigned triggerOff : 1; unsigned busyTime : 12; // time to reach next state unsigned waitTime : 12; // delay before callback unsigned restState : 1; // state to return to on callback unsigned difficulty : 2; unsigned map : 2; unsigned soundKit : 8; // physical triggers unsigned decoupled : 1; unsigned triggerOnce : 1; unsigned isTriggered : 1; // used for triggerOnce objects unsigned key : 3; unsigned triggerPush : 1; // action key unsigned triggerImpact : 1; // vector hits unsigned triggerExplode : 1; // exploding things, etc. unsigned triggerPickup : 1; // secrets unsigned triggerTouch : 1; // sawblades, spikes, zaps? unsigned triggerSight : 1; // dunno, yet. unsigned triggerProximity : 1; // proximity bombs unsigned triggerReserved1 : 1; unsigned triggerReserved2 : 1; signed data1 : 16; // combo value? signed data2 : 16; // combo key? signed data3 : 16; // combo max? unsigned respawn : 2; // 0=optional never, 1=optional always, 2=always, 3=never unsigned respawnTime : 12; // 0=permanent, >0=time in tenths of a second, unsigned launchMode : 2; // 0=all, 1=bloodbath, 2=ally, 3=ally&bloodbath, // this stuff needed for dudes unsigned moveState : 8; // same as player move states unsigned aiState : 8; signed health : 12; unsigned dudeDeaf : 1; unsigned dudeAmbush : 1; unsigned dudeFlag3 : 1; unsigned dudeFlag4 : 1; signed target : 16; signed targetX : 32; signed targetY : 32; signed targetZ : 32; signed avel : 16; unsigned weaponTimer : 16; unsigned stateTimer : 16; signed support : 16; // will be used by physics system unsigned interruptable : 1; unsigned pad : 31; }; struct XWALL5 { signed reference : 14; unsigned state : 1; // trigger data unsigned busy : 17; signed data : 16; unsigned txID : 10; unsigned rxID : 10; unsigned command : 8; unsigned triggerOn : 1; unsigned triggerOff : 1; unsigned busyTime : 12; // time to reach next state unsigned waitTime : 12; // delay before callback unsigned restState : 1; // state to return to on callback unsigned map : 2; // panning data unsigned panAlways : 1; signed panXVel : 8; signed panYVel : 8; // physical triggers unsigned decoupled : 1; unsigned triggerOnce : 1; unsigned isTriggered : 1; // used for triggerOnce objects unsigned key : 3; unsigned triggerPush : 1; unsigned triggerImpact : 1; unsigned triggerExplode : 1; // exploding things, etc. unsigned triggerReserved1 : 1; unsigned triggerReserved2 : 1; unsigned xpanFrac : 8; unsigned ypanFrac : 8; unsigned interruptable : 1; unsigned pad1 : 15; unsigned pad2 : 32; }; struct XSECTOR5 { signed reference : 14; unsigned state : 1; // trigger data unsigned busy : 17; signed data : 16; unsigned txID : 10; unsigned rxID : 10; unsigned command : 8; unsigned triggerOn : 1; unsigned triggerOff : 1; unsigned busyTime : 12; // time to reach next state unsigned waitTime : 12; // delay before callback unsigned restState : 1; // state to return to on callback // lighting data unsigned shadeAlways : 1; signed amplitude : 8; unsigned freq : 8; unsigned wave : 4; unsigned phase : 8; unsigned shadeFloor : 1; unsigned shadeCeiling : 1; unsigned shadeWalls : 1; signed shade : 8; // panning data unsigned panAlways : 1; unsigned panFloor : 1; unsigned panCeiling : 1; unsigned drag : 1; // wind/water stuff unsigned underwater : 1; unsigned depth : 2; unsigned panVel : 8; unsigned panAngle : 11; unsigned wind : 1; // physical triggers unsigned decoupled : 1; unsigned triggerOnce : 1; unsigned isTriggered : 1; unsigned key : 3; unsigned triggerPush : 1; unsigned triggerImpact : 1; unsigned triggerExplode : 1; // exploding things, etc. unsigned triggerEnter : 1; unsigned triggerExit : 1; unsigned triggerWPush : 1; unsigned triggerReserved1 : 1; unsigned triggerReserved2 : 1; // movement data signed offCeilZ : 32; signed onCeilZ : 32; signed offFloorZ : 32; signed onFloorZ : 32; signed marker0 : 16; signed marker1 : 16; // unsigned crush : 1; unsigned ceilxpanFrac : 8; unsigned ceilypanFrac : 8; unsigned floorxpanFrac : 8; unsigned floorypanFrac : 8; unsigned interruptable : 1; unsigned pad1 : 31; }; void ShowUsage(void) { printf("Usage:\n"); printf(" CONVDB6 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; HEADER5 header5; HEADER6 header6; INFO5 info5; INFO6 info6; char filename[_MAX_PATH], bakfilename[_MAX_PATH]; short numsprites; int length; BYTE *buffer; strcpy(filename, filespec); 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(&header5, sizeof(HEADER5)); // check signature and major version number if ( (memcmp(header5.signature, kBloodMapSig, sizeof(header5.signature)) != 0) || ((header5.version >> 8) != (kBloodMapOldVersion >> 8)) ) { printf("not expected format, skipping\n"); return; } iob.Read(&info5, sizeof(INFO5)); numsectors = info5.numsectors; numwalls = info5.numwalls; numsprites = info5.numsprites; gSkyCount = 1 << info5.pskybits; iob.Read(pskyoff, gSkyCount * sizeof(pskyoff[0])); for (i = 0; i < numsectors; i++) { SECTOR5 sector5; iob.Read(§or5, sizeof(sector5)); sector[i].wallptr = sector5.wallptr; sector[i].wallnum = sector5.wallnum; sector[i].ceilingz = sector5.ceilingz; sector[i].floorz = sector5.floorz; sector[i].ceilingstat = sector5.ceilingstat; sector[i].floorstat = sector5.floorstat; sector[i].ceilingpicnum = sector5.ceilingpicnum; sector[i].ceilingslope = (sshort)(sector5.ceilingslope << 5); sector[i].ceilingshade = sector5.ceilingshade; sector[i].ceilingpal = sector5.ceilingpal; sector[i].ceilingxpanning = sector5.ceilingxpanning; sector[i].ceilingypanning = sector5.ceilingypanning; sector[i].floorpicnum = sector5.floorpicnum; sector[i].floorslope = (sshort)(sector5.floorslope << 5); sector[i].floorshade = sector5.floorshade; sector[i].floorpal = sector5.floorpal; sector[i].floorxpanning = sector5.floorxpanning; sector[i].floorypanning = sector5.floorypanning; sector[i].visibility = sector5.visibility; sector[i].filler = 0; sector[i].type = sector5.type; sector[i].hitag = sector5.hitag; sector[i].extra = sector5.extra; if ( sector[i].ceilingslope == 0 ) sector[i].ceilingstat &= ~kSectorSloped; if ( sector[i].floorslope == 0 ) sector[i].floorstat &= ~kSectorSloped; if (sector[i].extra > 0) { int j = sector[i].extra; XSECTOR5 xsector5; iob.Read(&xsector5, sizeof(XSECTOR5)); memset(&xsector[j], 0, sizeof(XSECTOR)); xsector[j].reference = xsector5.reference; xsector[j].state = xsector5.state; // trigger data xsector[j].busy = 0; xsector[j].data = xsector5.data; xsector[j].txID = xsector5.txID; xsector[j].rxID = xsector5.rxID; xsector[j].command = xsector5.command; xsector[j].triggerOn = xsector5.triggerOn; xsector[j].triggerOff = xsector5.triggerOff; xsector[j].busyTime = xsector5.busyTime; xsector[j].waitTime = xsector5.waitTime; xsector[j].restState = xsector5.restState; xsector[j].interruptable = xsector5.interruptable; // lighting data xsector[j].amplitude = xsector5.amplitude; xsector[j].freq = xsector5.freq; xsector[j].phase = xsector5.phase; xsector[j].wave = xsector5.wave; xsector[j].shadeAlways = xsector5.shadeAlways; xsector[j].shadeFloor = xsector5.shadeFloor; xsector[j].shadeCeiling = xsector5.shadeCeiling; xsector[j].shadeWalls = xsector5.shadeWalls; xsector[j].shade = xsector5.shade; // panning data xsector[j].panAlways = xsector5.panAlways; xsector[j].panFloor = xsector5.panFloor; xsector[j].panCeiling = xsector5.panCeiling; xsector[j].drag = xsector5.drag; // wind/water stuff xsector[j].underwater = xsector5.underwater; xsector[j].depth = xsector5.depth; xsector[j].panVel = xsector5.panVel; xsector[j].panAngle = xsector5.panAngle; xsector[j].wind = xsector5.wind; // physical triggers xsector[j].decoupled = xsector5.decoupled; xsector[j].triggerOnce = xsector5.triggerOnce; xsector[j].isTriggered = xsector5.isTriggered; xsector[j].key = xsector5.key; xsector[j].triggerPush = xsector5.triggerPush; xsector[j].triggerImpact = xsector5.triggerImpact; xsector[j].triggerExplode = xsector5.triggerExplode; xsector[j].triggerEnter = xsector5.triggerEnter; xsector[j].triggerExit = xsector5.triggerExit; xsector[j].triggerWPush = xsector5.triggerWPush; xsector[j].triggerReserved1 = xsector5.triggerReserved1; xsector[j].triggerReserved2 = xsector5.triggerReserved2; // movement data xsector[j].offCeilZ = xsector5.offCeilZ; xsector[j].onCeilZ = xsector5.onCeilZ; xsector[j].offFloorZ = xsector5.offFloorZ; xsector[j].onFloorZ = xsector5.onFloorZ; xsector[j].marker0 = xsector5.marker0; xsector[j].marker1 = xsector5.marker1; xsector[j].crush = 0; } } for (i = 0; i < numwalls; i++) { WALL5 wall5; iob.Read(&wall5, sizeof(wall5)); wall[i].x = wall5.x; wall[i].y = wall5.y; wall[i].point2 = wall5.point2; wall[i].nextwall = wall5.nextwall; wall[i].nextsector = wall5.nextsector; wall[i].cstat = wall5.cstat; wall[i].picnum = wall5.picnum; wall[i].overpicnum = wall5.overpicnum; wall[i].shade = wall5.shade; wall[i].pal = wall5.pal; wall[i].xrepeat = wall5.xrepeat; wall[i].yrepeat = wall5.yrepeat; wall[i].xpanning = wall5.xpanning; wall[i].ypanning = wall5.ypanning; wall[i].type = wall5.type; wall[i].hitag = wall5.hitag; wall[i].extra = wall5.extra; if (wall[i].extra > 0) { int j = wall[i].extra; XWALL5 xwall5; iob.Read(&xwall5, sizeof(XWALL5)); memset(&xwall[j], 0, sizeof(XWALL)); xwall[j].reference = xwall5.reference; xwall[j].state = xwall5.state; // trigger data xwall[j].busy = xwall5.busy; xwall[j].data = xwall5.data; xwall[j].txID = xwall5.txID; xwall[j].rxID = xwall5.rxID; xwall[j].command = xwall5.command; xwall[j].triggerOn = xwall5.triggerOn; xwall[j].triggerOff = xwall5.triggerOff; xwall[j].busyTime = xwall5.busyTime; xwall[j].waitTime = xwall5.waitTime; xwall[j].restState = xwall5.restState; xwall[j].interruptable = xwall5.interruptable; // panning data xwall[j].panAlways = xwall5.panAlways; xwall[j].panXVel = xwall5.panXVel; xwall[j].panYVel = xwall5.panYVel; // physical triggers xwall[j].decoupled = xwall5.decoupled; xwall[j].triggerOnce = xwall5.triggerOnce; xwall[j].isTriggered = xwall5.isTriggered; xwall[j].key = xwall5.key; xwall[j].triggerPush = xwall5.triggerPush; xwall[j].triggerImpact = xwall5.triggerImpact; xwall[j].triggerExplode = xwall5.triggerExplode; } } for (i = 0; i < numsprites; i++) { SPRITE5 sprite5; iob.Read(&sprite5, sizeof(sprite5)); sprite[i].x = sprite5.x; sprite[i].y = sprite5.y; sprite[i].z = sprite5.z; sprite[i].cstat = sprite5.cstat; sprite[i].picnum = sprite5.picnum; sprite[i].shade = sprite5.shade; sprite[i].pal = sprite5.pal; sprite[i].clipdist = sprite5.clipdist; sprite[i].filler = 0; sprite[i].xrepeat = sprite5.xrepeat; sprite[i].yrepeat = sprite5.yrepeat; sprite[i].xoffset = sprite5.xoffset; sprite[i].yoffset = sprite5.yoffset; sprite[i].sectnum = sprite5.sectnum; sprite[i].statnum = sprite5.statnum; sprite[i].ang = sprite5.ang; sprite[i].owner = sprite5.owner; sprite[i].xvel = sprite5.xvel; sprite[i].yvel = sprite5.yvel; sprite[i].zvel = sprite5.zvel; sprite[i].type = sprite5.type; sprite[i].flags = 0; // sprite5.mass; sprite[i].extra = sprite5.extra; if (sprite[i].extra > 0) { int j = sprite[i].extra; XSPRITE5 xsprite5; iob.Read(&xsprite5, sizeof(XSPRITE5)); memset(&xsprite[j], 0, sizeof(XSPRITE)); xsprite[j].reference = xsprite5.reference; xsprite[j].state = xsprite5.state; // trigger data xsprite[j].busy = 0; xsprite[j].txID = xsprite5.txID; xsprite[j].rxID = xsprite5.rxID; xsprite[j].command = xsprite5.command; xsprite[j].triggerOn = xsprite5.triggerOn; xsprite[j].triggerOff = xsprite5.triggerOff; xsprite[j].busyTime = xsprite5.busyTime; xsprite[j].waitTime = xsprite5.waitTime; xsprite[j].restState = xsprite5.restState; xsprite[j].interruptable = xsprite5.interruptable; xsprite[j].difficulty = xsprite5.difficulty; xsprite[j].soundKit = xsprite5.soundKit; // physical triggers xsprite[j].decoupled = xsprite5.decoupled; xsprite[j].triggerOnce = xsprite5.triggerOnce; xsprite[j].isTriggered = 0; xsprite[j].key = xsprite5.key; xsprite[j].triggerPush = xsprite5.triggerPush; xsprite[j].triggerImpact = xsprite5.triggerImpact; xsprite[j].triggerExplode = xsprite5.triggerExplode; xsprite[j].triggerPickup = xsprite5.triggerPickup; xsprite[j].triggerTouch = xsprite5.triggerTouch; xsprite[j].triggerSight = xsprite5.triggerSight; xsprite[j].triggerProximity = xsprite5.triggerProximity; xsprite[j].data1 = xsprite5.data1; xsprite[j].data2 = xsprite5.data2; xsprite[j].data3 = xsprite5.data3; xsprite[j].support = xsprite5.support; // respawn flags xsprite[j].respawn = xsprite5.respawn; xsprite[j].respawnTime = xsprite5.respawnTime; xsprite[j].launchMode = xsprite5.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].dudeGuard = 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].stateTimer = 0; } } } else ThrowError("Error reading map file", ES_ERROR); free(buffer); // convert slide types and angle markers for maps before 5.03 if ( (header5.version & 0xFF) < 3 ) { for ( i = 0; i < kMaxSectors; i++) { if ( sector[i].type == kSectorSlide ) { sector[i].type = kSectorSlideMarked; } } for ( i = 0; i < kMaxSprites; i++) if ( sprite[i].statnum == kStatMarker ) { if ( sprite[i].ang >= kAngle180 ) sprite[i].ang -= kAngle360; } } // add up all the elements in the file to determine the size of the buffer length = 0; length += sizeof(HEADER6); length += sizeof(INFO6); 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 = (BYTE *)malloc(length); IOBuffer iob(buffer, length); memcpy(header6.signature, kBloodMapSig, sizeof(header6.signature)); header6.version = kBloodMapVersion; iob.Write(&header6, sizeof(header6)); info6.x = info5.x; info6.y = info5.y; info6.z = info5.z; info6.angle = info5.angle; info6.sector = info5.sector; info6.pskybits = info5.pskybits; info6.visibility = info5.visibility; info6.songId = info5.songId; info6.parallax = info5.parallax; info6.mapRevisions = info5.mapRevisions; info6.numsectors = numsectors; info6.numwalls = numwalls; info6.numsprites = numsprites; iob.Write(&info6, sizeof(info6)); 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(bakfilename, "MA5"); rename(filename, bakfilename); 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); AddExtension(filespec, ".MAP"); char *drive, *dir; // separate the path from the filespec _splitpath2(filespec, buffer, &drive, &dir, NULL, NULL); _makepath(path, drive, dir, NULL, NULL); struct find_t fileinfo; unsigned r = _dos_findfirst(filespec, _A_NORMAL, &fileinfo); if (r != 0) printf("%s not found\n", filespec); 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( void ) { static SWITCH switches[] = { { NULL, 0, FALSE } }; int r; while ( (r = GetOptions(switches)) != GO_EOF ) { switch (r) { case GO_INVALID: QuitMessage("Invalid argument: %s", OptArgument); case GO_FULL: ProcessArgument(OptArgument); break; } } } void main(int argc) { printf("Blood Map 6 Converter Copyright (c) 1995 Q Studios Corporation\n"); if (argc < 2) ShowUsage(); ParseOptions(); // process the file list for (FNODE *n = head.next; n != &head; n = n->next) ProcessFile(n->name); }