#include #include #include #include #include #include #include #include "engine.h" #include "multi.h" #include "typedefs.h" #include "misc.h" #include "gameutil.h" #include "globals.h" #include "names.h" #include "key.h" #include "timer.h" #include "textio.h" #include "inifile.h" #include "screen.h" #include "view.h" #include "db.h" #include "debug4g.h" #include "error.h" #include "fire.h" #include "sectorfx.h" #include "helix.h" #include "getopt.h" #include "levels.h" #include "sound.h" #include "controls.h" #include "actor.h" #include "player.h" #include "resource.h" #include "protect.h" #include "triggers.h" #include "weapon.h" #include "options.h" #include "tile.h" #include "gui.h" #include "seq.h" #include "trig.h" #include "mirrors.h" #include "warp.h" #include "credits.h" #include "sfx.h" #include #define kAttrTitle (kColorRed * 16 + kColorYellow) EHF prevErrorHandler; static BOOL gForceMap = FALSE; short gPacketIndex = 0; BOOL ready2send = FALSE; static char playerreadyflag[kMaxPlayers]; #define kNetFifoSize 256 INPUT gNetInput[kMaxPlayers]; // input received from net players INPUT gFifoInput[kNetFifoSize][kMaxPlayers]; int gNetFifoClock; int gNetFifoHead, gNetFifoTail; ulong gFifoSendCRC[kNetFifoSize], gFifoReceiveCRC[kNetFifoSize]; int gSendCRCHead, gReceiveCRCHead, gCRCTail; BOOL bOutOfSync = FALSE; /*********************************************************************** * Function prototypes **********************************************************************/ enum { // kTweakDecel = 0, // kTweakSideDecel, kTweakFrontAccel, kTweakSideAccel, kTweakBackAccel, kTweakFrontSpeed1, kTweakFrontSpeed2, kTweakSideSpeed1, kTweakSideSpeed2, kTweakBackSpeed1, kTweakBackSpeed2, kTweakPace1, kTweakPace2, kMaxTweaks }; struct TWEAKS { int x; int y; int *pData; int nDefault; } tweakSet[ kMaxTweaks ] = { // {2, 2, &gPosture[0].decel, 0}, // {2, 20, &gPosture[0].sideDecel, 0}, {2, 38, &gPosture[0].frontAccel, 0}, {2, 56, &gPosture[0].sideAccel, 0}, {2, 74, &gPosture[0].backAccel, 0}, {2, 92, &gPosture[0].frontSpeed[0], 0}, {2, 110, &gPosture[0].frontSpeed[1], 0}, {2, 128, &gPosture[0].sideSpeed[0], 0}, {2, 146, &gPosture[0].sideSpeed[1], 0}, {2, 164, &gPosture[0].backSpeed[0], 0}, {90, 2, &gPosture[0].backSpeed[1], 0}, {90, 20, &gPosture[0].pace[0], 0}, {90, 38, &gPosture[0].pace[1], 0}, }; void TweakProfile( char *title ) { int i; EditNumber *enTweaks[kMaxTweaks]; // create the dialog Window dialog(0, 0, 319, 199, title); // these will automatically be destroyed when the dialog is destroyed for (i = 0; i < kMaxTweaks; i++) { tweakSet[i].nDefault = *tweakSet[i].pData; // initialize the defaults enTweaks[i] = new EditNumber(tweakSet[i].x, tweakSet[i].y, 80, 16, tweakSet[i].nDefault); dialog.Insert(enTweaks[i]); } ShowModal(&dialog); for (i = 0; i < kMaxTweaks; i++) { if ( dialog.endState != mrOk) *tweakSet[i].pData = tweakSet[i].nDefault; // restore the defaults else *tweakSet[i].pData = enTweaks[i]->value; } } void PreloadCache( void ) { long i; dprintf("Preload floor and ceiling tiles\n"); for (i = 0; i < numsectors; i++) { tilePreloadTile(sector[i].floorpicnum); tilePreloadTile(sector[i].ceilingpicnum); if ( sector[i].ceilingstat & kSectorParallax ) { for (int j = 1; j < gSkyCount; j++) tilePreloadTile(sector[i].ceilingpicnum + j); } } dprintf("Preload wall tiles\n"); for (i = 0; i < numwalls; i++) { tilePreloadTile(wall[i].picnum); if (wall[i].overpicnum >= 0) tilePreloadTile(wall[i].overpicnum); } dprintf("Preload sprite tiles\n"); for (i = 0; i < kMaxSprites; i++) { if (sprite[i].statnum < kMaxStatus) tilePreloadTile(sprite[i].picnum); } } void prepareboard( void ) { if ( !gForceMap ) levelGetName(gEpisodeId, gMapId); dbLoadMap(gLevelName, &startposx, &startposy, &startposz, &startang, &startsectnum); srand(gMapCRC); levelLoadDef(); scrLoadPLUs(); for ( int i = 0; i < kMaxPlayers; i++ ) { gStartZone[i].x = startposx; gStartZone[i].y = startposy; gStartZone[i].z = sector[startsectnum].floorz; gStartZone[i].sector = startsectnum; gStartZone[i].angle = startang; } // make sure the player starts out on the floor startposz = sector[startsectnum].floorz; seqKillAll(); // clear the current sequence list InitSectorFX(); InitPlayerStartZones(); actInit(); // initialize all the tag buckets dprintf("evInit()\n"); evInit(); gFrameClock = gGameClock = 0; // initialize external trigger masks for sprites, sectors, and walls dprintf("trInit()\n"); trInit(); PreloadCache(); //Map starts out blank; you map out what you see in 3D mode automapping = 1; for (i = 0; i < numplayers; i++) playerInit( i ); InitMirrors(); // start the music for the level sndPlaySong(gLevelSong, TRUE); gNetFifoClock = gFrameClock = gGameClock = 0; gNetFifoHead = gNetFifoTail = 0; gSendCRCHead = gReceiveCRCHead = gCRCTail = 0; } BYTE cheatAmmo[] = { KEY_Q, KEY_S, KEY_A, KEY_M, KEY_M, KEY_O }; BYTE cheatRate[] = { KEY_Q, KEY_S, KEY_R, KEY_A, KEY_T, KEY_E }; BYTE cheatGoto[] = { KEY_Q, KEY_S, KEY_G, KEY_O, KEY_T, KEY_O }; BYTE cheatJump[] = { KEY_Q, KEY_S, KEY_J, KEY_U, KEY_M, KEY_P }; BYTE cheatClip[] = { KEY_Q, KEY_S, KEY_C, KEY_L, KEY_I, KEY_P }; BYTE cheatKeys[] = { KEY_Q, KEY_S, KEY_K, KEY_E, KEY_Y, KEY_S }; BYTE cheatMaps[] = { KEY_Q, KEY_S, KEY_M, KEY_A, KEY_P, KEY_S }; BYTE cheatHurt[] = { KEY_Q, KEY_S, KEY_O, KEY_U, KEY_C, KEY_H }; BYTE cheatExplode[] = { KEY_Q, KEY_S, KEY_B, KEY_O, KEY_O, KEY_M }; BYTE cheatBurn[] = { KEY_Q, KEY_S, KEY_B, KEY_U, KEY_R, KEY_N }; BYTE cheatHigh[] = { KEY_Q, KEY_S, KEY_H, KEY_I, KEY_G, KEY_H }; BYTE cheatInviso[] = { KEY_Q, KEY_S, KEY_O, KEY_N, KEY_E, KEY_R, KEY_I, KEY_N, KEY_G }; BYTE cheatBeast[] = { KEY_Q, KEY_S, KEY_L, KEY_U, KEY_S, KEY_T }; BYTE cheatHealth[] = { KEY_Q, KEY_S, KEY_L, KEY_A, KEY_M, KEY_E }; BYTE cheatSeer[] = { KEY_Q, KEY_S, KEY_I, KEY_C, KEY_U }; BYTE cheatFrag[] = { KEY_Q, KEY_S, KEY_F, KEY_R, KEY_A, KEY_G }; BYTE cheatGod[] = { KEY_Q, KEY_S, KEY_G, KEY_O, KEY_D }; BYTE cheatSight[] = { KEY_Q, KEY_S, KEY_O, KEY_S, KEY_C , KEY_A , KEY_R }; BYTE cheatAim[] = { KEY_Q, KEY_S, KEY_A, KEY_I, KEY_M }; void LocalKeys( void ) { char buffer[80]; BYTE key; BYTE alt = keystatus[KEY_LALT] | keystatus[KEY_RALT]; while ( (key = keyGet()) != 0 ) { switch( key ) { case KEY_F1: // help/info screens TweakProfile( "Player Profile" ); break; case KEY_F2: // save game break; case KEY_F3: // load game break; case KEY_F4: // volume break; case KEY_F5: // detail break; case KEY_F6: // quicksave break; case KEY_F7: // end game break; case KEY_F8: // messages break; case KEY_F10: // quit game break; case KEY_F11: // gamma if (gGamma == gGammaLevels - 1) gGamma = 0; else gGamma = ClipHigh(gGamma + 1, gGammaLevels - 1); BloodINI.PutKeyInt("View", "Gamma", gGamma); scrSetGamma(gGamma); sprintf(buffer, "Gamma correction level %i", gGamma); viewSetMessage(buffer); break; case KEY_F12: // setup gViewIndex for different play and power-up modes if ( gNetGame && gNetPlayers > 1 ) { if ( powerupCheck(gMe, kItemCrystalBall - kItemBase) ) { gViewIndex = connectpoint2[gViewIndex]; if (gViewIndex == -1) gViewIndex = connecthead; } else { if (gNetMode == kNetModeTeams) { gViewIndex = connectpoint2[gViewIndex]; if (gViewIndex == -1) gViewIndex = connecthead; while ( gPlayer[gViewIndex].teamID != gMe->teamID ) { gViewIndex = connectpoint2[gViewIndex]; if (gViewIndex == -1) gViewIndex = connecthead; } gView = &gPlayer[gViewIndex]; } } } else { gViewIndex = connectpoint2[gViewIndex]; if (gViewIndex == -1) gViewIndex = connecthead; gView = &gPlayer[gViewIndex]; } break; case KEY_MINUS: if ( keystatus[KEY_G] ) { gGamma = ClipLow(gGamma - 1, 0); BloodINI.PutKeyInt("View", "Gamma", gGamma); scrSetGamma(gGamma); sprintf(buffer, "Gamma correction level %i", gGamma); viewSetMessage(buffer); } else if ( keystatus[KEY_D] ) { visibility = ClipHigh(visibility + 16, 4096); sprintf(buffer, "Depth cueing level %i", visibility); viewSetMessage(buffer); } else if ( keystatus[KEY_S] ) { gSpring = ClipLow(gSpring - 1, 1); BloodINI.PutKeyInt("Player", "SpringCoefficient", gSpring); gSpringPhaseInc = (int)(kAngle360 / (2 * 3.141592654 * (double)gSpring)); sprintf(buffer, "Spring coefficient = %i", gSpring); viewSetMessage(buffer); } else if (gViewMode == kView3D) { viewResizeView(gViewSize + 1); BloodINI.PutKeyInt("View", "Size", gViewSize); } break; case KEY_PLUS: if ( keystatus[KEY_G] ) { gGamma = ClipHigh(gGamma + 1, gGammaLevels - 1); BloodINI.PutKeyInt("View", "Gamma", gGamma); scrSetGamma(gGamma); sprintf(buffer, "Gamma correction level %i", gGamma); viewSetMessage(buffer); } else if ( keystatus[KEY_D] ) { visibility = ClipLow(visibility - 16, 128); sprintf(buffer, "Depth cueing level %i", visibility); viewSetMessage(buffer); } else if ( keystatus[KEY_S]) { gSpring++; BloodINI.PutKeyInt("Player", "SpringCoefficient", gSpring); gSpringPhaseInc = (int)(kAngle360 / (2 * 3.141592654 * (double)gSpring)); sprintf(buffer, "Spring coefficient = %i", gSpring); viewSetMessage(buffer); } else if (gViewMode == kView3D) { viewResizeView(gViewSize - 1); BloodINI.PutKeyInt("View", "Size", gViewSize); } break; case KEY_P: parallaxtype = (char)IncRotate(parallaxtype, 3); break; case KEY_V: if ( alt ) { if ( gViewPos > 0 ) gViewPos = (VIEWPOS)((gViewPos & 7) + 1); sprintf(buffer, "gViewPos = %i", gViewPos); viewSetMessage(buffer); } else { if ( gViewPos > 0 ) gViewPos = kViewPosCenter; else gViewPos = kViewPosBack; } break; case KEY_PRINTSCREEN: screencapture("captxxxx.pcx"); break; default: break; } } if (keystatus[KEY_MINUS] && (gViewMode == kView2D || gViewMode == kView2DIcon)) { gZoom = ClipLow(gZoom - (gZoom >> 6), 64); } if (keystatus[KEY_PLUS] && (gViewMode == kView2D || gViewMode == kView2DIcon)) { gZoom = ClipHigh(gZoom + (gZoom >> 6), 4096); } int nDelirium = powerupCheck(gView, kItemShroomDelirium - kItemBase); if ( nDelirium ) { static int timer = 0; timer += kFrameTicks; int maxTilt = kAngle30; int maxTurn = kAngle30; int maxPitch = 20; // this constant determine when the effect starts to wear off #define kWaneTime 512 if (nDelirium < kWaneTime) { int scale = (nDelirium << 16) / kWaneTime; maxTilt = mulscale16(maxTilt, scale); maxTurn = mulscale16(maxTurn, scale); maxPitch = mulscale16(maxPitch, scale); } gScreenTilt = mulscale30(Sin(timer * 2) / 2 + Sin(timer * 3) / 2, maxTilt); deliriumTurn = mulscale30(Sin(timer * 3) / 2 + Sin(timer * 4) / 2, maxTurn); deliriumPitch = mulscale30(Sin(timer * 4) / 2 + Sin(timer * 5) / 2, maxPitch); } else { // this code needs a different calculation, // and should probably be partially placed in ctrlGetInput if ( keystatus[KEY_LBRACE] ) gScreenTilt += kFrameTicks * 4; if ( keystatus[KEY_RBRACE] ) gScreenTilt -= kFrameTicks * 4; // put gScreenTilt in the range -kAngle180 to kAngle180 gScreenTilt = ((gScreenTilt + kAngle180) & kAngleMask) - kAngle180; // pull screen to vertical if ( gScreenTilt > 0 ) gScreenTilt = ClipLow(gScreenTilt - kFrameTicks * 2, 0); else if ( gScreenTilt < 0 ) gScreenTilt = ClipHigh(gScreenTilt + kFrameTicks * 2, 0); } if ( keyCompareStream( cheatRate, LENGTH(cheatRate)) ) { keyPokeStream(0); gShowFrameRate = !gShowFrameRate; } if ( keyCompareStream( cheatFrag, LENGTH(cheatFrag)) ) { keyPokeStream(0); gShowFrags = !gShowFrags; } if ( keyCompareStream( cheatAim, LENGTH(cheatAim)) ) { keyPokeStream(0); gAimReticle = !gAimReticle; } if ( gNetGame == FALSE ) { if ( keyCompareStream( cheatAmmo, LENGTH(cheatAmmo)) ) { keyPokeStream(0); for (int i = 0; i < kWeaponMax; i++) { gMe->hasWeapon[i] = TRUE; //gMe->weaponState[i] = 0; } for (i = 0; i < kAmmoMax; i++) { gMe->ammoCount[i] = gAmmoInfo[i].max; } viewSetMessage("Full armory granted!"); } if ( keyCompareStream( cheatKeys, LENGTH(cheatKeys)) ) { keyPokeStream(0); for (int i = 1; i <= 6; i++) gMe->hasKey[i] = (short)(kPicKey1 + i - 1); viewSetMessage("Full keychain granted!"); } if ( keyCompareStream( cheatMaps, LENGTH(cheatMaps)) ) { keyPokeStream(0); gFullMap = !gFullMap; if ( gFullMap ) viewSetMessage("Full atlas granted"); else viewSetMessage("Full atlas revoked"); } if ( keyCompareStream( cheatClip, LENGTH(cheatClip)) ) { keyPokeStream(0); gNoClip = !gNoClip; if ( gNoClip ) viewSetMessage("Non-clipped mode enabled"); else viewSetMessage("Non-clipped mode disabled"); } if ( keyCompareStream( cheatGoto, LENGTH(cheatGoto)) ) { keyPokeStream(0); long x = GetNumberBox("X=", gMe->sprite->x, gMe->sprite->x); long y = GetNumberBox("Y=", gMe->sprite->y, gMe->sprite->y); short nSector; updatesector(x, y, &nSector); if (nSector != -1) { changespritesect((short)gMe->nSprite, nSector); gMe->sprite->x = x; gMe->sprite->y = y; gMe->sprite->z = sector[nSector].floorz; gMe->sprite->zvel = 0; } } if ( keyCompareStream( cheatJump, LENGTH(cheatJump)) ) { keyPokeStream(0); long newEpisodeId = GetNumberBox("Episode (1..3)=", gEpisodeId, 1); long newMapId = GetNumberBox("Map (1..15)=", gMapId, 1); if ((newEpisodeId != 0) && (newMapId != 0)) { gEpisodeId = newEpisodeId; gMapId = newMapId; gForceMap = FALSE; ready2send = FALSE; prepareboard(); ready2send = TRUE; } viewResizeView(gViewSize); } if ( keyCompareStream( cheatHurt, LENGTH(cheatHurt)) ) { keyPokeStream(0); actDamageSprite(gMe->nSprite, gMe->nSprite, kDamagePummel, 100 << 4); viewSetMessage("Ouch!"); } if ( keyCompareStream( cheatExplode, LENGTH(cheatExplode)) ) { keyPokeStream(0); actDamageSprite(gMe->nSprite, gMe->nSprite, kDamageExplode, 100 << 4); viewSetMessage("Boom."); } if ( keyCompareStream( cheatBurn, LENGTH(cheatBurn)) ) { keyPokeStream(0); actAddBurnTime( gMe->nSprite, gMe->xsprite, kMaxBurnTime ); viewSetMessage("Crispy!"); } if ( keyCompareStream( cheatHigh, LENGTH(cheatHigh)) ) { keyPokeStream(0); if ( powerupCheck( gMe, kItemShroomDelirium - kItemBase ) ) powerupDeactivate( gMe, kItemShroomDelirium - kItemBase ); else powerupActivate( gMe, kItemShroomDelirium - kItemBase ); viewSetMessage("Cosmic, man!"); } if ( keyCompareStream( cheatGod, LENGTH(cheatGod)) ) { keyPokeStream(0); playerSetGodMode( gMe, !gMe->godMode ); if (gMe->godMode) viewSetMessage("Entering Broussard mode!"); else viewSetMessage("Back to normal mode!"); } if ( keyCompareStream( cheatInviso, LENGTH(cheatInviso)) ) { keyPokeStream(0); if ( powerupCheck( gMe, kItemLtdInvisibility - kItemBase ) ) { viewSetMessage("Deactivating the One Ring"); powerupDeactivate( gMe, kItemLtdInvisibility - kItemBase ); } else { viewSetMessage("Activating the One Ring"); powerupActivate( gMe, kItemLtdInvisibility - kItemBase ); } } if ( keyCompareStream( cheatSeer, LENGTH(cheatSeer)) ) { keyPokeStream(0); if ( powerupCheck( gMe, kItemCrystalBall - kItemBase ) ) { viewSetMessage("Deactivating the Crystal Ball"); powerupDeactivate( gMe, kItemCrystalBall - kItemBase ); } else { viewSetMessage("Activating the Crystal Ball"); powerupActivate( gMe, kItemCrystalBall - kItemBase ); } } if ( keyCompareStream( cheatBeast, LENGTH(cheatBeast)) ) { keyPokeStream(0); if (gMe->lifemode == kModeBeast) playerSetRace( gMe, kModeHuman, gMe->sprite->type ); else playerSetRace( gMe, kModeBeast, gMe->sprite->type ); } if ( keyCompareStream( cheatHealth, LENGTH(cheatHealth)) ) { keyPokeStream(0); gMe->xsprite->health = 200 << 4; viewSetMessage("Full health granted, you maggot!"); } if ( keyCompareStream( cheatSight, LENGTH(cheatSight)) ) { keyPokeStream(0); if ( powerupCheck( gMe, kItemRoseGlasses - kItemBase ) ) { viewSetMessage("Deactivating Oscar Sight"); powerupDeactivate( gMe, kItemRoseGlasses - kItemBase ); } else { viewSetMessage("Activating Oscar Sight"); powerupActivate( gMe, kItemRoseGlasses - kItemBase ); } } } if (*control[kAutomapToggle]) { *control[kAutomapToggle] = 0; if (gViewMode == kView3D) { gViewMode = kView2D; } else if (gViewMode == kView2D) { gViewMode = kView2DIcon; } else { gViewMode = kView3D; viewResizeView(gViewSize); } } if (gNetPlayers == 1) //Single player only keys { if (keystatus[KEY_INSERT]) //Insert - Insert player { keystatus[KEY_INSERT] = 0; if (numplayers < kMaxPlayers) { connectpoint2[numplayers-1] = numplayers; connectpoint2[numplayers] = -1; gViewIndex = myconnectindex = numplayers++; gView = gMe = &gPlayer[myconnectindex]; playerInit( myconnectindex ); } } if (keystatus[KEY_DELETE]) //Delete - Delete player { keystatus[KEY_DELETE] = 0; if (numplayers > 1) { // actDeletePlayer( myconnectindex ); // should probably add something like this! numplayers--; connectpoint2[numplayers-1] = -1; seqKill( SS_SPRITE, gPlayer[numplayers].sprite->extra ); actPostSprite( gPlayer[numplayers].nSprite, kStatFree ); gPlayer[numplayers].sprite = NULL; if (myconnectindex >= numplayers) myconnectindex = 0; gMe = &gPlayer[myconnectindex]; if (gViewIndex >= numplayers) gViewIndex = 0; gView = &gPlayer[gViewIndex]; } } if (keystatus[KEY_SCROLLLOCK]) //Scroll Lock { keystatus[KEY_SCROLLLOCK] = 0; myconnectindex = connectpoint2[myconnectindex]; if (myconnectindex < 0) myconnectindex = connecthead; gViewIndex = myconnectindex; gView = gMe = &gPlayer[myconnectindex]; } } } ulong CalcGameChecksum( void ) { ulong value = 0; value = Random(65536); // dprintf("Frame #%d: randCount1 = %d, randCount2 = %d, Random() = %4x\n", // gFrame, randCount1, randCount2, value); //value ^= CRC32(gPlayer, sizeof(gPlayer)); for ( int i = connecthead; i >= 0; i = connectpoint2[i] ) value ^= CRC32(gPlayer[i].sprite, sizeof(SPRITE)); return value; } /* void CheckMasterSwitch( void ) { int i, j; i = connecthead; j = connectpoint2[i]; while (j >= 0) { if (gPlayer[j].keyFlags.master) { connectpoint2[i] = connectpoint2[j]; connectpoint2[j] = connecthead; connecthead = (short)j; gGameClock = gFrameClock; if (connecthead == myconnectindex) viewSetMessage("Master"); else viewSetMessage("Slave"); gPlayer[j].keyFlags.master = 0; return; } i = j; j = connectpoint2[i]; } } */ /******************************************************************************* FUNCTION: ProcessFrame() DESCRIPTION: Main game processing function *******************************************************************************/ void ProcessFrame() { for ( int i = connecthead; i >= 0; i = connectpoint2[i] ) { gPlayerInput[i].syncFlags = gFifoInput[gNetFifoHead][i].syncFlags; gPlayerInput[i].buttonFlags = gFifoInput[gNetFifoHead][i].buttonFlags; // keys and weapon selection are non-destructive gPlayerInput[i].keyFlags.byte |= gFifoInput[gNetFifoHead][i].keyFlags.byte; if (gFifoInput[gNetFifoHead][i].newWeapon != 0) gPlayerInput[i].newWeapon = gFifoInput[gNetFifoHead][i].newWeapon; gPlayerInput[i].forward = gFifoInput[gNetFifoHead][i].forward; gPlayerInput[i].strafe = gFifoInput[gNetFifoHead][i].strafe; gPlayerInput[i].turn = gFifoInput[gNetFifoHead][i].turn; gPlayerInput[i].ticks = gFifoInput[gNetFifoHead][i].ticks; } gNetFifoHead = IncRotate(gNetFifoHead, kNetFifoSize); gFifoSendCRC[gSendCRCHead] = CalcGameChecksum(); gSendCRCHead = IncRotate(gSendCRCHead, kNetFifoSize); for (i = connecthead; i >= 0; i = connectpoint2[i]) { if ( gPlayerInput[i].keyFlags.restart ) { gPlayerInput[i].keyFlags.restart = 0; gForceMap = TRUE; gPaused = FALSE; ready2send = FALSE; prepareboard(); ready2send = TRUE; } if ( gPlayerInput[i].keyFlags.pause ) { gPlayerInput[i].keyFlags.pause = 0; gPaused = !gPaused; } } if (gPaused) return; for (i = connecthead; i >= 0; i = connectpoint2[i]) viewBackupPlayerLoc(i); trProcessBusy(); // process busy triggers evProcess(gFrameClock); // process event queue seqProcess(kFrameTicks); for (i = connecthead; i >= 0; i = connectpoint2[i]) { powerupProcess(&gPlayer[i]); // process the power-ups playerMove(&gPlayer[i], &gPlayerInput[i]); // Move player } actProcessSprites(); // Actor code: projectiles, etc. actPostProcess(); // clean up deleted sprites or sprites changing status sndProcess(); sfxUpdate3DSounds(); gFrame++; gFrameClock += kFrameTicks; } void UpdateNetFifo( void ) { for ( int i = connecthead; i >= 0; i = connectpoint2[i] ) { gFifoInput[gNetFifoTail][i] = gNetInput[i]; } gNetFifoTail = IncRotate(gNetFifoTail, kNetFifoSize); // CheckMasterSwitch(); } char packet[576]; inline char GetPacketByte(char * &p) { return *p++; } inline PutPacketByte(char * &p, int b) { *p++ = (char)b; } inline short GetPacketShort(char * &p) { short w = *(short *)p; p += 2; return w; } inline PutPacketShort(char * &p, int w) { *(short *)p = (short)w; p += 2; } inline long GetPacketLong(char * &p) { long l = *(long *)p; p += 4; return l; } inline PutPacketLong(char * &p, int l) { *(long *)p = (long)l; p += 4; } int GetPackets( void ) { short nPlayer, length; long i, j; char *p; int n = 0; int nMoveCount = 0; while ( (length = getpacket(&nPlayer, packet)) > 0) { n++; p = packet; switch( GetPacketByte(p) ) { case 0: // receive master sync buffer j = GetPacketShort(p); if (j != gPacketIndex++) ThrowError("Missing master packet", ES_ERROR); for (i = connecthead; i >= 0; i = connectpoint2[i]) { SYNCFLAGS syncFlags; syncFlags.byte = GetPacketByte(p); gNetInput[i].syncFlags.byte = syncFlags.byte; if (syncFlags.buttonChange) gNetInput[i].buttonFlags.byte = GetPacketByte(p); if (syncFlags.keyChange) gNetInput[i].keyFlags.byte = GetPacketByte(p); if (syncFlags.weaponChange) gNetInput[i].newWeapon = GetPacketByte(p); if (syncFlags.forwardChange) gNetInput[i].forward = GetPacketByte(p); if (syncFlags.turnChange) gNetInput[i].turn = GetPacketShort(p); if (syncFlags.strafeChange) gNetInput[i].strafe = GetPacketByte(p); gNetInput[i].ticks = GetPacketByte(p); } while ( p < &packet[length] ) { gFifoReceiveCRC[gReceiveCRCHead] = GetPacketLong(p); gReceiveCRCHead = IncRotate(gReceiveCRCHead, kNetFifoSize); } if ( gReceiveCRCHead != gCRCTail && gSendCRCHead != gCRCTail ) { bOutOfSync = FALSE; do { if ( gFifoReceiveCRC[gCRCTail] != gFifoSendCRC[gCRCTail] ) { dprintf("SYNC: gFifoReceiveCRC[%d] = %8x, gFifoSendCRC[%d] = %8x\n", gCRCTail, gFifoReceiveCRC[gCRCTail], gCRCTail, gFifoSendCRC[gCRCTail]); bOutOfSync = TRUE; } gCRCTail = IncRotate(gCRCTail, kNetFifoSize); } while ( gReceiveCRCHead != gCRCTail && gSendCRCHead != gCRCTail ); } UpdateNetFifo(); nMoveCount++; continue; case 1: // receive slave sync buffer SYNCFLAGS syncFlags; syncFlags.byte = GetPacketByte(p); gNetInput[nPlayer].syncFlags.byte = syncFlags.byte; if (syncFlags.buttonChange) gNetInput[nPlayer].buttonFlags.byte = GetPacketByte(p); if (syncFlags.keyChange) gNetInput[nPlayer].keyFlags.byte = GetPacketByte(p); if (syncFlags.weaponChange) gNetInput[nPlayer].newWeapon = GetPacketByte(p); if (syncFlags.forwardChange) gNetInput[nPlayer].forward = GetPacketByte(p); if (syncFlags.turnChange) gNetInput[nPlayer].turn = GetPacketShort(p); if (syncFlags.strafeChange) gNetInput[nPlayer].strafe = GetPacketByte(p); gNetInput[nPlayer].ticks = GetPacketByte(p); continue; case 250: playerreadyflag[nPlayer] = GetPacketByte(p); // if the message came from the master, acknowledge it if ( nPlayer == connecthead && playerreadyflag[connecthead] == 2 ) sendpacket(connecthead, packet, 2); break; case 255: //[255] (logout) keystatus[1] = 1; break; } break; } // try to prevent slave and master from getting into phase lock-step if ( myconnectindex != connecthead && nMoveCount != 1 ) { if (nMoveCount == 2) dprintf("%4d: Received 2 packets from master\n", gPacketIndex); // if (nMoveCount & 2) // gNetFifoClock += kFrameTicks / 2; // else // gNetFifoClock -= kFrameTicks / 2; } return n; } void GetInput( void ) { if ( gNetPlayers > 1 ) GetPackets(); // this doesn't currently do anything but Ken might add to this later if (getoutputcirclesize() >= 16) return; ctrlGetInput(); char *p = packet; // single player or master if (gNetPlayers == 1 || myconnectindex == connecthead) { // copy in local input gNetInput[myconnectindex].syncFlags.byte = gInput.syncFlags.byte; if (gInput.syncFlags.buttonChange) gNetInput[myconnectindex].buttonFlags.byte = gInput.buttonFlags.byte; if (gInput.syncFlags.keyChange) gNetInput[myconnectindex].keyFlags.byte = gInput.keyFlags.byte; if (gInput.syncFlags.weaponChange) gNetInput[myconnectindex].newWeapon = gInput.newWeapon; if (gInput.syncFlags.forwardChange) gNetInput[myconnectindex].forward = gInput.forward; if (gInput.syncFlags.turnChange) gNetInput[myconnectindex].turn = gInput.turn; if (gInput.syncFlags.strafeChange) gNetInput[myconnectindex].strafe = gInput.strafe; gNetInput[myconnectindex].ticks = gInput.ticks; if ( gNetPlayers > 1 ) { // build master packet PutPacketByte(p, 0); PutPacketShort(p, gPacketIndex++); for (int i = connecthead; i >= 0; i = connectpoint2[i]) { PutPacketByte(p, gNetInput[i].syncFlags.byte); if (gNetInput[i].syncFlags.buttonChange) PutPacketByte(p, gNetInput[i].buttonFlags.byte); if (gNetInput[i].syncFlags.keyChange) PutPacketByte(p, gNetInput[i].keyFlags.byte); if (gNetInput[i].syncFlags.weaponChange) PutPacketByte(p, gNetInput[i].newWeapon); if (gNetInput[i].syncFlags.forwardChange) PutPacketByte(p, gNetInput[i].forward); if (gNetInput[i].syncFlags.turnChange) PutPacketShort(p, gNetInput[i].turn); if (gNetInput[i].syncFlags.strafeChange) PutPacketByte(p, gNetInput[i].strafe); PutPacketByte(p, gNetInput[i].ticks); gNetInput[i].syncFlags.byte = 0; } while ( gCRCTail != gSendCRCHead ) { PutPacketLong(p, gFifoSendCRC[gCRCTail]); gCRCTail = IncRotate(gCRCTail, kNetFifoSize); // master increment SEND tail } // send packets to slaves for (i = connectpoint2[connecthead]; i >= 0; i = connectpoint2[i]) sendpacket((short)i, packet, (short)(p - packet)); } UpdateNetFifo(); } else //I am a SLAVE { // build slave packet PutPacketByte(p, 1); PutPacketByte(p, gInput.syncFlags.byte); if (gInput.syncFlags.buttonChange) PutPacketByte(p, gInput.buttonFlags.byte); if (gInput.syncFlags.keyChange) PutPacketByte(p, gInput.keyFlags.byte); if (gInput.syncFlags.weaponChange) PutPacketByte(p, gInput.newWeapon); if (gInput.syncFlags.forwardChange) PutPacketByte(p, gInput.forward); if (gInput.syncFlags.turnChange) PutPacketShort(p, gInput.turn); if (gInput.syncFlags.strafeChange) PutPacketByte(p, gInput.strafe); PutPacketByte(p, gInput.ticks); // send packet to master sendpacket((short)connecthead, packet, (short)(p - packet)); } } void faketimerhandler( void ) { if ( gGameClock >= gNetFifoClock && ready2send ) { gNetFifoClock += kFrameTicks; GetInput(); } } BOOL WaitForAllPlayers( void ) { long i, j, oldtotalclock; if (numplayers < 2) return TRUE; // if I'm the Master if (myconnectindex == connecthead) { for (j = 1; j <= 2; j++) { for (i = connectpoint2[connecthead]; i >= 0; i = connectpoint2[i]) playerreadyflag[i] = 0; oldtotalclock = gGameClock - 8; do { if (keystatus[1]) return FALSE; if ( gNetPlayers > 1 ) GetPackets(); if (gGameClock >= oldtotalclock + 8) { oldtotalclock = gGameClock; char *p = packet; PutPacketByte(p, 250); PutPacketByte(p, j); for(i = connectpoint2[connecthead]; i >= 0; i = connectpoint2[i]) if (playerreadyflag[i] != j) sendpacket((short)i, packet, 2); } for(i = connectpoint2[connecthead]; i >= 0; i = connectpoint2[i]) if (playerreadyflag[i] != j) break; } while (i >= 0); } } else { playerreadyflag[connecthead] = 0; while (playerreadyflag[connecthead] != 2) { if (keystatus[1]) return FALSE; if ( gNetPlayers > 1 ) GetPackets(); if (playerreadyflag[connecthead] == 1) { playerreadyflag[connecthead] = 0; sendpacket(connecthead, packet, 2); } } } return TRUE; } ErrorResult GameErrorHandler( const Error& error ) { // shut down the game engine if ( gNetGame ) { sendlogoff(); uninitmultiplayers(); } sndTerm(); timerRemove(); ctrlTerm(); UnlockClockStrobe(); uninitengine(); setvmode(gOldDisplayMode); // chain to the default error handler return prevErrorHandler(error); }; enum { kSwitchNet, kSwitchBloodBath, kSwitchTeams, }; SWITCH switches[] = { { "net", kSwitchNet, TRUE }, { "bloodbath", kSwitchBloodBath, FALSE }, { "teams", kSwitchBloodBath, FALSE }, { NULL, 0, FALSE }, }; /*********************************************************************** * Process command line arguments **********************************************************************/ void ParseOptions( void ) { char buffer[256]; int r; while ( (r = GetOptions(switches)) != GO_EOF ) { switch (r) { case GO_INVALID: sprintf(buffer, "Invalid argument: %s", OptArgument); ThrowError(buffer, ES_ERROR); case kSwitchNet: gNetGame = TRUE; dprintf("Net parameter is %s\n", OptArgument); break; case kSwitchBloodBath: // NET PARAMETER MUST COME FIRST: Can you add a CheckParm? gNetMode = kNetModeBloodBath; dprintf("Bloodbath network mode\n"); break; case kSwitchTeams: // NET PARAMETER MUST COME FIRST: Can you add a CheckParm? gNetMode = kNetModeTeams; dprintf("Team network mode\n"); break; case GO_FULL: strcpy(gLevelName, OptArgument); gForceMap = TRUE; break; } } } /*********************************************************************** * main() * * It all starts here. Have a Bloody day. * **********************************************************************/ void main( /* int argc, char *argv[] */ ) { char title[256]; gOldDisplayMode = getvmode(); sprintf(title, "BLOOD ALPHA BUILD [%s] -- DO NOT DISTRIBUTE", gBuildDate); tioInit(); tioCenterString(0, 0, tioScreenCols - 1, title, kAttrTitle); tioCenterString(tioScreenRows - 1, 0, tioScreenCols - 1, "Copyright (c) 1994, 1995 Q Studios Corporation", kAttrTitle); tioWindow(1, 0, tioScreenRows - 2, tioScreenCols); if ( _grow_handles(kRequiredFiles) < kRequiredFiles ) { tioPrint("Increase FILES=## value in CONFIG.SYS."); ThrowError("Not enough file handles available", ES_ERROR); } ParseOptions(); tioPrint("Loading INI file"); optLoadINI(); tioPrint("Initializing heap and resource system"); Resource::heap = new QHeap(dpmiDetermineMaxRealAlloc()); tioPrint("Initializing resource archive"); gSysRes.Init("BLOOD.RFF", "*.*"); gGuiRes.Init("GUI.RFF", NULL); CheckDemoDate(gSysRes); // install our error handler prevErrorHandler = errSetHandler(GameErrorHandler); tioPrint("Initializing 3D engine"); InitEngine(); tioPrint("Creating standard color lookups"); scrCreateStdColors(); tioPrint("Loading tiles"); if (tileInit() == 0) ThrowError("ART files not found", ES_ERROR); powerupInit(); // calc zoom table for power-up animations tioPrint("Loading cosine table"); trigInit(gSysRes); tioPrint("Initializing screen"); scrInit(); tioPrint("Initializing view subsystem"); viewInit(); tioPrint("Initializing dynamic fire"); FireInit(); tioPrint("Initializing weapon animations"); WeaponInit(); tioPrint("Installing keyboard handler"); keyInstall(); tioPrint("Initializing network users"); initmultiplayers(0, 0, 0); gNetPlayers = numplayers; gMe = gView = &gPlayer[myconnectindex]; gViewIndex = myconnectindex; // this must be done before initializing timer, because mouse interrupt hooks timer vector tioPrint("Loading control setup"); ctrlInit(); tioPrint("Installing timer"); LockClockStrobe(); timerRegisterClient(ClockStrobe, kTimerRate); timerInstall(); tioPrint("Initializing sound system"); sndInit(); sfxInit(); tioPrint("Waiting for network players"); WaitForAllPlayers(); scrSetGameMode(); scrSetGamma(gGamma); // if ( !gForceMap ) // credCredits(); viewResizeView(gViewSize); prepareboard(); gNetFifoClock = gFrameClock = gGameClock = 0; gNetFifoHead = gNetFifoTail = 0; gSendCRCHead = gReceiveCRCHead = gCRCTail = 0; ready2send = TRUE; //This is the whole game loop! while ( keystatus[KEY_ESC] == 0 && !syncstate ) { LocalKeys(); if ( gNetPlayers > 1 ) GetPackets(); faketimerhandler(); while ( gNetFifoHead != gNetFifoTail ) ProcessFrame(); if ( !gPaused ) gInterpolate = (gGameClock + kFrameTicks - gFrameClock) * (0x10000 / kFrameTicks); viewDrawScreen(); if (bOutOfSync) viewSetMessage("Out of Sync!"); if ( gEndLevelFlag ) { // fade out the current music sndFadeSong(4000); if ( gEndLevelFlag < 0 ) { // end episode or something special like that } else gMapId = gEndLevelFlag; gEndLevelFlag = 0; gForceMap = FALSE; ready2send = FALSE; prepareboard(); ready2send = TRUE; viewResizeView(gViewSize); } } ready2send = FALSE; setvmode(gOldDisplayMode); sndTerm(); dprintf("Removing timer\n"); timerRemove(); ctrlTerm(); UnlockClockStrobe(); if ( gNetGame ) { sendlogoff(); uninitmultiplayers(); } dprintf("uninitengine()\n"); uninitengine(); BloodINI.Save(); if (syncstate) printf("A packet was lost! (syncstate)\n"); dprintf("All subsystems shut down. Processing exit functions\n"); errSetHandler(prevErrorHandler); }