/* GAMEDO.C Contains all of the gamedo_xxx functions...which are called from the main game loop. These functions perform some task that is done each time around the game loop, not directly related to the player. */ #include "keen.h" #include "include/game.h" #include "include/gamedo.h" #include "include/gamepdo.h" #include "include/misc.h" #include "sdl/CVideoDriver.h" #include "sdl/CTimer.h" #include "sdl/CInput.h" #include "sdl/sound/CSound.h" #include "CGraphics.h" #include "externals.h" #include "StringUtils.h" #include "include/enemyai.h" extern unsigned long gotPlayX; extern unsigned long CurrentTickCount; extern unsigned int unknownKey; int animtiletimer, curanimtileframe; // gathers data from input controllers: keyboard, joystick, network, // whatever to populate each player's keytable unsigned char oldleftkey = 5; unsigned char oldrightkey = 5; unsigned char oldupkey = 5; unsigned char olddownkey = 5; unsigned char oldctrlkey = 5; unsigned char oldaltkey = 5; void gamedo_getInput(stCloneKeenPlus *pCKP) { int i=0; int byt; unsigned int msb, lsb; if (pCKP->Control.levelcontrol.demomode==DEMO_PLAYBACK) { // time to get a new key block? if (!demo_RLERunLen) { /* get next RLE run length */ lsb = demo_data[demo_data_index++]; msb = demo_data[demo_data_index++]; demo_RLERunLen = (msb<<8) | lsb; byt = demo_data[demo_data_index++]; // get keys down player[0].playcontrol[PA_X] = 0; player[0].playcontrol[PA_POGO] = 0; player[0].playcontrol[PA_JUMP] = 0; player[0].playcontrol[PA_FIRE] = 0; player[0].playcontrol[PA_STATUS] = 0; if (byt & 1) player[0].playcontrol[PA_X] -= 100; if (byt & 2) player[0].playcontrol[PA_X] += 100; if (byt & 4) player[0].playcontrol[PA_POGO] = 1; if (byt & 8) player[0].playcontrol[PA_JUMP] = 1; if (byt & 16)player[0].playcontrol[PA_FIRE] = 1; if (byt & 32)player[0].playcontrol[PA_STATUS] = 1; if (byt & 64) { // demo STOP command if (fade.mode!=FADE_GO) endlevel(1, pCKP); } } else { // we're still in the last RLE run, don't change any keys demo_RLERunLen--; } // user trying to cancel the demo? for(i=0;igetPressedKey(i)) { if (fade.mode!=FADE_GO) endlevel(0, pCKP); } } if (g_pInput->getPressedCommand(IC_STATUS)) { if (fade.mode!=FADE_GO) endlevel(0, pCKP); } return; } for(Uint8 p=0 ; pgetHoldedCommand(p, IC_LEFT)) player[p].playcontrol[PA_X] -= 100; if(g_pInput->getHoldedCommand(p, IC_RIGHT)) player[p].playcontrol[PA_X] += 100; if(g_pInput->getHoldedCommand(p, IC_UP)) player[p].playcontrol[PA_Y] -= 100; if(g_pInput->getHoldedCommand(p, IC_DOWN)) player[p].playcontrol[PA_Y] += 100; if(g_pInput->getHoldedCommand(p, IC_JUMP)) player[p].playcontrol[PA_JUMP] = 1; if(g_pInput->getHoldedCommand(p, IC_POGO)) player[p].playcontrol[PA_POGO] = 1; if(g_pInput->getHoldedCommand(p, IC_FIRE)) player[p].playcontrol[PA_FIRE] = 1; if(g_pInput->getHoldedCommand(p, IC_STATUS)) player[p].playcontrol[PA_STATUS] = 1; if (pCKP->Control.levelcontrol.demomode==DEMO_RECORD) { if(i) player[p].playcontrol[PA_X] += 100; fputc(i, demofile); if(i) player[p].playcontrol[PA_X] -= 100; fputc(i, demofile); if(i) player[p].playcontrol[PA_POGO] = 1; fputc(i, demofile); if(i) player[p].playcontrol[PA_JUMP] = 1; fputc(i, demofile); if(i) player[p].playcontrol[PA_FIRE] = 1; fputc(i, demofile); if(i) player[p].playcontrol[PA_STATUS] = 1; fputc(i, demofile); } } } // handles scrolling, for player cp // returns nonzero if the scroll was changed int gamedo_ScrollTriggers(int theplayer) { signed int px, py; int scrollchanged; if (player[theplayer].pdie) return 0; //px = (Player[theplayer].getCoordX()>>CSF)-scroll_x; //py = (Player[theplayer].getCoordY()>>CSF)-scroll_y; px = (player[theplayer].x>>CSF)-scroll_x; py = (player[theplayer].y>>CSF)-scroll_y; scrollchanged = 0; /* left-right scrolling */ if(px > SCROLLTRIGGERRIGHT && scroll_x < max_scroll_x) { map_scroll_right(); scrollchanged = 1; } else if(px < SCROLLTRIGGERLEFT && scroll_x > 32) { map_scroll_left(); scrollchanged = 1; } /* up-down scrolling */ if (py > SCROLLTRIGGERDOWN && scroll_y < max_scroll_y) { map_scroll_down(); scrollchanged = 1; } else if (py < SCROLLTRIGGERUP && scroll_y > 32) { map_scroll_up(); scrollchanged = 1; } return scrollchanged; } // animates animated tiles void gamedo_AnimatedTiles(bool animate_hinttiles) { int i; /* animate animated tiles */ if (animtiletimer>ANIM_TILE_TIME) { /* advance to next frame */ curanimtileframe = (curanimtileframe+1)&7; /* re-draw all animated tiles */ for(i=1;idrawTile(animtiles[i].x, animtiles[i].y, animtiles[i].baseframe+((animtiles[i].offset+curanimtileframe)%TileProperty[animtiles[i].baseframe][ANIMATION])); } } } animtiletimer = 0; } else animtiletimer++; } // do object and enemy AI void gamedo_enemyai(stCloneKeenPlus *pCKP) { int i, topobj; // handle objects and do enemy AI topobj = highest_objslot; for(i=1;i (map.xsize << CSF << 4) || objects[i].y > (map.ysize << CSF << 4)) continue; objects[i].scrx = (objects[i].x>>CSF)-scroll_x; objects[i].scry = (objects[i].y>>CSF)-scroll_y; if (objects[i].scrx < -(sprites[objects[i].sprite].xsize) || objects[i].scrx > 320 \ || objects[i].scry < -(sprites[objects[i].sprite].ysize) || objects[i].scry > 200) { objects[i].onscreen = 0; objects[i].wasoffscreen = 1; if (objects[i].type==OBJ_ICEBIT) objects[i].exists = 0; } else { objects[i].onscreen = 1; objects[i].hasbeenonscreen = 1; } if (objects[i].hasbeenonscreen || objects[i].zapped || objects[i].type==OBJ_RAY || \ objects[i].type==OBJ_ICECHUNK || objects[i].type==OBJ_PLATFORM ||\ objects[i].type==OBJ_PLATVERT || objects[i].type==OBJ_YORP || objects[i].type==OBJ_FOOB || objects[i].type==OBJ_WALKER) { common_enemy_ai(i); switch(objects[i].type) { //KEEN1 case OBJ_YORP: yorp_ai(i, pCKP->Control.levelcontrol); break; case OBJ_GARG: garg_ai(i, pCKP); break; case OBJ_VORT: vort_ai(i, pCKP, pCKP->Control.levelcontrol); break; case OBJ_BUTLER: butler_ai(i, pCKP->Control.levelcontrol.hardmode); break; case OBJ_TANK: tank_ai(i, pCKP->Control.levelcontrol.hardmode); break; case OBJ_RAY: ray_ai(i, pCKP, pCKP->Control.levelcontrol); break; case OBJ_DOOR: door_ai(i, pCKP->Control.levelcontrol.cepvars.DoorOpenDir); break; //case OBJ_ICECANNON: icecannon_ai(i); break; TODO: Add this AI case OBJ_ICECHUNK: icechunk_ai(i); break; case OBJ_ICEBIT: icebit_ai(i); break; case OBJ_TELEPORTER: teleporter_ai(i, pCKP->Control.levelcontrol); break; case OBJ_ROPE: rope_ai(i); break; //KEEN2 case OBJ_WALKER: walker_ai(i, pCKP->Control.levelcontrol); break; case OBJ_TANKEP2: tankep2_ai(i, pCKP); break; case OBJ_PLATFORM: platform_ai(i, pCKP->Control.levelcontrol); break; case OBJ_VORTELITE: vortelite_ai(i, pCKP->Control.levelcontrol.dark); break; case OBJ_SECTOREFFECTOR: se_ai(i, pCKP); break; case OBJ_BABY: baby_ai(i, pCKP->Control.levelcontrol.episode, pCKP->Control.levelcontrol.hardmode); break; case OBJ_EXPLOSION: explosion_ai(i); break; case OBJ_EARTHCHUNK: earthchunk_ai(i); break; //case OBJ_SPARK: spark_ai(i); break; TODO: Add this AI //KEEN3 case OBJ_FOOB: foob_ai(i, pCKP); break; case OBJ_NINJA: ninja_ai(i, pCKP); break; case OBJ_MEEP: meep_ai(i, pCKP->Control.levelcontrol); break; case OBJ_SNDWAVE: sndwave_ai(i, pCKP); break; case OBJ_MOTHER: mother_ai(i, pCKP->Control.levelcontrol); break; case OBJ_FIREBALL: fireball_ai(i, pCKP->Control.levelcontrol.hardmode); break; case OBJ_BALL: ballandjack_ai(i); break; case OBJ_JACK: ballandjack_ai(i); break; case OBJ_PLATVERT: platvert_ai(i); break; case OBJ_NESSIE: nessie_ai(i); break; //Specials //case OBJ_AUTORAY: case OBJ_AUTORAY_V: autoray_ai(i); break; //case OBJ_GOTPOINTS: gotpoints_ai(i); break; case OBJ_DEMOMSG: break; default: //crash("gamedo_enemy_ai: Object %d is of invalid type %d\n", i, objects[i].type); break; } objects[i].scrx = (objects[i].x>>CSF)-scroll_x; objects[i].scry = (objects[i].y>>CSF)-scroll_y; } } } // common enemy/object ai, such as falling, setting blocked variables, // detecting player contact, etc. void common_enemy_ai(int o) { int x,y,xa,ya,xsize,ysize; int temp; int cplayer; //if (objects[o].type==OBJ_GOTPOINTS) return; xsize = sprites[objects[o].sprite].xsize; ysize = sprites[objects[o].sprite].ysize; // set value of blockedd--should object fall? temp = (objects[o].y>>CSF)+ysize; if ((temp>>TILE_S)<>CSF); y = (objects[o].y>>CSF)+ysize+1; for(xa=0;xa>CSF); y = (objects[o].y>>CSF)-1; for(xa=1;xa>CSF)-1; y = (objects[o].y>>CSF)+1; for(ya=0;ya>CSF)+ysize-1))][BRIGHT] || IsStopPoint(x, (objects[o].y>>CSF)+ysize-1, o)) { objects[o].blockedl = 1; } blockedl_set: ; // set blockedr objects[o].blockedr = 0; x = (objects[o].x>>CSF)+xsize; y = (objects[o].y>>CSF)+1; for(ya=0;ya>CSF)+ysize-1))][BLEFT] || IsStopPoint(x, (objects[o].y>>CSF)+ysize-1, o)) { objects[o].blockedr = 1; } blockedr_set: ; // hit detection with players objects[o].touchPlayer = 0; for(cplayer=0;cplayerOBJ_YINERTIA_RATE) { if (objects[o].yinertia < OBJFALLSPEED) objects[o].yinertia++; objects[o].yinertiatimer = 0; } else objects[o].yinertiatimer++; } objects[o].y += objects[o].yinertia; } } int savew, saveh; void gamedo_render_drawobjects(stCloneKeenPlus *pCKP) { unsigned int i; int x,y,o,tl,xsize,ysize; int xa,ya; // copy player data to their associated objects show they can get drawn // in the object-drawing loop with the rest of the objects for( i=0 ;i < numplayers ; i++) { o = player[i].useObject; if (!player[i].hideplayer) { objects[o].sprite = player[i].playframe + playerbaseframes[i]; } else { objects[o].sprite = BlankSprite; } objects[o].x = player[i].x; objects[o].y = player[i].y; objects[o].scrx = (player[i].x>>CSF)-scroll_x; objects[o].scry = (player[i].y>>CSF)-scroll_y; } // if we're playing a demo keep the "DEMO" message on the screen // as an object if (pCKP->Control.levelcontrol.demomode==DEMO_PLAYBACK) { #define DEMO_X_POS 137 #define DEMO_Y_POS 6 objects[DemoObjectHandle].exists = 1; objects[DemoObjectHandle].onscreen = 1; objects[DemoObjectHandle].type = OBJ_DEMOMSG; objects[DemoObjectHandle].sprite = DemoSprite; objects[DemoObjectHandle].x = (DEMO_X_POS+scroll_x)<>CSF)-scroll_x); objects[i].scry = ((objects[i].y>>CSF)-scroll_y); g_pGraphics->drawSprite(objects[i].scrx, objects[i].scry, objects[i].sprite, i); if (objects[i].honorPriority) { // handle priority tiles and tiles with masks // get the upper-left coordinates to start checking for tiles x = (((objects[i].x>>CSF)-1)>>4)<<4; y = (((objects[i].y>>CSF)-1)>>4)<<4; // get the xsize/ysize of this sprite--round up to the nearest 16 xsize = ((sprites[objects[i].sprite].xsize)>>4<<4); if (xsize != sprites[objects[i].sprite].xsize) xsize+=16; ysize = ((sprites[objects[i].sprite].ysize)>>4<<4); if (ysize != sprites[objects[i].sprite].ysize) ysize+=16; // now redraw any priority/masked tiles that we covered up // with the sprite for(ya=0;ya<=ysize;ya+=16) { for(xa=0;xa<=xsize;xa+=16) { tl = getmaptileat(x+xa,y+ya); if(TileProperty[tl][BEHAVIOR] == 65534) { g_pGraphics->drawTilewithmask(x+xa-scroll_x,y+ya-scroll_y,tl,tl+1); } else if (TileProperty[tl][BEHAVIOR] == 65535) { if ( TileProperty[tl][ANIMATION] > 1 ) { tl = (tl-tiles[tl].animOffset)+((tiles[tl].animOffset+curanimtileframe)%TileProperty[tl][ANIMATION]); } g_pGraphics->drawPrioritytile(x+xa-scroll_x,y+ya-scroll_y,tl); } } } } } if(i==0) break; } } void gamedo_render_drawdebug(void) { int tl=0,y; /*int h;*/ std::string debugmsg; if (debugmode) { if (debugmode==1) { savew = 190; saveh = 80; g_pGraphics->saveArea(4,4,savew,saveh); y = 5-8; debugmsg = "p1x/y: " + itoa(player[0].x) + "/" + itoa(player[0].y); g_pGraphics->sb_font_draw( debugmsg, 5, y+=8); debugmsg = "p2x/y: " + itoa(player[1].x) + "/" + itoa(player[1].y); g_pGraphics->sb_font_draw( debugmsg, 5, y+=8); debugmsg = "scroll_x/y = " + itoa(scroll_x) + "/" + itoa(scroll_y); g_pGraphics->sb_font_draw( debugmsg, 5, y+=8); debugmsg = "scrollbuf_x/y: " + itoa(scrollx_buf) + "/" + itoa(scrolly_buf); g_pGraphics->sb_font_draw( debugmsg, 5, y+=8); debugmsg = "iw,pw: " + itoa(player[0].inhibitwalking) + "/" + itoa(player[0].pwalking); g_pGraphics->sb_font_draw( debugmsg, 5, y+=8); debugmsg = "pinertia_x: " + itoa(player[0].pinertia_x); g_pGraphics->sb_font_draw( debugmsg, 5, y+=8); debugmsg = "psupt: (" + itoa(player[0].psupportingtile) + "," + itoa(player[0].psupportingobject) + ")"; g_pGraphics->sb_font_draw( debugmsg, 5, y+=8); debugmsg = "lvl,tile = " + itoa(getlevelat((player[0].x>>CSF)+4, (player[0].y>>CSF)+9)) + "," + itoa(tl); g_pGraphics->sb_font_draw( debugmsg, 5, y+=8); /* sprintf(debugmsg, "NOH=%d", NessieObjectHandle); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, "x,y=(%d,%d)", objects[NessieObjectHandle].x,objects[NessieObjectHandle].y); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, " >>CSF=(%d,%d)", objects[NessieObjectHandle].x>>CSF,objects[NessieObjectHandle].y>>CSF); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, " >>CSF>>4=(%d,%d)", objects[NessieObjectHandle].x>>CSF>>4,objects[NessieObjectHandle].y>>CSF>>4); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, "nessiestate = %d", objects[NessieObjectHandle].ai.nessie.state); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, "pausetimer = %d", objects[NessieObjectHandle].ai.nessie.pausetimer); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, "pausex/y = (%d,%d)", objects[NessieObjectHandle].ai.nessie.pausex,objects[NessieObjectHandle].ai.nessie.pausey); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, "destx/y = %d/%d", objects[NessieObjectHandle].ai.nessie.destx,objects[NessieObjectHandle].ai.nessie.desty); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, " >>CSF = %d/%d", objects[NessieObjectHandle].ai.nessie.destx>>CSF,objects[NessieObjectHandle].ai.nessie.desty>>CSF); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, " >>CSF>>4 = %d/%d", objects[NessieObjectHandle].ai.nessie.destx>>CSF>>4,objects[NessieObjectHandle].ai.nessie.desty>>CSF>>4); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, "mort_swim_amt = %d", objects[NessieObjectHandle].ai.nessie.mortimer_swim_amt); sb_font_draw(debugmsg, 5, y+=8); h = objects[NessieObjectHandle].ai.nessie.tiletrailhead; sprintf(debugmsg, "tthead=%d", h); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, "ttX=%d,%d,%d,%d,%d", objects[NessieObjectHandle].ai.nessie.tiletrailX[0],objects[NessieObjectHandle].ai.nessie.tiletrailX[1],objects[NessieObjectHandle].ai.nessie.tiletrailX[2],objects[NessieObjectHandle].ai.nessie.tiletrailX[3],objects[NessieObjectHandle].ai.nessie.tiletrailX[4]); sb_font_draw(debugmsg, 5, y+=8); sprintf(debugmsg, "ttY=%d,%d,%d,%d,%d", objects[NessieObjectHandle].ai.nessie.tiletrailY[0],objects[NessieObjectHandle].ai.nessie.tiletrailY[1],objects[NessieObjectHandle].ai.nessie.tiletrailY[2],objects[NessieObjectHandle].ai.nessie.tiletrailY[3],objects[NessieObjectHandle].ai.nessie.tiletrailY[4]); sb_font_draw(debugmsg, 5, y+=8); */ } else if (debugmode==2) { savew = map.xsize+4; saveh = map.ysize+4; g_pGraphics->saveArea(4,4,savew,saveh); radar(); } } } void gamedo_render_erasedebug(void) { if (debugmode) g_pGraphics->restoreArea(4,4,savew,saveh); } void gamedo_render_eraseobjects(void) { int i; // erase all objects. // note that this is done in the reverse order they are drawn. // this is necessary or you will see corrupted pixels when // two objects are occupying the same space. for(i=0;ieraseSprite(objects[i].scrx, objects[i].scry, objects[i].sprite, i); } } } extern int NumConsoleMessages; // draws sprites, players, and debug messages (if debug mode is on), // performs frameskipping and blits the display as needed, // at end of functions erases all drawn objects from the scrollbuf. void gamedo_RenderScreen(stCloneKeenPlus *pCKP) { int x,y,bmnum; g_pGraphics->renderHQBitmap(); if(pCKP != NULL) { gamedo_render_drawobjects(pCKP); if (pCKP->Control.levelcontrol.gameovermode) { // figure out where to center the gameover bitmap and draw it bmnum = g_pGraphics->getBitmapNumberFromName("GAMEOVER"); x = (320/2)-(bitmaps[bmnum].xsize/2); y = (200/2)-(bitmaps[bmnum].ysize/2); g_pGraphics->drawBitmap(x, y, bmnum); } } g_pVideoDriver->sb_blit(); // blit scrollbuffer to display gamedo_render_erasedebug(); gamedo_render_eraseobjects(); curfps++; } int ctspace=0, lastctspace=0; void gamedo_HandleFKeys(stCloneKeenPlus *pCKP) { int i; if (g_pInput->getHoldedKey(KC) && g_pInput->getHoldedKey(KT) && g_pInput->getHoldedKey(KSPACE)) { ctspace = 1; } else ctspace = 0; if (ctspace && !lastctspace) { for(i=0;iAddConsoleMsg("All items cheat"); } lastctspace = ctspace; // GOD cheat -- toggle god mode if (g_pInput->getHoldedKey(KG) && g_pInput->getHoldedKey(KO) && g_pInput->getHoldedKey(KD)) { for(i=0;iDeleteConsoleMsgs(); if (player[0].godmode) g_pVideoDriver->AddConsoleMsg("God mode ON"); else g_pVideoDriver->AddConsoleMsg("God mode OFF"); g_pSound->playSound(SOUND_GUN_CLICK, PLAY_FORCE); // Show a message like in the original game char **text; text = (char**) malloc(sizeof(char*)); static const int MAX_STRING_LENGTH = 256; text[0]= (char*) malloc(MAX_STRING_LENGTH*sizeof(char)); if (player[0].godmode) strcpy(text[0], "Godmode enabled"); else strcpy(text[0], "Godmode disabled"); showTextMB(1,text,pCKP); free(text[0]); free(text); } if (pCKP->Option[OPT_CHEATS].value) { if (g_pInput->getHoldedKey(KTAB)) // noclip/revive { // resurrect any dead players. the rest of the KTAB magic is // scattered throughout the various functions. for(i=0;igetPressedKey(KF8)) { framebyframe = 1; #ifdef BUILD_SDL g_pVideoDriver->AddConsoleMsg("Frame-by-frame mode F8:advance F7:stop"); #endif } // F9 - exit level immediately if(g_pInput->getPressedKey(KF9)) { endlevel(1, pCKP); } // F6 - onscreen debug--toggle through debug/radar/off if(g_pInput->getPressedKey(KF6)) { debugmode++; if (debugmode>2) debugmode=0; } // F7 - accelerate mode/frame by frame frame advance if(g_pInput->getPressedKey(KF7)) { if (!framebyframe) acceleratemode=1-acceleratemode; } } // F10 - change primary player if(g_pInput->getPressedKey(KF10)) { primaryplayer++; if (primaryplayer>=numplayers) primaryplayer=0; } // F3 - save game if (g_pInput->getPressedKey(KF3)) game_save_interface(pCKP); } void gamedo_fades(void) { if (fade.mode != FADE_GO) return; if (fade.fadetimer > fade.rate) { if (fade.dir==FADE_IN) { if (fade.curamt < PAL_FADE_SHADES) { fade.curamt++; // coming in from black } else { fade.curamt--; // coming in from white-out } if (fade.curamt==PAL_FADE_SHADES) { fade.mode = FADE_COMPLETE; } g_pGraphics->fadePalette(fade.curamt); } else if (fade.dir==FADE_OUT) { fade.curamt--; if (fade.curamt==0) fade.mode = FADE_COMPLETE; g_pGraphics->fadePalette(fade.curamt); } fade.fadetimer = 0; } else { fade.fadetimer++; } } void gamedo_frameskipping(stCloneKeenPlus *pCKP) { if (framebyframe) { gamedo_RenderScreen(pCKP); return; } if (frameskiptimer >= g_pVideoDriver->getFrameskip()) { gamedo_RenderScreen(pCKP); frameskiptimer = 0; } else frameskiptimer++; } // same as above but only does a sb_blit, not the full RenderScreen. // used for intros etc. void gamedo_frameskipping_blitonly(void) { if (framebyframe) { g_pVideoDriver->sb_blit(); return; } if (frameskiptimer >= g_pVideoDriver->getFrameskip()) { g_pVideoDriver->sb_blit(); frameskiptimer = 0; } else frameskiptimer++; }