/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "sys/platform.h" #include "framework/Session_local.h" #include "renderer/Image.h" #include "sound/sound.h" #include "ui/DeviceContext.h" #include "ui/Window.h" #include "ui/UserInterfaceLocal.h" #include "ui/GameBustOutWindow.h" #define BALL_RADIUS 12.f #define BALL_SPEED 250.f #define BALL_MAXSPEED 450.f #define S_UNIQUE_CHANNEL 6 /* ***************************************************************************** * BOEntity **************************************************************************** */ BOEntity::BOEntity(idGameBustOutWindow* _game) { game = _game; visible = true; materialName = ""; material = NULL; width = height = 8; color = colorWhite; powerup = POWERUP_NONE; position.Zero(); velocity.Zero(); removed = false; fadeOut = 0; } BOEntity::~BOEntity() { } /* ====================== BOEntity::WriteToSaveGame ====================== */ void BOEntity::WriteToSaveGame( idFile *savefile ) { savefile->Write( &visible, sizeof(visible) ); game->WriteSaveGameString( materialName, savefile ); savefile->Write( &width, sizeof(width) ); savefile->Write( &height, sizeof(height) ); savefile->Write( &color, sizeof(color) ); savefile->Write( &position, sizeof(position) ); savefile->Write( &velocity, sizeof(velocity) ); savefile->Write( &powerup, sizeof(powerup) ); savefile->Write( &removed, sizeof(removed) ); savefile->Write( &fadeOut, sizeof(fadeOut) ); } /* ====================== BOEntity::ReadFromSaveGame ====================== */ void BOEntity::ReadFromSaveGame( idFile *savefile, idGameBustOutWindow* _game ) { game = _game; savefile->Read( &visible, sizeof(visible) ); game->ReadSaveGameString( materialName, savefile ); SetMaterial( materialName ); savefile->Read( &width, sizeof(width) ); savefile->Read( &height, sizeof(height) ); savefile->Read( &color, sizeof(color) ); savefile->Read( &position, sizeof(position) ); savefile->Read( &velocity, sizeof(velocity) ); savefile->Read( &powerup, sizeof(powerup) ); savefile->Read( &removed, sizeof(removed) ); savefile->Read( &fadeOut, sizeof(fadeOut) ); } /* ====================== BOEntity::SetMaterial ====================== */ void BOEntity::SetMaterial(const char* name) { materialName = name; material = declManager->FindMaterial( name ); material->SetSort( SS_GUI ); } /* ====================== BOEntity::SetSize ====================== */ void BOEntity::SetSize( float _width, float _height ) { width = _width; height = _height; } /* ====================== BOEntity::SetVisible ====================== */ void BOEntity::SetColor( float r, float g, float b, float a ) { color.x = r; color.y = g; color.z = b; color.w = a; } /* ====================== BOEntity::SetVisible ====================== */ void BOEntity::SetVisible( bool isVisible ) { visible = isVisible; } /* ====================== BOEntity::Update ====================== */ void BOEntity::Update( float timeslice, int guiTime ) { if ( !visible ) { return; } // Move the entity position += velocity * timeslice; // Fade out the ent if ( fadeOut ) { color.w -= timeslice * 2.5; if ( color.w <= 0.f ) { color.w = 0.f; removed = true; } } } /* ====================== BOEntity::Draw ====================== */ void BOEntity::Draw(idDeviceContext *dc) { if ( visible ) { dc->DrawMaterialRotated( position.x, position.y, width, height, material, color, 1.0f, 1.0f, DEG2RAD(0.f) ); } } /* ***************************************************************************** * BOBrick **************************************************************************** */ BOBrick::BOBrick( void ) { ent = NULL; x = y = width = height = 0; powerup = POWERUP_NONE; isBroken = false; } BOBrick::BOBrick( BOEntity *_ent, float _x, float _y, float _width, float _height ) { ent = _ent; x = _x; y = _y; width = _width; height = _height; powerup = POWERUP_NONE; isBroken = false; ent->position.x = x; ent->position.y = y; ent->SetSize( width, height ); ent->SetMaterial( "game/bustout/brick" ); ent->game->entities.Append( ent ); } BOBrick::~BOBrick( void ) { } /* ====================== BOBrick::WriteToSaveGame ====================== */ void BOBrick::WriteToSaveGame( idFile *savefile ) { savefile->Write( &x, sizeof(x) ); savefile->Write( &y, sizeof(y) ); savefile->Write( &width, sizeof(width) ); savefile->Write( &height, sizeof(height) ); savefile->Write( &powerup, sizeof(powerup) ); savefile->Write( &isBroken, sizeof(isBroken) ); int index = ent->game->entities.FindIndex( ent ); savefile->Write( &index, sizeof(index) ); } /* ====================== BOBrick::ReadFromSaveGame ====================== */ void BOBrick::ReadFromSaveGame( idFile *savefile, idGameBustOutWindow *game ) { savefile->Read( &x, sizeof(x) ); savefile->Read( &y, sizeof(y) ); savefile->Read( &width, sizeof(width) ); savefile->Read( &height, sizeof(height) ); savefile->Read( &powerup, sizeof(powerup) ); savefile->Read( &isBroken, sizeof(isBroken) ); int index; savefile->Read( &index, sizeof(index) ); ent = game->entities[index]; } /* ====================== BOBrick::SetColor ====================== */ void BOBrick::SetColor( idVec4 bcolor ) { ent->SetColor( bcolor.x, bcolor.y, bcolor.z, bcolor.w ); } /* ====================== BOBrick::checkCollision ====================== */ collideDir_t BOBrick::checkCollision( idVec2 pos, idVec2 vel ) { idVec2 ptA, ptB; float dist; collideDir_t result = COLLIDE_NONE; if ( isBroken ) { return result; } // Check for collision with each edge idVec2 vec; // Bottom ptA.x = x; ptA.y = y + height; ptB.x = x + width; ptB.y = y + height; if ( vel.y < 0 && pos.y > ptA.y ) { if( pos.x > ptA.x && pos.x < ptB.x ) { dist = pos.y - ptA.y; if ( dist < BALL_RADIUS ) { result = COLLIDE_DOWN; } } else { if ( pos.x <= ptA.x ) { vec = pos - ptA; } else { vec = pos - ptB; } if ( (idMath::Fabs(vec.y) > idMath::Fabs(vec.x)) && (vec.LengthFast() < BALL_RADIUS) ) { result = COLLIDE_DOWN; } } } if ( result == COLLIDE_NONE ) { // Top ptA.y = y; ptB.y = y; if ( vel.y > 0 && pos.y < ptA.y ) { if( pos.x > ptA.x && pos.x < ptB.x ) { dist = ptA.y - pos.y; if ( dist < BALL_RADIUS ) { result = COLLIDE_UP; } } else { if ( pos.x <= ptA.x ) { vec = pos - ptA; } else { vec = pos - ptB; } if ( (idMath::Fabs(vec.y) > idMath::Fabs(vec.x)) && (vec.LengthFast() < BALL_RADIUS) ) { result = COLLIDE_UP; } } } if ( result == COLLIDE_NONE ) { // Left side ptA.x = x; ptA.y = y; ptB.x = x; ptB.y = y + height; if ( vel.x > 0 && pos.x < ptA.x ) { if( pos.y > ptA.y && pos.y < ptB.y ) { dist = ptA.x - pos.x; if ( dist < BALL_RADIUS ) { result = COLLIDE_LEFT; } } else { if ( pos.y <= ptA.y ) { vec = pos - ptA; } else { vec = pos - ptB; } if ( (idMath::Fabs(vec.x) >= idMath::Fabs(vec.y)) && (vec.LengthFast() < BALL_RADIUS) ) { result = COLLIDE_LEFT; } } } if ( result == COLLIDE_NONE ) { // Right side ptA.x = x + width; ptB.x = x + width; if ( vel.x < 0 && pos.x > ptA.x ) { if( pos.y > ptA.y && pos.y < ptB.y ) { dist = pos.x - ptA.x; if ( dist < BALL_RADIUS ) { result = COLLIDE_LEFT; } } else { if ( pos.y <= ptA.y ) { vec = pos - ptA; } else { vec = pos - ptB; } if ( (idMath::Fabs(vec.x) >= idMath::Fabs(vec.y)) && (vec.LengthFast() < BALL_RADIUS) ) { result = COLLIDE_LEFT; } } } } } } return result; } /* ***************************************************************************** * idGameBustOutWindow **************************************************************************** */ idGameBustOutWindow::idGameBustOutWindow(idDeviceContext *d, idUserInterfaceLocal *g) : idWindow(d, g) { dc = d; gui = g; CommonInit(); } idGameBustOutWindow::idGameBustOutWindow(idUserInterfaceLocal *g) : idWindow(g) { gui = g; CommonInit(); } idGameBustOutWindow::~idGameBustOutWindow() { entities.DeleteContents(true); Mem_Free( levelBoardData ); } /* ============================= idGameBustOutWindow::WriteToSaveGame ============================= */ void idGameBustOutWindow::WriteToSaveGame( idFile *savefile ) { idWindow::WriteToSaveGame( savefile ); gamerunning.WriteToSaveGame( savefile ); onFire.WriteToSaveGame( savefile ); onContinue.WriteToSaveGame( savefile ); onNewGame.WriteToSaveGame( savefile ); onNewLevel.WriteToSaveGame( savefile ); savefile->Write( &timeSlice, sizeof(timeSlice) ); savefile->Write( &gameOver, sizeof(gameOver) ); savefile->Write( &numLevels, sizeof(numLevels) ); // Board Data is loaded when GUI is loaded, don't need to save savefile->Write( &numBricks, sizeof(numBricks) ); savefile->Write( ¤tLevel, sizeof(currentLevel) ); savefile->Write( &updateScore, sizeof(updateScore) ); savefile->Write( &gameScore, sizeof(gameScore) ); savefile->Write( &nextBallScore, sizeof(nextBallScore) ); savefile->Write( &bigPaddleTime, sizeof(bigPaddleTime) ); savefile->Write( &paddleVelocity, sizeof(paddleVelocity) ); savefile->Write( &ballSpeed, sizeof(ballSpeed) ); savefile->Write( &ballsRemaining, sizeof(ballsRemaining) ); savefile->Write( &ballsInPlay, sizeof(ballsInPlay) ); savefile->Write( &ballHitCeiling, sizeof(ballHitCeiling) ); // Write Entities int i; int numberOfEnts = entities.Num(); savefile->Write( &numberOfEnts, sizeof(numberOfEnts) ); for ( i=0; iWriteToSaveGame( savefile ); } // Write Balls numberOfEnts = balls.Num(); savefile->Write( &numberOfEnts, sizeof(numberOfEnts) ); for ( i=0; iWrite( &ballIndex, sizeof(ballIndex) ); } // Write Powerups numberOfEnts = powerUps.Num(); savefile->Write( &numberOfEnts, sizeof(numberOfEnts) ); for ( i=0; iWrite( &powerIndex, sizeof(powerIndex) ); } // Write paddle paddle->WriteToSaveGame( savefile ); // Write Bricks int row; for ( row=0; rowWrite( &numberOfEnts, sizeof(numberOfEnts) ); for ( i=0; iWriteToSaveGame( savefile ); } } } /* ============================= idGameBustOutWindow::ReadFromSaveGame ============================= */ void idGameBustOutWindow::ReadFromSaveGame( idFile *savefile ) { idWindow::ReadFromSaveGame( savefile ); // Clear out existing paddle and entities from GUI load delete paddle; entities.DeleteContents( true ); gamerunning.ReadFromSaveGame( savefile ); onFire.ReadFromSaveGame( savefile ); onContinue.ReadFromSaveGame( savefile ); onNewGame.ReadFromSaveGame( savefile ); onNewLevel.ReadFromSaveGame( savefile ); savefile->Read( &timeSlice, sizeof(timeSlice) ); savefile->Read( &gameOver, sizeof(gameOver) ); savefile->Read( &numLevels, sizeof(numLevels) ); // Board Data is loaded when GUI is loaded, don't need to save savefile->Read( &numBricks, sizeof(numBricks) ); savefile->Read( ¤tLevel, sizeof(currentLevel) ); savefile->Read( &updateScore, sizeof(updateScore) ); savefile->Read( &gameScore, sizeof(gameScore) ); savefile->Read( &nextBallScore, sizeof(nextBallScore) ); savefile->Read( &bigPaddleTime, sizeof(bigPaddleTime) ); savefile->Read( &paddleVelocity, sizeof(paddleVelocity) ); savefile->Read( &ballSpeed, sizeof(ballSpeed) ); savefile->Read( &ballsRemaining, sizeof(ballsRemaining) ); savefile->Read( &ballsInPlay, sizeof(ballsInPlay) ); savefile->Read( &ballHitCeiling, sizeof(ballHitCeiling) ); int i; int numberOfEnts; // Read entities savefile->Read( &numberOfEnts, sizeof(numberOfEnts) ); for ( i=0; iReadFromSaveGame( savefile, this ); entities.Append( ent ); } // Read balls savefile->Read( &numberOfEnts, sizeof(numberOfEnts) ); for ( i=0; iRead( &ballIndex, sizeof(ballIndex) ); balls.Append( entities[ballIndex] ); } // Read powerups savefile->Read( &numberOfEnts, sizeof(numberOfEnts) ); for ( i=0; iRead( &powerIndex, sizeof(powerIndex) ); balls.Append( entities[powerIndex] ); } // Read paddle paddle = new BOBrick(); paddle->ReadFromSaveGame( savefile, this ); // Read board int row; for ( row=0; rowRead( &numberOfEnts, sizeof(numberOfEnts) ); for ( i=0; iReadFromSaveGame( savefile, this ); board[row].Append( brick ); } } } /* ============================= idGameBustOutWindow::ResetGameState ============================= */ void idGameBustOutWindow::ResetGameState() { gamerunning = false; gameOver = false; onFire = false; onContinue = false; onNewGame = false; onNewLevel = false; // Game moves forward 16 milliseconds every frame timeSlice = 0.016f; ballsRemaining = 3; ballSpeed = BALL_SPEED; ballsInPlay = 0; updateScore = false; numBricks = 0; currentLevel = 1; gameScore = 0; bigPaddleTime = 0; nextBallScore = gameScore + 10000; ClearBoard(); } /* ============================= idGameBustOutWindow::CommonInit ============================= */ void idGameBustOutWindow::CommonInit() { BOEntity *ent; // Precache images declManager->FindMaterial( "game/bustout/ball" ); declManager->FindMaterial( "game/bustout/doublepaddle" ); declManager->FindMaterial( "game/bustout/powerup_bigpaddle" ); declManager->FindMaterial( "game/bustout/powerup_multiball" ); declManager->FindMaterial( "game/bustout/brick" ); // Precache sounds declManager->FindSound( "arcade_ballbounce" ); declManager->FindSound( "arcade_brickhit" ); declManager->FindSound( "arcade_missedball" ); declManager->FindSound( "arcade_sadsound" ); declManager->FindSound( "arcade_extraball" ); declManager->FindSound( "arcade_powerup" ); ResetGameState(); numLevels = 0; boardDataLoaded = false; levelBoardData = NULL; // Create Paddle ent = new BOEntity( this ); paddle = new BOBrick( ent, 260.f, 440.f, 96.f, 24.f ); paddle->ent->SetMaterial( "game/bustout/paddle" ); } /* ============================= idGameBustOutWindow::HandleEvent ============================= */ const char *idGameBustOutWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { int key = event->evValue; // need to call this to allow proper focus and capturing on embedded children const char *ret = idWindow::HandleEvent(event, updateVisuals); if ( event->evType == SE_KEY ) { if ( !event->evValue2 ) { return ret; } if ( key == K_MOUSE1) { // Mouse was clicked if ( ballsInPlay == 0 ) { BOEntity *ball = CreateNewBall(); ball->SetVisible( true ); ball->position.x = paddle->ent->position.x + 48.f; ball->position.y = 430.f; ball->velocity.x = ballSpeed; ball->velocity.y = -ballSpeed*2.f; ball->velocity.NormalizeFast(); ball->velocity *= ballSpeed; } } else { return ret; } } return ret; } /* ============================= idGameBustOutWindow::ParseInternalVar ============================= */ bool idGameBustOutWindow::ParseInternalVar(const char *_name, idParser *src) { if ( idStr::Icmp(_name, "gamerunning") == 0 ) { gamerunning = src->ParseBool(); return true; } if ( idStr::Icmp(_name, "onFire") == 0 ) { onFire = src->ParseBool(); return true; } if ( idStr::Icmp(_name, "onContinue") == 0 ) { onContinue = src->ParseBool(); return true; } if ( idStr::Icmp(_name, "onNewGame") == 0 ) { onNewGame = src->ParseBool(); return true; } if ( idStr::Icmp(_name, "onNewLevel") == 0 ) { onNewLevel = src->ParseBool(); return true; } if ( idStr::Icmp(_name, "numLevels") == 0 ) { numLevels = src->ParseInt(); // Load all the level images LoadBoardFiles(); return true; } return idWindow::ParseInternalVar(_name, src); } /* ============================= idGameBustOutWindow::GetWinVarByName ============================= */ idWinVar *idGameBustOutWindow::GetWinVarByName(const char *_name, bool winLookup, drawWin_t** owner) { idWinVar *retVar = NULL; if ( idStr::Icmp(_name, "gamerunning") == 0 ) { retVar = &gamerunning; } else if ( idStr::Icmp(_name, "onFire") == 0 ) { retVar = &onFire; } else if ( idStr::Icmp(_name, "onContinue") == 0 ) { retVar = &onContinue; } else if ( idStr::Icmp(_name, "onNewGame") == 0 ) { retVar = &onNewGame; } else if ( idStr::Icmp(_name, "onNewLevel") == 0 ) { retVar = &onNewLevel; } if(retVar) { return retVar; } return idWindow::GetWinVarByName(_name, winLookup, owner); } /* ============================= idGameBustOutWindow::PostParse ============================= */ void idGameBustOutWindow::PostParse() { idWindow::PostParse(); } /* ============================= idGameBustOutWindow::Draw ============================= */ void idGameBustOutWindow::Draw(int time, float x, float y) { int i; //Update the game every frame before drawing UpdateGame(); for( i = entities.Num()-1; i >= 0; i-- ) { entities[i]->Draw(dc); } } /* ============================= idGameBustOutWindow::UpdateScore ============================= */ void idGameBustOutWindow::UpdateScore() { if ( gameOver ) { gui->HandleNamedEvent( "GameOver" ); return; } // Check for level progression if ( numBricks == 0 ) { ClearBalls(); gui->HandleNamedEvent( "levelComplete" ); } // Check for new ball score if ( gameScore >= nextBallScore ) { ballsRemaining++; gui->HandleNamedEvent( "extraBall" ); // Play sound session->sw->PlayShaderDirectly( "arcade_extraball", S_UNIQUE_CHANNEL ); nextBallScore = gameScore + 10000; } gui->SetStateString( "player_score", va("%i", gameScore ) ); gui->SetStateString( "balls_remaining", va("%i", ballsRemaining ) ); gui->SetStateString( "current_level", va("%i", currentLevel ) ); gui->SetStateString( "next_ball_score", va("%i", nextBallScore ) ); } /* ============================= idGameBustOutWindow::ClearBoard ============================= */ void idGameBustOutWindow::ClearBoard( void ) { int i,j; ClearPowerups(); ballHitCeiling = false; for ( i=0; ient->removed = true; } board[i].DeleteContents( true ); } } /* ============================= idGameBustOutWindow::ClearPowerups ============================= */ void idGameBustOutWindow::ClearPowerups( void ) { while ( powerUps.Num() ) { powerUps[0]->removed = true; powerUps.RemoveIndex( 0 ); } } /* ============================= idGameBustOutWindow::ClearBalls ============================= */ void idGameBustOutWindow::ClearBalls( void ) { while ( balls.Num() ) { balls[0]->removed = true; balls.RemoveIndex( 0 ); } ballsInPlay = 0; } /* ============================= idGameBustOutWindow::LoadBoardFiles ============================= */ void idGameBustOutWindow::LoadBoardFiles( void ) { int i; int w,h; ID_TIME_T time; int boardSize; byte *currentBoard; if ( boardDataLoaded ) { return; } boardSize = 9 * 12 * 4; levelBoardData = (byte*)Mem_Alloc( boardSize * numLevels ); currentBoard = levelBoardData; for ( i=0; iDWarning( "Hell Bust-Out level image not correct dimensions! (%d x %d)", w, h ); } memcpy( currentBoard, pic, boardSize ); Mem_Free(pic); } currentBoard += boardSize; } boardDataLoaded = true; } /* ============================= idGameBustOutWindow::SetCurrentBoard ============================= */ void idGameBustOutWindow::SetCurrentBoard( void ) { int i,j; int realLevel = ((currentLevel-1) % numLevels); int boardSize; byte *currentBoard; float bx = 11.f; float by = 24.f; float stepx = 619.f / 9.f; float stepy = ( 256 / 12.f ); boardSize = 9 * 12 * 4; currentBoard = levelBoardData + ( realLevel * boardSize ); for ( j=0; jSetColor( bcolor ); pType = currentBoard[pixelindex + 3] / 255.f; if ( pType > 0.f && pType < 1.f ) { if ( pType < 0.5f ) { brick->powerup = POWERUP_BIGPADDLE; } else { brick->powerup = POWERUP_MULTIBALL; } } board[j].Append( brick ); numBricks++; } bx += stepx; } by += stepy; } } /* ============================= idGameBustOutWindow::CreateNewBall ============================= */ BOEntity * idGameBustOutWindow::CreateNewBall( void ) { BOEntity *ball; ball = new BOEntity( this ); ball->position.x = 300.f; ball->position.y = 416.f; ball->SetMaterial( "game/bustout/ball" ); ball->SetSize( BALL_RADIUS*2.f, BALL_RADIUS*2.f ); ball->SetVisible( false ); ballsInPlay++; balls.Append( ball ); entities.Append( ball ); return ball; } /* ============================= idGameBustOutWindow::CreatePowerup ============================= */ BOEntity * idGameBustOutWindow::CreatePowerup( BOBrick *brick ) { BOEntity *powerEnt = new BOEntity( this ); powerEnt->position.x = brick->x; powerEnt->position.y = brick->y; powerEnt->velocity.x = 0.f; powerEnt->velocity.y = 64.f; powerEnt->powerup = brick->powerup; switch( powerEnt->powerup ) { case POWERUP_BIGPADDLE: powerEnt->SetMaterial( "game/bustout/powerup_bigpaddle" ); break; case POWERUP_MULTIBALL: powerEnt->SetMaterial( "game/bustout/powerup_multiball" ); break; default: powerEnt->SetMaterial( "textures/common/nodraw" ); break; } powerEnt->SetSize( 619/9, 256/12 ); powerEnt->SetVisible( true ); powerUps.Append( powerEnt ); entities.Append( powerEnt ); return powerEnt; } /* ============================= idGameBustOutWindow::UpdatePowerups ============================= */ void idGameBustOutWindow::UpdatePowerups( void ) { idVec2 pos; for ( int i=0; i < powerUps.Num(); i++ ) { BOEntity *pUp = powerUps[i]; // Check for powerup falling below screen if ( pUp->position.y > 480 ) { powerUps.RemoveIndex( i ); pUp->removed = true; continue; } // Check for the paddle catching a powerup pos.x = pUp->position.x + ( pUp->width / 2 ); pos.y = pUp->position.y + ( pUp->height / 2 ); collideDir_t collision = paddle->checkCollision( pos, pUp->velocity ); if ( collision != COLLIDE_NONE ) { BOEntity *ball; // Give the powerup to the player switch( pUp->powerup ) { case POWERUP_BIGPADDLE: bigPaddleTime = gui->GetTime() + 15000; break; case POWERUP_MULTIBALL: // Create 2 new balls in the spot of the existing ball for ( int b=0; b<2; b++ ) { ball = CreateNewBall(); ball->position = balls[0]->position; ball->velocity = balls[0]->velocity; if ( b == 0 ) { ball->velocity.x -= 35.f; } else { ball->velocity.x += 35.f; } ball->velocity.NormalizeFast(); ball->velocity *= ballSpeed; ball->SetVisible( true ); } break; default: break; } // Play the sound session->sw->PlayShaderDirectly( "arcade_powerup", S_UNIQUE_CHANNEL ); // Remove it powerUps.RemoveIndex( i ); pUp->removed = true; } } } /* ============================= idGameBustOutWindow::UpdatePaddle ============================= */ void idGameBustOutWindow::UpdatePaddle( void ) { idVec2 cursorPos; float oldPos = paddle->x; cursorPos.x = gui->CursorX(); cursorPos.y = gui->CursorY(); if ( bigPaddleTime > gui->GetTime() ) { paddle->x = cursorPos.x - 80.f; paddle->width = 160; paddle->ent->width = 160; paddle->ent->SetMaterial( "game/bustout/doublepaddle" ); } else { paddle->x = cursorPos.x - 48.f; paddle->width = 96; paddle->ent->width = 96; paddle->ent->SetMaterial( "game/bustout/paddle" ); } paddle->ent->position.x = paddle->x; paddleVelocity = (paddle->x - oldPos); } /* ============================= idGameBustOutWindow::UpdateBall ============================= */ void idGameBustOutWindow::UpdateBall( void ) { int ballnum,i,j; bool playSoundBounce = false; bool playSoundBrick = false; static int bounceChannel = 1; if ( ballsInPlay == 0 ) { return; } for ( ballnum = 0; ballnum < balls.Num(); ballnum++ ) { BOEntity *ball = balls[ballnum]; // Check for ball going below screen, lost ball if ( ball->position.y > 480.f ) { ball->removed = true; continue; } // Check world collision if ( ball->position.y < 20 && ball->velocity.y < 0 ) { ball->velocity.y = -ball->velocity.y; // Increase ball speed when it hits ceiling if ( !ballHitCeiling ) { ballSpeed *= 1.25f; ballHitCeiling = true; } playSoundBounce = true; } if ( ball->position.x > 608 && ball->velocity.x > 0 ) { ball->velocity.x = -ball->velocity.x; playSoundBounce = true; } else if ( ball->position.x < 8 && ball->velocity.x < 0 ) { ball->velocity.x = -ball->velocity.x; playSoundBounce = true; } // Check for Paddle collision idVec2 ballCenter = ball->position + idVec2( BALL_RADIUS, BALL_RADIUS ); collideDir_t collision = paddle->checkCollision( ballCenter, ball->velocity ); if ( collision == COLLIDE_UP ) { if ( ball->velocity.y > 0 ) { idVec2 paddleVec( paddleVelocity*2, 0 ); float centerX; if ( bigPaddleTime > gui->GetTime() ) { centerX = paddle->x + 80.f; } else { centerX = paddle->x + 48.f; } ball->velocity.y = -ball->velocity.y; paddleVec.x += (ball->position.x - centerX) * 2; ball->velocity += paddleVec; ball->velocity.NormalizeFast(); ball->velocity *= ballSpeed; playSoundBounce = true; } } else if ( collision == COLLIDE_LEFT || collision == COLLIDE_RIGHT ) { if ( ball->velocity.y > 0 ) { ball->velocity.x = -ball->velocity.x; playSoundBounce = true; } } collision = COLLIDE_NONE; // Check for collision with bricks for ( i=0; icheckCollision( ballCenter, ball->velocity ); if ( collision ) { // Now break the brick if there was a collision brick->isBroken = true; brick->ent->fadeOut = true; if ( brick->powerup > POWERUP_NONE ) { CreatePowerup( brick ); } numBricks--; gameScore += 100; updateScore = true; // Go ahead an forcibly remove the last brick, no fade if ( numBricks == 0 ) { brick->ent->removed = true; } board[i].Remove( brick ); break; } } if ( collision ) { playSoundBrick = true; break; } } if ( collision == COLLIDE_DOWN || collision == COLLIDE_UP ) { ball->velocity.y *= -1; } else if ( collision == COLLIDE_LEFT || collision == COLLIDE_RIGHT ) { ball->velocity.x *= -1; } if ( playSoundBounce ) { session->sw->PlayShaderDirectly( "arcade_ballbounce", bounceChannel ); } else if ( playSoundBrick ) { session->sw->PlayShaderDirectly( "arcade_brickhit", bounceChannel ); } if ( playSoundBounce || playSoundBrick ) { bounceChannel++; if ( bounceChannel == 4 ) { bounceChannel = 1; } } } // Check to see if any balls were removed from play for ( ballnum=0; ballnumremoved ) { ballsInPlay--; balls.RemoveIndex( ballnum ); } } // If all the balls were removed, update the game accordingly if ( ballsInPlay == 0 ) { if ( ballsRemaining == 0 ) { gameOver = true; // Game Over sound session->sw->PlayShaderDirectly( "arcade_sadsound", S_UNIQUE_CHANNEL ); } else { ballsRemaining--; // Ball was lost, but game is not over session->sw->PlayShaderDirectly( "arcade_missedball", S_UNIQUE_CHANNEL ); } ClearPowerups(); updateScore = true; } } /* ============================= idGameBustOutWindow::UpdateGame ============================= */ void idGameBustOutWindow::UpdateGame() { int i; if ( onNewGame ) { ResetGameState(); // Create Board SetCurrentBoard(); gamerunning = true; } if ( onContinue ) { gameOver = false; ballsRemaining = 3; onContinue = false; } if ( onNewLevel ) { currentLevel++; ClearBoard(); SetCurrentBoard(); ballSpeed = BALL_SPEED * ( 1.f + ((float)currentLevel/5.f) ); if ( ballSpeed > BALL_MAXSPEED ) { ballSpeed = BALL_MAXSPEED; } updateScore = true; onNewLevel = false; } if(gamerunning == true) { UpdatePaddle(); UpdateBall(); UpdatePowerups(); for( i = 0; i < entities.Num(); i++ ) { entities[i]->Update( timeSlice, gui->GetTime() ); } // Delete entities that need to be deleted for( i = entities.Num()-1; i >= 0; i-- ) { if( entities[i]->removed ) { BOEntity* ent = entities[i]; delete ent; entities.RemoveIndex(i); } } if ( updateScore ) { UpdateScore(); updateScore = false; } } }