/*
===========================================================================
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;
}
}
}