game.c

Go to the documentation of this file.
00001 
00025 #define QueuePlaceTank   __func_QueuePlaceTank
00026 #define QueuePlaceShell  __func_QueuePlaceShell
00027 #define QueuePlaceName   __func_QueuePlaceName
00028 
00029 #include "game.h"
00030 #include "gameconfig.h"
00031 #include "color.h"
00032 #include "net.h"
00033 #include "obstacle.h"
00034 #include "tanksprite.h"
00035 #include "playernet.h"
00036 #include "random.h"
00037 #include "notice.h"
00038 #include <assert.h>
00039 #include <stddef.h>
00040 #include <stdlib.h>
00041 #include <string.h>
00042 #ifdef USE_AUDIO
00043 #include "audio.h"
00044 #endif
00045 
00046 #undef QueuePlaceTank
00047 #undef QueuePlaceShell
00048 #undef QueuePlaceName
00049 
00050 #ifdef _MSC_VER
00051 // For some reason it seems Microsoft's math.h in Visual Studio 2003 only has
00052 // prototypes for the fabsf and sqrtf functions for C++. Or maybe I did
00053 // something wrong. I can't imagine this wouldn't be a major problem. The
00054 // following macros are a workaround. The problem doesn't exist in Visual
00055 // Studio 2005.
00056 #ifndef fabsf
00057 #define fabsf(x)  (float)fabs(x)
00058 #endif
00059 #ifndef sqrtf
00060 #define sqrtf(x)  (float)sqrt(x)
00061 #endif
00062 #endif
00063 
00070 static const Sint32 motionTable[16][2] = {
00071     // up
00072     { 0, -rounddiv(MOTION_0_1, MOTION_DIV) },
00073     // 22.5 right of up
00074     { rounddiv(MOTION_22_0, MOTION_DIV), -rounddiv(MOTION_22_1, MOTION_DIV) },
00075     // 45 right and up
00076     { rounddiv(MOTION_45_0, MOTION_DIV), -rounddiv(MOTION_45_1, MOTION_DIV) },
00077     // 22.5 up of right
00078     { rounddiv(MOTION_22_1, MOTION_DIV), -rounddiv(MOTION_22_0, MOTION_DIV) },
00079     // right
00080     { rounddiv(MOTION_0_1, MOTION_DIV), 0 },
00081     // 22.5 down of right
00082     { rounddiv(MOTION_22_1, MOTION_DIV), rounddiv(MOTION_22_0, MOTION_DIV) },
00083     // 45 right and down
00084     { rounddiv(MOTION_45_0, MOTION_DIV), rounddiv(MOTION_45_1, MOTION_DIV) },
00085     // 22.5 right of down
00086     { rounddiv(MOTION_22_0, MOTION_DIV), rounddiv(MOTION_22_1, MOTION_DIV) },
00087     // down
00088     { 0, rounddiv(MOTION_0_1, MOTION_DIV) },
00089     // 22.5 left of down
00090     { -rounddiv(MOTION_22_0, MOTION_DIV), rounddiv(MOTION_22_1, MOTION_DIV) },
00091     // 45 left and down
00092     { -rounddiv(MOTION_45_0, MOTION_DIV), rounddiv(MOTION_45_1, MOTION_DIV) },
00093     // 22.5 down of left
00094     { -rounddiv(MOTION_22_1, MOTION_DIV), rounddiv(MOTION_22_0, MOTION_DIV) },
00095     // left
00096     { -rounddiv(MOTION_0_1, MOTION_DIV), 0 },
00097     // 22.5 up of left
00098     { -rounddiv(MOTION_22_1, MOTION_DIV), -rounddiv(MOTION_22_0, MOTION_DIV) },
00099     // 45 left and up
00100     { -rounddiv(MOTION_45_0, MOTION_DIV), -rounddiv(MOTION_45_1, MOTION_DIV) },
00101     // 22.5 left of up
00102     { -rounddiv(MOTION_22_0, MOTION_DIV), -rounddiv(MOTION_22_1, MOTION_DIV) }
00103 };
00104 
00105 #ifdef DOXYGEN
00106 
00112 static const float normalTable[16][2] = {
00113     // up
00114     { 0, -FMOTION_0_1 },
00115     // 22.5 right of up
00116     { FMOTION_22_0, -FMOTION_22_1 },
00117     // 45 right and up
00118     { FMOTION_45_0, -FMOTION_45_1 },
00119     // 22.5 up of right
00120     { FMOTION_22_1, -FMOTION_22_0 },
00121     // right
00122     { FMOTION_0_1, 0 },
00123     // 22.5 down of right
00124     { FMOTION_22_1, FMOTION_22_0 },
00125     // 45 right and down
00126     { FMOTION_45_0, FMOTION_45_1 },
00127     // 22.5 right of down
00128     { FMOTION_22_0, FMOTION_22_1 },
00129     // down
00130     { 0, FMOTION_0_1 },
00131     // 22.5 left of down
00132     { -FMOTION_22_0, FMOTION_22_1 },
00133     // 45 left and down
00134     { -FMOTION_45_0, FMOTION_45_1 },
00135     // 22.5 down of left
00136     { -FMOTION_22_1, FMOTION_22_0 },
00137     // left
00138     { -FMOTION_0_1, 0 },
00139     // 22.5 up of left
00140     { -FMOTION_22_1, -FMOTION_22_0 },
00141     // 45 left and up
00142     { -FMOTION_45_0, -FMOTION_45_1 },
00143     // 22.5 left of up
00144     { -FMOTION_22_0, -FMOTION_22_1 }
00145 };
00146 #endif
00147 
00148 #if defined(USE_AUDIO) || defined(DOXYGEN)
00149 
00154 static unsigned int oldClosest = 0xFFFFFFFF;
00155 #endif
00156 
00157 void PlacePlayerLabel(Player *p) {
00158     if (p->labelName.renderer == NULL) return;
00159     // see if the whole frame will be rendered
00160     if (RenderWillRenderAll()) {
00161         // set the required physical location fields that are not set when
00162         // the whole frame is rendered
00163         p->tank.physDest.x = p->tank.loc.x + p->tank.parent->offX;
00164         p->tank.physDest.y = p->tank.loc.y + p->tank.parent->offY;
00165         p->tank.physDest.w = p->tank.loc.w;
00166         p->tank.physDest.h = p->tank.loc.h;
00167         // clip to the display region
00168         ClipRectToWH(&(p->tank.physDest), dInfo.pw, dInfo.ph);
00169     }
00170     // see if the tank is visible enough
00171     if ((p->tank.physDest.h >= (dInfo.luh << 2)) &&
00172     (p->tank.physDest.w >= (dInfo.luw << 2))) {
00173         // horizontally place the name centered with the tank
00174         p->labelName.loc.x = p->tank.loc.x + (dInfo.luw << 2) -
00175         (p->labelName.loc.w >> 1);
00176         // check for adequate room below the tank
00177         if (((dInfo.luw << 3) + p->tank.physLocY +
00178         p->labelName.loc.h) < dInfo.ph) {
00179             // vertically place beneth the tank
00180             p->labelName.loc.y = (dInfo.luw << 3) + p->tank.loc.y;
00181         }
00182         else {
00183             // vertically place above the tank
00184             p->labelName.loc.y = p->tank.loc.y - p->labelName.loc.h;
00185         }
00186         // place the label
00187         if (dInfo.showLabels)
00188             PlaceRenderItem(&(p->labelName));
00189     }
00190     // check for a visible name
00191     else if (p->labelName.enableDraw && dInfo.showLabels) {
00192         // make it invisible
00193         ClearRenderItem(&(p->labelName));
00194     }
00195 }
00196 
00197 void EnablePlayerLabels() {
00198     Player *p;
00199     int loop;
00200 
00201     // place all player's labels
00202     for (p = players, loop = MAX_PLAYERS; loop > 0; loop--, p++) {
00203         if (((p->flags & (PLAYER_ACTIVE | PLAYER_DEAD)) == PLAYER_ACTIVE) &&
00204         !p->labelName.enableDraw && p->labelName.renderer) {
00205             PlaceRenderItem(&(p->labelName));
00206         }
00207     }
00208     // set the flag
00209     dInfo.showLabels = TRUE;
00210 }
00211 
00212 void DisablePlayerLabels() {
00213     Player *p;
00214     int loop;
00215 
00216     // clear all player's labels
00217     for (p = players, loop = MAX_PLAYERS; loop > 0; loop--, p++) {
00218         if ((p->flags & PLAYER_ACTIVE) && p->labelName.enableDraw) {
00219             ClearRenderItem(&(p->labelName));
00220         }
00221     }
00222     // clear the flag
00223     dInfo.showLabels = FALSE;
00224 }
00225 
00233 static Uint32 batchTankDraw[rounddivup(MAX_PLAYERS, 32)] = { 0 };
00234 
00242 static Uint32 batchShellDraw[rounddivup(MAX_PLAYERS, 32)] = { 0 };
00243 
00251 static Uint32 batchNameDraw[rounddivup(MAX_PLAYERS, 32)] = { 0 };
00252 
00264 #define BatchIndex(pid)  ((pid) >> 5)
00265 
00279 #define BatchBit(pid)    (1 << ((pid) & 0x1F))
00280 
00288 #define QueuePlaceTank(pid) \
00289     batchTankDraw[BatchIndex(pid)] |= BatchBit(pid)
00290 
00298 #define QueuePlaceShell(pid) \
00299     batchShellDraw[BatchIndex(pid)] |= BatchBit(pid)
00300 
00309 #define QueuePlaceName(pid) \
00310     batchNameDraw[BatchIndex(pid)] |= BatchBit(pid)
00311 
00320 typedef void (*PlacementFunc)(void *data);
00321 
00335 static void BatchRender(Uint32 *batch, PlacementFunc placement, long offset) {
00336     Player *p;
00337     int index, bit;
00338     Uint32 byte;
00339     // loop through all the batch flags
00340     for (index = rounddivup(MAX_PLAYERS, 32), p = players; index > 0;
00341     batch++, index--) {
00342         // see if any of these 32 items need to be placed
00343         if (*batch) {
00344             // at least one shell needs placement, so consider them 8 at a time
00345             for (bit = 1, byte = 0xFF; byte; byte <<= 8) {
00346                 // see if any of these 8 items need to be placed
00347                 if (*batch & byte) {
00348                     // consider these 8 items one at a time
00349                     for (; bit & byte; p++, bit <<= 1) {
00350                         // place this item?
00351                         if (*batch & bit) {
00352                             (*placement)(((char*)p) + offset);
00353                         }
00354                     }
00355                 }
00356                 else {
00357                     // none of these 8 items need placement; advance past them
00358                     p += 8;
00359                     bit <<= 8;
00360                 }
00361             }
00362         }
00363         else {
00364             // none of these 32 items need placement; advance past them
00365             p += 32;
00366         }
00367         // clear the batch flags
00368         *batch = 0;
00369     }
00370 }
00371 
00372 void RenderGame() {
00373     // batch render tanks
00374     BatchRender(batchTankDraw, (PlacementFunc)PlaceRenderItem,
00375     offsetof(Player, tank));
00376     // batch render shells
00377     BatchRender(batchShellDraw, (PlacementFunc)PlaceRenderItem,
00378     offsetof(Player, shell));
00379     // if player labes are currently visible . . .
00380     if (dInfo.showLabels) {
00381         // batch render tanks
00382         BatchRender(batchNameDraw, (PlacementFunc)PlacePlayerLabel, 0);
00383     }
00384 }
00385 
00386 void MoveTank(Player *p, int factor) {
00387     int tempLoc;
00388     // forward motion
00389     if (factor > 0) {
00390         // move the tank
00391         p->x += motionTable[(p->rot >> 8) & 0xF][0] * factor;
00392         p->y += motionTable[(p->rot >> 8) & 0xF][1] * factor;
00393     }
00394     // backward motion
00395     else if (factor < 0) {
00396         p->x += (motionTable[(p->rot >> 8) & 0xF][0] * factor) >> 1;
00397         p->y += (motionTable[(p->rot >> 8) & 0xF][1] * factor) >> 1;
00398     }
00399     // check for out-of-bounds tank and correct
00400     if (gameOpts.width > 0) {
00401         if (p->x < 0)
00402             p->x = 0;
00403         else if (p->x > ((gameOpts.width - 8) << 8))
00404             p->x = (gameOpts.width - 8) << 8;
00405         if (p->y < 0)
00406             p->y = 0;
00407         else if (p->y > ((gameOpts.height - 8) << 8))
00408             p->y = (gameOpts.height - 8) << 8;
00409     }
00410     // see if this tank needs to be drawn
00411     if ((tempLoc = (p->y >> 8) * dInfo.luh) != p->tank.loc.y) {
00412         // place the item accordingly
00413         p->tank.loc.y = tempLoc;
00414         p->tank.loc.x = (p->x >> 8) * dInfo.luw;
00415         // flag the tank and the player name label for a redraw
00416         QueuePlaceTank(p->pid);
00417         // put the label in place
00418         QueuePlaceName(p->pid);
00419     }
00420     else if ((tempLoc = (p->x >> 8) * dInfo.luw) != p->tank.loc.x) {
00421         // place the item accordingly
00422         p->tank.loc.x = tempLoc;
00423         QueuePlaceTank(p->pid);
00424         // put the label in place
00425         QueuePlaceName(p->pid);
00426     }
00427 }
00428 
00429 void MoveShell(Player *p) {
00430     // record distance traveled and check for an uninitalized shell
00431     if (p->shellDist++ == 0) {
00432         // place the shell on the tank
00433         p->shellX = p->x + (shellInitLoc[(p->rot >> 8) & 0xF][0] << 8);
00434         p->shellY = p->y + (shellInitLoc[(p->rot >> 8) & 0xF][1] << 8);
00435         // set the shell's rotation to match the tank's
00436         p->shellRot = p->rot;
00437         goto MoveShell_placeShell;
00438     }
00439     // check for maximum distance
00440     else if (p->flags & PLAYER_PONG) { // test the pong flag once
00441         if (p->shellDist > SHELL_PONG_DIST) {
00442             MoveShell_killShell:
00443             // quit rendering the shell
00444             batchShellDraw[BatchIndex(p->pid)] &= ~BatchBit(p->pid);
00445             ClearRenderItem(&(p->shell));
00446             // flag the shot as no longer in flight
00447             p->flags &= ~PLAYER_SHOT;
00448             // don't move the shell
00449             return;
00450         }
00451         // here is were the bouncing of the shell can be done
00452     }
00453     else if (p->shellDist > SHELL_GUIDED_DIST) {
00454         goto MoveShell_killShell;
00455     }
00456     // forward motion
00457     p->shellX += motionTable[(p->shellRot >> 8) & 0xF][0] * MOTION_SHELL_FACTOR;
00458     p->shellY += motionTable[(p->shellRot >> 8) & 0xF][1] * MOTION_SHELL_FACTOR;
00459     // kill out-of-bounds shells
00460     if ((p->shellX < 0) || (p->shellY < 0) ||
00461     ((p->shellX >> 8) > gameOpts.width) || ((p->shellY >> 8) > gameOpts.height))
00462         goto MoveShell_killShell;
00463     // see if the shell has moved a visible amount
00464     if ((p->shell.loc.x != (p->shellX >> 8)) ||
00465     (p->shell.loc.y != (p->shellY >> 8))) {
00466         MoveShell_placeShell:
00467         // setup the shell's location
00468         p->shell.loc.x = (p->shellX >> 8) * dInfo.luh;
00469         p->shell.loc.y = (p->shellY >> 8) * dInfo.luh;
00470         // place it onscreen
00471         QueuePlaceShell(p->pid);
00472     }
00473 }
00474 
00484 static void RotateTank(Player *p, Sint16 rot) {
00485     Sint16 oldRot;
00486     // keep the old rotation
00487     oldRot = p->rot;
00488     // compute the new rotation
00489     p->rot += rot;
00490     // check for a change and a need for a redraw
00491     if (!p->tank.placed && ((oldRot & 0xF00) != (p->rot & 0xF00))) {
00492         // record that the whole tank must be redrawn
00493         QueuePlaceTank(p->pid);
00494         //PlaceRenderItem(&(p->tank));
00495     }
00496     // see if a non-pong shell is in use
00497     if ((p->flags & PLAYER_SHOT) && !(p->flags & PLAYER_PONG)) {
00498         // adjust the shell's rotation to match
00499         p->shellRot = p->rot;
00500     }
00501 }
00502 
00503 void SpawnTank(Player *p) {
00504     Player *c;
00505     SDL_Rect *rect;
00506     int attempts = 8, maxArea = gameOpts.spawnAreas[p->team], loop;
00507     Uint32 rndnum;
00508     Bool bad;
00509     // this function is only for servers
00510     assert(networkState == NETSTATE_SERVER);
00511     // up to some maximum tries . . .
00512     do {
00513         // pick a spawn area at random
00514         rndnum = RandNum();
00515         p->rot = rndnum >> 16;
00516         rect = &(spawnAreas[p->team][rndnum % maxArea]);
00517         // pick a spot in the area at random
00518         rndnum = RandNum();
00519         p->x = (p->tank.loc.x = (rect->x + (rndnum & 0xFFFF) % rect->w)) << 8;
00520         p->y = (p->tank.loc.y = (rect->y + (rndnum >> 16) % rect->h)) << 8;
00521         p->tank.loc.x *= dInfo.luw;
00522         p->tank.loc.y *= dInfo.luh;
00523         // might be good
00524         bad = FALSE;
00525         // max tries?
00526         if (--attempts == 0) {
00527             // use this attempt no matter what
00528             break;
00529         }
00530         // check for a collision with other tanks
00531         for (c = players, loop = MAX_PLAYERS; loop > 0; loop--, c++) {
00532             // is this tank not collidable?
00533             if ((c == p) || !(c->flags & PLAYER_ACTIVE) ||
00534             (c->flags & PLAYER_DEAD)) {
00535                 // c is p, or c isn't in the game, or c is dead
00536                 continue;
00537             }
00538             // check for collision
00539             if (IsCollidingTank(p, c)) {
00540                 // not good
00541                 bad = TRUE;
00542                 break;
00543             }
00544         }
00545     } while (bad);
00546     // finalize tank's position
00547     QueuePlaceTank(p->pid);
00548     QueuePlaceName(p->pid);
00549     // make the tank not dead
00550     p->flags = (p->flags & ~PLAYER_DEAD) | PLAYER_UPDATE_TANK |
00551     PLAYER_UPDATE_DFS;
00552     p->deadTime = 0;
00553 }
00554 
00555 void HandleInput(Uint8 *eventState) {
00556     // get a reference to the local player
00557     Player *p = &(players[localPlayer]);
00558     // do a limited set for dead players
00559     if (!(p->flags & PLAYER_DEAD)) {
00560         // new request to move forward
00561         if (eventState[EC_FORWARD] == 1) {
00562             // flag the request as handled
00563             eventState[EC_FORWARD]++;
00564             // set the forward motion flag and request the player's data be sent
00565             // over the network
00566             p->flags |= PLAYER_FORWARD | PLAYER_UPDATE_TANK;
00567             #ifdef USE_AUDIO
00568             // play the movement sound
00569             oldClosest = 0;
00570             //PlaySound(SND_MOVE, SND_CH_LOCALTANK, -1);
00571             #endif
00572         }
00573         // new request to stop forward motion
00574         else if ((eventState[EC_FORWARD] == 0) && (p->flags & PLAYER_FORWARD)) {
00575             // clear the forward motion flag and request the player's data be
00576             // sent over the network
00577             p->flags = (p->flags & ~PLAYER_FORWARD) | PLAYER_UPDATE_TANK;
00578             #ifdef USE_AUDIO
00579             if (!(p->flags & PLAYER_BACKWARD)) {
00580                 // play the idle engine sound
00581                 oldClosest = 0;
00582                 //PlaySound(SND_IDLE, SND_CH_LOCALTANK, -1);
00583             }
00584             #endif
00585         }
00586         // new request to move backward
00587         if (eventState[EC_BACKWARD] == 1) {
00588             // flag the request as handled
00589             eventState[EC_BACKWARD]++;
00590             // set the backward motion flag and request the player's data be
00591             // sent over the network
00592             p->flags |= PLAYER_BACKWARD | PLAYER_UPDATE_TANK;
00593             #ifdef USE_AUDIO
00594             // play the movement sound
00595             oldClosest = 0;
00596             //PlaySound(SND_MOVE, SND_CH_LOCALTANK, -1);
00597             #endif
00598         }
00599         else if ((eventState[EC_BACKWARD] == 0) && (p->flags & PLAYER_BACKWARD)) {
00600             // clear the backward motion flag and request the player's data be
00601             // sent over the network
00602             p->flags = (p->flags & ~PLAYER_BACKWARD) | PLAYER_UPDATE_TANK;
00603             #ifdef USE_AUDIO
00604             if (!(p->flags & PLAYER_FORWARD)) {
00605                 // play the idle engine sound
00606                 oldClosest = 0;
00607                 //PlaySound(SND_IDLE, SND_CH_LOCALTANK, -1);
00608             }
00609             #endif
00610         }
00611         if (eventState[EC_ROTRIGHT] == 1) {
00612             eventState[EC_ROTRIGHT]++;
00613             p->flags |= PLAYER_STARTROTRIGHT | PLAYER_UPDATE_TANK;
00614         }
00615         else if ((eventState[EC_ROTRIGHT] == 0) && p->flags & PLAYER_ROTRIGHT) {
00616             p->flags = (p->flags & ~PLAYER_ROTRIGHT) | PLAYER_UPDATE_TANK;
00617         }
00618         if (eventState[EC_ROTLEFT] == 1) {
00619             eventState[EC_ROTLEFT]++;
00620             p->flags |= PLAYER_STARTROTLEFT | PLAYER_UPDATE_TANK;
00621         }
00622         else if ((eventState[EC_ROTLEFT] == 0) && p->flags & PLAYER_ROTLEFT) {
00623             p->flags = (p->flags & ~PLAYER_ROTLEFT) | PLAYER_UPDATE_TANK;
00624         }
00625     }
00626     // Check for request to fire while there is no shell in flight for the
00627     // local player. Unlike the other inputs, this one will be serviced each
00628     // time it changes the game state rather than when the user changes the
00629     // input.
00630     if (eventState[EC_FIRE] == 1) {
00631         // if the player has been dead long enough, this is a request to spawn
00632         if ((p->flags & PLAYER_DEAD) && (p->deadTime > DEAD_TIME)) {
00633             // must not be taken as an immediate request to fire next frame
00634             eventState[EC_FIRE]++;
00635             // for servers . . .
00636             if (networkState == NETSTATE_SERVER) {
00637                 // spwan the player
00638                 SpawnTank(p);
00639             }
00640             // for clients . . .
00641             else {
00642                 // send a spawn request to the server
00643                 SendSpawnRequest();
00644             }
00645             #ifdef USE_AUDIO
00646             // start the idle engine sound
00647             PlaySound(SND_IDLE, SND_CH_TANK, -1);
00648             #endif
00649         }
00650         // an input to shoot
00651         else if ((p->flags & (PLAYER_SHOT | PLAYER_DEAD)) == 0) {
00652             // set the flag that brings the shell into existance and
00653             // request a network update
00654             p->flags |= PLAYER_SHOT | PLAYER_UPDATE_SHELL;
00655             // initalize the shell
00656             p->shellDist = 0;
00657             // place the shell later to avoid rendering issues that might be
00658             // caused by the tank moving based on the input
00659             #ifdef USE_AUDIO
00660             // play the shooting sound
00661             PlaySound(SND_SHOOT, SND_CH_LOCALSHOOT, 0);
00662             #endif
00663         }
00664     }
00665 }
00666 
00673 static void FixTankCollision(Player *p) {
00674     // check for motion on tank p
00675     if (p->flags & (PLAYER_FORWARD | PLAYER_BACKWARD | PLAYER_ROTRIGHT |
00676     PLAYER_ROTLEFT)) {
00677         // move in the opposite direction
00678         if (p->flags & PLAYER_FORWARD) {
00679             MoveTank(p, -10);
00680         }
00681         else if (p->flags & PLAYER_BACKWARD) {
00682             MoveTank(p, 8);
00683         }
00684         // rotate in the opposite direction
00685         else if (p->flags & PLAYER_ROTRIGHT) {
00686             RotateTank(p, -64);
00687         }
00688         else if (p->flags & PLAYER_ROTLEFT) {
00689             RotateTank(p, 64);
00690         }
00691         // for the server . . .
00692         if (networkState == NETSTATE_SERVER) {
00693             // send out a network update for this tank
00694             p->flags |= PLAYER_UPDATE_TANK;
00695         }
00696     }
00697 }
00698 
00699 void UpdatePlayers(Uint32 time) {
00700     // Stores the updates to scores from this frame. Used to produce the
00701     // ::MSG_TYPE_SCORE network messages.
00702     ScoreUpdate scores[MAX_PLAYERS / 2];
00703     int numScores = 0, loop, inner;
00704     // number of players still out-of-date
00705     int outdated;
00706     #ifdef USE_AUDIO
00707     unsigned int dist, closest = 0xFFFFFFFF;
00708     Player *local = &(players[localPlayer]);
00709     #endif
00710     Player *p, *c;
00711     Obstacle *obst;
00712 
00713     // loop until all players are at least up-to-date
00714     do {
00715 
00720         // clear the number of players still needing an update
00721         outdated = 0;
00722         // loop to update player locations
00723         for (p = players, loop = MAX_PLAYERS; loop > 0; loop--, p++) {
00724             // if this player is inactive or is already current . . .
00725             if (!(p->flags & PLAYER_ACTIVE) || (p->localUpdate >= time))
00726                 // skip this player
00727                 continue;
00728             // increment the time
00729             p->localUpdate += FRAME_DURATION;
00730             // check for a dead player
00731             if (p->flags & PLAYER_DEAD) {
00732                 // count the time spent dead and see if the tank should not stay
00733                 // dead
00734                 if (p->deadTime == DEAD_TIME) {
00735                     // stop drawing the player
00736                     batchTankDraw[BatchIndex(p->pid)] &= ~BatchBit(p->pid);
00737                     ClearRenderItem(&(p->tank));
00738                     ClearRenderItem(&(p->labelName));
00739                     p->deadTime++;
00740                 }
00741                 else if (p->deadTime < DEAD_TIME) {
00742                     // Explode the tank!  By explode, I mean spin really fast.
00743                     RotateTank(p, 256);
00744                     p->deadTime++;
00745                 }
00746             }
00747             // for live players . . .
00748             else {
00749                 // check for forward motion
00750                 if (p->flags & PLAYER_FORWARD) {
00751                     MoveTank(p, 1);
00752                 }
00753                 // check for backward motion
00754                 if (p->flags & PLAYER_BACKWARD) {
00755                     MoveTank(p, -1);
00756                 }
00757                 // check for the start of a right turn
00758                 if (p->flags & PLAYER_STARTROTRIGHT) {
00759                     // turn started; flag a continuing turn
00760                     p->flags = (p->flags & ~PLAYER_STARTROTRIGHT) |
00761                     PLAYER_ROTRIGHT;
00762                     RotateTank(p, 64);
00763                 }
00764                 // check for a continuing right turn
00765                 else if (p->flags & PLAYER_ROTRIGHT) {
00766                     RotateTank(p, 16);
00767                 }
00768                 // check for the start of a left turn
00769                 if (p->flags & PLAYER_STARTROTLEFT) {
00770                     // turn started; flag a continuing turn
00771                     p->flags = (p->flags & ~PLAYER_STARTROTLEFT) |
00772                     PLAYER_ROTLEFT;
00773                     RotateTank(p, -64);
00774                 }
00775                 // check for a continuing left turn
00776                 else if (p->flags & PLAYER_ROTLEFT) {
00777                     RotateTank(p, -16);
00778                 }
00779             }
00780             // see if the player has a shell in flight
00781             if (p->flags & PLAYER_SHOT) {
00782                 // move the shell
00783                 MoveShell(p);
00784             }
00785         }
00786         // loop to detect and correct collisions
00787         for (p = players, loop = MAX_PLAYERS; loop > 0; loop--, p++) {
00788             // only check collisions on active players
00789             if (!(p->flags & PLAYER_ACTIVE) || ((p->flags & PLAYER_DEAD) &&
00790             (p->deadTime > DEAD_TIME))) {
00791                 continue;
00792             }
00793             // check collisions for this player against others
00794             if (networkState == NETSTATE_SERVER) {
00795                 c = players;
00796                 inner = MAX_PLAYERS;
00797             }
00798             else {
00799                 c = p + 1;
00800                 inner = loop - 1;
00801             }
00802             for ( ; inner > 0; inner--, c++) {
00803                 // see if player c is not active or dead
00804                 if ((c == p) || !(c->flags & PLAYER_ACTIVE) ||
00805                 ((c->flags & PLAYER_DEAD) && (c->deadTime > DEAD_TIME))) {
00806                     // do not collide with this player
00807                     continue;
00808                 }
00809                 // if this code is for the server . . .
00810                 if (networkState == NETSTATE_SERVER) {
00811                     // Check for a shell collision. Only the server may decide a
00812                     // player has been hit and count the score.
00813                     if ((c->flags & PLAYER_SHOT) && IsCollidingShell(p, c)) {
00814                         // c hit p!
00815                         // get rid of the shell
00816                         c->flags &= ~PLAYER_SHOT;
00817                         ClearRenderItem(&(c->shell));
00818                         batchShellDraw[BatchIndex(c->pid)] &= ~BatchBit(c->pid);
00819                         // force an update to be sent
00820                         c->flags |= PLAYER_UPDATE_DFS;
00821                         // see if p was not dead
00822                         if (!(p->flags & PLAYER_DEAD)) {
00823                             // reset the time the player has spent dead
00824                             p->deadTime = 0;
00825                             // set the dead flag and force an update to be sent
00826                             p->flags |= PLAYER_DEAD | PLAYER_UPDATE_DFS;
00827                             // clear the movement flags
00828                             p->flags &= ~(PLAYER_FORWARD | PLAYER_BACKWARD |
00829                             PLAYER_ROTRIGHT | PLAYER_ROTLEFT | PLAYER_SHOT);
00830                             // remove the dead player's shell
00831                             ClearRenderItem(&(p->shell));
00832                             batchShellDraw[BatchIndex(p->pid)] &=
00833                             ~BatchBit(p->pid);
00834                             // check for a team match
00835                             if (c->team == p->team) {
00836                                 // is p in good standing?
00837                                 if (p->kills >= 0) {
00838                                     // count this against c
00839                                     c->kills -= 4;
00840                                 }
00841                                 // else, p probably deserved it
00842                             }
00843                             else {
00844                                 // count the kill; check for non-CTF match
00845                                 if ((++c->kills > 0) &&
00846                                 !(gameOpts.flags & OPT_ISCTF)) {
00847                                     // count another kill for the team
00848                                     gameOpts.teamScore[c->team]++;
00849                                     // flag the need to update the score
00850                                     gameOpts.flags |= OPT_UPDATESCORE;
00851                                 }
00852                             }
00853                             // record the score and show a notice on-screen
00854                             AddScoreNotice(scores[numScores].killerId = c->pid,
00855                             scores[numScores].killedId = p->pid, c->kills);
00856                             numScores++;
00857                             #ifdef USE_AUDIO
00858                             // is p the local (server) player?
00859                             if (p == local) {
00860                                 // play the death sound
00861                                 PlayLocalDeath();
00862                             }
00863                             else {
00864                                 // compute the square of the distance
00865                                 int distsq = DistSq(p, local);
00866                                 // close enough to hear?
00867                                 if (distsq < (MAX_SQ_DIST >> 1)) {
00868                                     // play the death sound
00869                                     PlayTankKilled(distsq);
00870                                 }
00871                             }
00872                             #endif
00873                         }
00874                     }
00875                     // finished with O(n*2) stuff; make rest of loop O(n log n)
00876                     if (inner > loop) continue;
00877                 }
00878                 // check for a tank to tank collision
00879                 if (IsCollidingTank(p, c)) {
00880                     // fix the problem
00881                     FixTankCollision(p);
00882                     FixTankCollision(c);
00883                 }
00884             }
00885             // iterate through the obstacles
00886             for (inner = gameOpts.obstacles, obst = obstacles; inner > 0;
00887             obst++, inner--) {
00888                 // check for a collision between a moving tank and an obstacle
00889                 if ((p->flags & (PLAYER_FORWARD | PLAYER_BACKWARD |
00890                 PLAYER_ROTRIGHT | PLAYER_ROTLEFT)) &&
00891                 IsCollidingObstacle(p, obst)) {
00892                     // for the server . . .
00893                     if (networkState == NETSTATE_SERVER) {
00894                         // send out a network update the colliding tank
00895                         p->flags |= PLAYER_UPDATE_TANK;
00896                     }
00897                     // check for motion
00898                     if (p->flags & PLAYER_FORWARD) {
00899                         MoveTank(p, -10);
00900                         goto UpdatePlayers_contObst;
00901                     }
00902                     else if (p->flags & PLAYER_BACKWARD) {
00903                         MoveTank(p, 8);
00904                         goto UpdatePlayers_contObst;
00905                     }
00906                     // check for rotation
00907                     if (p->flags & PLAYER_ROTRIGHT) {
00908                         RotateTank(p, -64);
00909                     }
00910                     else if (p->flags & PLAYER_ROTLEFT) {
00911                         RotateTank(p, 64);
00912                     }
00913                 }
00914                 UpdatePlayers_contObst:
00915                 // check for a collision between a shell and an obstacle
00916                 if ((p->flags & PLAYER_SHOT) &&
00917                 ((p->shellX >> 8) >= obst->loc.x) &&
00918                 ((p->shellX >> 8) < (obst->loc.x + obst->loc.w)) &&
00919                 ((p->shellY >> 8) >= obst->loc.y) &&
00920                 ((p->shellY >> 8) < (obst->loc.y + obst->loc.h))) {
00921                     // The shell collided, so kill it.
00922                     p->flags &= ~PLAYER_SHOT;
00923                     ClearRenderItem(&(p->shell));
00924                     batchShellDraw[BatchIndex(p->pid)] &= ~BatchBit(p->pid);
00925                 }
00926             }
00927             #ifdef USE_AUDIO
00928             // if the local player is not dead . . .
00929             // for players other than the local player that are moving . . .
00930             if ((p != local) &&
00931             !(local->flags & (PLAYER_DEAD | PLAYER_MOVEMENT_SOUND)) &&
00932             (p->flags & PLAYER_MOVEMENT_SOUND)) {
00933                 // compute the square of the distance to the local player
00934                 dist = DistSq(p, local);
00935                 // see if this is the closest tank to the player's tank
00936                 // so far
00937                 if (dist < closest) {
00938                     // record it
00939                     closest = dist;
00940                 }
00941             }
00942             #endif
00943         }
00944     } while (outdated);
00945     // for servers, if there was a score update . . .
00946     if ((networkState == NETSTATE_SERVER) && (numScores > 0)) {
00947         // send the score update
00948         SendScoreUpdate(scores, numScores);
00949     }
00950     // if the game should be rendered . . .
00951     if (dInfo.showGame) {
00952         RenderGame();
00953     }
00954     #ifdef USE_AUDIO
00955     // if there has been a change in the tank distance . . .
00956     if (closest != oldClosest) {
00957         // if there is a tank movement sound other than for the local player
00958         // that is close enough to be audible . . .
00959         if (closest < (MAX_SQ_DIST >> 2)) {
00960             // request to play the sound
00961             PlayTankMove(closest);
00962         }
00963         else {
00964             // play sound for the local player's tank instead
00965             if (local->flags & PLAYER_MOVEMENT_SOUND) {
00966                 PlayLocalTankMove();
00967             }
00968             else {
00969                 PlayTankIdle();
00970             }
00971         }
00972         // record the distance for the next iteration of the game loop
00973         oldClosest = closest;
00974     }
00975     #endif
00976 }
00977 
00978 #undef QueuePlaceTank
00979 #undef QueuePlaceShell
00980 #undef QueuePlaceName
00981 
00982 void QueuePlaceTank(Uint8 pid) {
00983     batchTankDraw[BatchIndex(pid)] |= BatchBit(pid);
00984 }
00985 
00986 void QueuePlaceShell(Uint8 pid) { 
00987     batchShellDraw[BatchIndex(pid)] |= BatchBit(pid);
00988 }
00989 
00990 void RemoveShell(Uint8 pid) { 
00991     batchShellDraw[BatchIndex(pid)] &= ~BatchBit(pid);
00992 }
00993 
00994 void QueuePlaceName(Uint8 pid) {
00995     batchNameDraw[BatchIndex(pid)] |= BatchBit(pid);
00996 }

Generated on Mon May 28 04:41:38 2007 for Retro Tank Super Attack by  doxygen 1.5.2