/*

Firestorm
by Brent P. Newhall 

 */

#include "Firestorm.h"




Window *window;




// ---------- Utilities




float calc_float_number( const float number_part1, const float number,
                         bool is_negative )
    {
    float final_number;
    if( number_part1 == 0 )
        final_number = (float)number;
    else
        final_number = (float)number_part1 + ((float)number * 0.1);
    if( is_negative )
        final_number = 0 - final_number;
    return final_number;
    }




std::vector<loat>* parse_line( const char * line )
    {
    std::vector<loat>*v;
    v = new std::vector<loat>
    unsigned int i, number = 0, number_part1 = 0;
    char c;
    bool in_number = false;
    bool is_negative = false;
    for( i = 0; i < strlen( line ); i++ )
        {
        c = line[i];
        if( c >= 48  &&  c <= 57 ) // Is a digit
            {
            if( in_number )
                number = (number * 10) + (c - 48);
            else
                {
                in_number = true;
                number = (c - 48);
                }
            }
        else if( c == '.' )
            {
            if( in_number )
                {
                number_part1 = number;
                number = 0;
                }
            }
        else if( c == '-' )
            {
            is_negative = true;
            }
        else
            {
            if( in_number )
                {
                in_number = false;
                v->push_back( calc_float_number( number_part1, number,
                              is_negative ) );
                number = 0;
                number_part1 = 0;
                }
            is_negative = false;
            }
        }
    if( in_number )
        v->push_back( calc_float_number( number_part1, number, is_negative ) );
    return v;
    }





// ---------- Star




Star :: Star()
    {
    PlaceRandomly();
    }




void Star :: PlaceRandomly( void )
    {
    x = rand() % (int)(window->GetBounds().Width());
    y = rand() % (int)(window->GetBounds().Height());
    depth = rand() % 3;
    }




// ---------- Bullet




Bullet :: Bullet()
    {
    size = BULLET_SMALL;
    location.left = 300.0;
    location.top = 50.0;
    velocity.x = -3.0;
    velocity.y = 0.0;
    color = RED;
    heartbeat = 0;
    sync();
    }




Bullet :: Bullet( float x, float y )
    {
    size = BULLET_SMALL;
    location.left = x;
    location.top = y;
    velocity.x = -3.0;
    velocity.y = 0.0;
    color = RED;
    heartbeat = 0;
    sync();
    }




void Bullet :: sync( void )
    {
    if( size == BULLET_SMALL )
        {
        location.right = location.left + 4;
        location.bottom = location.top + 4;
        }
    else if( size == BULLET_WAVE )
        {
        location.right = location.left + 4;
        location.bottom = location.top + 30;
        }
    }




void Bullet :: update( void )
    {
    heartbeat++;
    location.left   += velocity.x;
    location.right  += velocity.x;
    location.top    += velocity.y;
    location.bottom += velocity.y;
    if( size == BULLET_WAVE  &&  heartbeat % 10 == 0 )
        {
        location.top -= 2;
        location.bottom += 2;
        }
    }




// ---------- Player




Player :: Player()
    {
    setColor( RED );
    location.x = 10.0;
    location.y = 50.0;
    setBounds( 5.0, 5.0, 300.0, 150.0 );
    }



void Player :: setColor( unsigned int inColor )
    {
    if( inColor < 1  ||  inColor > 2 )
        return;
    color = inColor;
    String filename = "^/graphics/player_";
    if( color == RED )
        filename += "red";
    else
        filename += "yellow";
    filename += ".png";
    image = new BitmapImage( new File( filename ) );
    }




void Player :: move( float xDelta, float yDelta )
    {
    location.x += xDelta;
    location.y += yDelta;
    if( location.x < boundary.left )
        location.x = boundary.left;
    else if( location.x > boundary.right )
        location.x = boundary.right;
    if( location.y < boundary.top )
        location.y = boundary.top;
    else if( location.y > boundary.bottom )
        location.y = boundary.bottom;
    }




void Player :: setBounds( float inLeft, float inTop, float inRight, float inBottom )
    {
    boundary.left = inLeft;
    boundary.right = inRight;
    boundary.top = inTop;
    boundary.bottom = inBottom;
    if( boundary.left < 0.0 )
        boundary.left = 0.0;
    if( boundary.top < 0.0 )
        boundary.top = 0.0;
    }




void Player :: draw( View *drawingView )
    {
    drawingView->SetDrawingMode( DM_BLEND );
    image->Draw( location, drawingView );
    drawingView->SetDrawingMode( DM_COPY );
    }




// ---------- Enemy




EnemyMovementEvent :: EnemyMovementEvent()
    {
    time = 0;
    velocity.x = -3.0;
    velocity.y = 0.0;
    }




Enemy :: Enemy()
    {
    type = 0;
    health = 0;
    location.left = 0;
    location.top = 0;
    location.right = 63;
    location.bottom = 63;
    velocity.x = 0.0;
    velocity.y = 0.0;
    direction = 0.0;
    color = 1;
    pathTicks = 100;
    heartbeat = 0;
    nextMovement = 0;
    image = NULL;
    }




void Enemy :: SetPath( std::vector<loat>*pathNumbers )
    {
    Enemy();
    unsigned int i;
    EnemyMovementEvent event;
    movements.clear();
    /* We grab the last element in each tuple and the two before it;
       otherwise, we might get near the end of an uneven vector and try to
       look beyond the last item. */
    for( i = 2; i < pathNumbers->size(); i = i + 3 )
        {
        event.time = (int)((*pathNumbers)[i - 2]);
        event.velocity.x  = (*pathNumbers)[i - 1];
        event.velocity.y  = (*pathNumbers)[i];
        movements.push_back( event );
        }
    }




void Enemy :: setType( unsigned int inType )
    {
    if( inType < 1  ||  inType > 3 )
        return;
    type = inType;
    health = 1;
    if( type == 3 )
        health = 3;
    String filename = "^/graphics/enemy";
    char enemyTypeString[3];
    sprintf( enemyTypeString, "%d", type );
    filename += (String)(enemyTypeString) + "_";
    if( color == 1 )
        filename += "red";
    else
        filename += "yellow";
    filename += ".png";
    image = new BitmapImage( new File( filename ) );
    Point p = image->GetSize();
    location.right = location.left + p.x;
    location.bottom = location.top + p.y;
    }




void Enemy :: addDamage( unsigned int damage )
    {
    health = health - damage;
    if( health < 0 )
        health = 0;
    }




void Enemy :: update( void )
    {
    heartbeat++;
    unsigned int pathTick = heartbeat % pathTicks;
    if( movements[nextMovement].time == pathTick )
        {
        /* Fire a bullet */
        if( movements[nextMovement].velocity.x <  0.1  &&
            movements[nextMovement].velocity.x > -0.1  &&
            movements[nextMovement].velocity.y <  0.1  &&
            movements[nextMovement].velocity.y > -0.1 )
            {
            Message msg( MSG_FIRE_ENEMY_BULLET );
            msg.AddPoint( "location", Point( location.left, location.top - 32 ) );
            msg.AddInt32( "type", (int32)color );
            window->PostMessage( &msg, window );
            }
        else
            {
            /* Activate the next movement */
            velocity = movements[nextMovement].velocity;
            if( nextMovement == movements.size() - 1 )
                nextMovement = 0;
            else
                nextMovement++;
            }
        }
    location.left   += velocity.x;
    location.right  += velocity.x;
    location.top    += velocity.y;
    location.bottom += velocity.y;
    }




EnemyManager :: EnemyManager()
    {
    levelTimeCount = 0;
    mainWindow = NULL;
    }




status_t EnemyManager :: set( String levelName )
    {
    levelTimeCount = 0;
    return eventQueue.set( levelName );
    }




status_t EnemyManager :: heartbeat( View *drawingView, Point playerPoint )
    {
    unsigned int i, j;
    levelTimeCount++;
    Rect playerLocation( playerPoint.x, playerPoint.y,
                         playerPoint.x + 30, playerPoint.y + 30 );
    /* Move existing enemies */
    for( i = 0; i < enemies.size(); i++ )
        {
        enemies[i].update();
        /* Remove this enemy if it goes off the screen */
        if( enemies[i].location.right <= 1 )
            {
            enemies.erase( enemies.begin() + i );
            i--;
            }
        /* Remove this enemy if it collides with the player */
        else if( i >= 0 )
            {
            if( enemies[i].location.DoIntersect( playerLocation ) )
                {
                enemies.erase( enemies.begin() + i );
                i--;
                Message *msg = new Message( MSG_CHANGE_HEALTH );
                msg->AddFloat( "healthChange", -25.0 );
                mainWindow->PostMessage( msg, mainWindow );
                }
            }
        }
    /* Check for collisions between bullets and enemies */
    for( i = 0; i < bullets.size(); i++ )
        {
        for( j = 0; i < bullets.size()  &&  j < enemies.size(); j++ )
            {
            if( enemies[j].location.DoIntersect( bullets[i].location )  &&
                enemies[j].color == bullets[i].color )
                {
                bullets.erase( bullets.begin() + i );
                i--;
                enemies[j].addDamage( 1 );
                if( enemies[j].getHealth() <= 0 )
                    {
                    delete enemies[j].image;
                    enemies.erase( enemies.begin() + j );
                    j--;
                    if( mainWindow != NULL )
                        {
                        Message *msg = new Message( MSG_INCREMENT_SCORE );
                        if( enemies[j].type == 3 )
                            msg->AddInt32( "score", 100 );
                        else
                            msg->AddInt32( "score", 50 );
                        mainWindow->PostMessage( msg, mainWindow );
                        }
                    }
                }
            }
        /* Check for collision between bullets and player */
        if( i >= 0  &&  i < bullets.size() )
            {
            if( bullets[i].location.DoIntersect( playerLocation ) )
                {
                bullets.erase( bullets.begin() + i );
                i--;
                Message *msg = new Message( MSG_CHANGE_HEALTH );
                msg->AddFloat( "healthChange", -5.0 );
                mainWindow->PostMessage( msg, mainWindow );
                }
            }
        }
    /* Check for new enemies in queue */
    while( eventQueue.events.size() > 0  &&
           eventQueue.events[0]->time == levelTimeCount )
        {
        Enemy enemy;
        enemy.location.left = drawingView->GetBounds().Width();
        enemy.location.top = eventQueue.events[0]->location;
        enemy.color = eventQueue.events[0]->color;
        enemy.setType( eventQueue.events[0]->enemyType );
        enemy.direction = -0.5;
        std::vector<loat>pathNumbers;
        std::vector<loat>*v = parse_line(
           eventQueue.events[0]->path.c_str() );
        enemy.pathTicks = (unsigned int)((*v)[1]);
        for( i = 2; i < (*v).size(); i++ )
            {
            pathNumbers.push_back( (*v)[i] );
            }
        enemy.SetPath( &pathNumbers );
        enemies.push_back( enemy );
        eventQueue.events.erase( eventQueue.events.begin() );
        }
    if( eventQueue.events.size() == 0  &&  enemies.size() == 0 )
        {
        mainWindow->PostMessage( new Message( MSG_LEVEL_COMPLETE ), mainWindow );
        }
    /* Draw all existing enemies */
    drawingView->SetDrawingMode( DM_BLEND );
    for( i = 0; i < enemies.size(); i++ )
        {
        enemies[i].image->Draw( Point( enemies[i].location.left,
                                       enemies[i].location.top ), drawingView );
        }
    /* Move bullets */
    for( i = 0; i < bullets.size(); i++ )
        bullets[i].update();
    /* Draw all bullets */
    for( i = 0; i < bullets.size(); i++ )
        {
        bullets[i].update();
        drawingView->SetFgColor( bullets[i].getColor() );
        drawingView->FillRect( bullets[i].location );
        /* Get rid of out-of-bounds bullets */
        if( bullets[i].location.right <= 0  ||  bullets[i].location.left >= drawingView->GetBounds().Width() )
            {
            std::vector<ullet>:iterator it;
            it = bullets.begin() + i;
            bullets.erase( it );
            }
        }
    drawingView->SetDrawingMode( DM_COPY );
    return 0;
    }




void EnemyManager :: addBullet( float x, float y, float xVelocity, float yVelocity, unsigned int color, unsigned int size )
    {
    Bullet b( x, y );
    b.velocity.x = xVelocity;
    b.velocity.y = yVelocity;
    b.size = size;
    b.color = color;
    b.sync();
    bullets.push_back( b );
    }




// ---------- Event Queue




LevelEvent :: LevelEvent()
    {
    time = 0;
    location = 40.0;
    enemyType = 1;
    color = 1;
    }




LevelEventQueue :: LevelEventQueue()
    {
    }




status_t LevelEventQueue :: set( String levelName )
    {
    std::vector<loat>*lineOfNumbers;
    std::vector<tring>paths;
    events.erase( events.begin(), events.end() );
    FileReference ref( Directory( "^/levels" ), levelName.c_str() );
    if( ! ref.IsValid() )
        return( 1 );
    String path;
    ref.GetPath( &path );
    std::fstream levelFile( path.c_str(), std::ios::in );
    if( ! levelFile )
        return( 1 );
    String line;
    char cline[4096];
    bool inPaths = true;
    while( ! levelFile.eof() )
        {
        levelFile.getline( cline, 4096 );
        line = (String)cline;
        if( line.substr( 0, 7 ) == "[paths]" )
            inPaths = true;
        else if( line.substr( 0, 7 ) == "[ships]" )
            inPaths = false;
        else if( inPaths  &&  cline[0] != '#' )
            {
            lineOfNumbers = parse_line( cline );
            if( lineOfNumbers->size() > 0 )
                {
                unsigned int pathIndex = (unsigned int)((*lineOfNumbers)[0]);
                while( paths.size() < pathIndex + 1 )
                    paths.push_back( "" );
                paths[pathIndex] = line;
                }
            }
        else if( ! inPaths &&  cline[0] != '#' ) // In Ships section
            {
            lineOfNumbers = parse_line( cline );
            if( lineOfNumbers->size() > 0 )
                {
                LevelEvent *e = new LevelEvent();
                e->time = (unsigned int)(*lineOfNumbers)[0];
                e->location = (*lineOfNumbers)[1];
                e->enemyType = (unsigned int)(*lineOfNumbers)[2];
                e->color = (unsigned int)(*lineOfNumbers)[3];
                e->path = paths[ (unsigned int)(*lineOfNumbers)[4] ];
                events.push_back( e );
                }
            }
        }
    levelFile.close();
    return( 0 );
    }




// ---------- Status View



StatusView :: StatusView( const Rect& r )
    : View( r, "StatusView", CF_FOLLOW_LEFT | CF_FOLLOW_TOP,
            WID_WILL_DRAW | WID_CLEAR_BACKGROUND )
    {
    scoreStringView = new StringView( Rect( 1, 1, 30, GetBounds().bottom - 1 ), "scoreStringView", "0" );
    scoreStringView->SetFgColor( 0, 0, 0 );
    AddChild( scoreStringView );
    score = 0;
    health = 100;
    }





void StatusView :: Paint( const Rect& updateRect )
    {
    View::Paint( updateRect );
    /* Draw background bar */
    SetFgColor( 80, 80, 80 );
    FillRect( Rect( GetBounds().right - 106, 2, GetBounds().right - 4,
                    GetBounds().bottom - 2 ) );
    /* Draw health bar */
    SetFgColor( 140, 140, 240 );
    FillRect( Rect( GetBounds().right - 105, 3, GetBounds().right - 105 + health,
                    GetBounds().bottom - 3 ) );
    }




void StatusView :: HandleMessage( Message *pcMessage )
    {
    switch( pcMessage->GetCode() )
        {
        case MSG_INCREMENT_SCORE:
            {
            int32 newScoreValue;
            char s[20];
            pcMessage->FindInt32( "score", &newScoreValue );
            score = score + newScoreValue;
            if( score < 0 )
                score = 0;
            sprintf( s, "%d", score );
            scoreStringView->SetString( s );
            }
            break;
        case MSG_CHANGE_HEALTH:
            {
            float healthChange;
            pcMessage->FindFloat( "healthChange", &healthChange );
            health = health + healthChange;
            if( health < 0.0 )
                health = 0.0;
            else if( health > 100.0 )
                health = 100.0;
            Invalidate();
            }
            break;
        default:
            View :: HandleMessage( pcMessage );
        }
    }




// ---------- Game View



GameView :: GameView( const Rect& r )
    : View( r, "GameView", CF_FOLLOW_LEFT | CF_FOLLOW_TOP,
            WID_WILL_DRAW | WID_CLEAR_BACKGROUND )
    {
    /* Initialize star colors */
    star_color[0].red   = 50;
    star_color[0].green = 50;
    star_color[0].blue  = 50;
    star_color[1].red   = 150;
    star_color[1].green = 150;
    star_color[1].blue  = 150;
    star_color[2].red   = 250;
    star_color[2].green = 250;
    star_color[2].blue  = 250;
    /* Initialize keyboard key states */
    holdingUpArrow    = false;
    holdingDownArrow  = false;
    holdingLeftArrow  = false;
    holdingRightArrow = false;
    holdingFireButton = 0;
    /* Initialize player */
    player.setBounds( r.left, r.top, r.right - 100, r.bottom - 70 );
    rechargeDelay = 0;
    /* Initialize stars */
    unsigned int i;
    for( i = 0; i < NUM_STARS; i++ )
        {
        stars[i].PlaceRandomly();
        }
    /* Initialize double-buffering controls */
    drawingView = new View( r, "DrawView" );
    offscreenBitmap = new Bitmap( (int)r.Width() + 1, (int)r.Height() + 1,
                                 CS_RGB32,
                                 Bitmap::ACCEPT_VIEWS |
                                 Bitmap::SHARE_FRAMEBUFFER );
    offscreenBitmap->AddChild( drawingView );
    onscreenBitmap = new Bitmap( (int)r.Width() + 1, (int)r.Height() + 1, CS_RGB32 );
    /* Set up sounds */
    pcManager = MediaManager::Get();
    bulletSound = new MediaSoundPlayer();
    FileReference ref( Directory( "^/sounds" ), "Bullet.ogg" );
    String path;
    ref.GetPath( &path );
    bulletSound->SetFile( path );
    // Music
    backgroundMusic = new MediaSoundPlayer();
    ref.SetTo( Directory( "^/sounds" ), "technology.ogg" );
    ref.GetPath( &path );
    backgroundMusic->SetFile( path );
    backgroundMusic->Play();
    }




GameView :: ~GameView()
    {
    delete offscreenBitmap;
    delete onscreenBitmap;
    backgroundMusic->Stop();
    pcManager->Put();
    }





void GameView :: AttachedToWindow()
    {
    /* Initialize enemy manager */
    enemyManager.setWindow( GetWindow() );
    enemyManager.set( "level1" );
    }




void GameView :: Paint( const Rect& updateRect )
    {
    DrawBitmap( onscreenBitmap, updateRect, updateRect );
    }




void GameView :: KeyUp( const char *pzString, const char *pzRawString,
                        uint32 nQualifiers )
    {
    int32 nRawKey;
    GetWindow()->GetCurrentMessage()->FindInt32( "_raw_key", &nRawKey );
    if( nRawKey == 0xD7 ) /* Up arrow */
        holdingUpArrow = false;
    else if( nRawKey == 0xE2 ) /* Down arrow */
        holdingDownArrow = false;
    else if( nRawKey == 0xE1 ) /* Left arrow */
        holdingLeftArrow = false;
    else if( nRawKey == 0xE3 ) /* Right arrow */
        holdingRightArrow = false;
    else if( pzRawString[0] == 'f' )
        holdingFireButton = 0;
    else if( pzRawString[0] == 'd' )
        holdingFireButton = 0;
    else if( pzRawString[0] == 's' )
        holdingFireButton = 0;
    }




void GameView :: KeyDown( const char *pzString, const char *pzRawString,
                          uint32 nQualifiers )
    {
    int32 nRawKey;
    GetWindow()->GetCurrentMessage()->FindInt32( "_raw_key", &nRawKey );
    if( nRawKey == 0x57 ) /* Up arrow */
        holdingUpArrow = true;
    else if( nRawKey == 0x62 ) /* Down arrow */
        holdingDownArrow = true;
    else if( nRawKey == 0x61 ) /* Left arrow */
        holdingLeftArrow = true;
    else if( nRawKey == 0x63 ) /* Right arrow */
        holdingRightArrow = true;
    else if( pzRawString[0] == 'f' )
        {
        player.setColor( player.RED );
        holdingFireButton = RED;
        }
    else if( pzRawString[0] == 'd' )
        {
        player.setColor( player.YELLOW );
        holdingFireButton = YELLOW;
        }
    else if( pzRawString[0] == 's' )
        {
        holdingFireButton = FIRING_BOTH_BULLETS;
        }
    else if( pzRawString[0] == ' ' )
        {
        if( player.getColor() == player.RED )
            player.setColor( player.YELLOW );
        else
            player.setColor( player.RED );
        }
    }




void GameView :: GameHeartbeat( void )
    {
    unsigned int i;
    const float playerMovementDelta = 3.0;
    /* Draw black background */
    Rect bounds = GetBounds();
    drawingView->SetFgColor( 0, 0, 0 );
    drawingView->FillRect( bounds );
    /* Draw stars */
    for( i = 0; i < NUM_STARS; i++ )
        {
        stars[i].x -= stars[i].depth + 1;
        if( stars[i].x < 0 )
            {
            stars[i].PlaceRandomly();
            stars[i].x = bounds.Width();
            }
        drawingView->SetFgColor( star_color[stars[i].depth] );
        drawingView->DrawLine( stars[i], stars[i] );
        }
    /* Update and draw enemies */
    enemyManager.heartbeat( drawingView, player.getLocation() );
    /* Update player */
    if( holdingUpArrow )
        player.move( 0, 0 - playerMovementDelta );
    if( holdingDownArrow )
        player.move( 0, playerMovementDelta );
    if( holdingLeftArrow )
        player.move( 0 - playerMovementDelta, 0 );
    if( holdingRightArrow )
        player.move( playerMovementDelta, 0 );
    /* Fire bullets */
    if( holdingFireButton > 0  &&  rechargeDelay == 0 )
        {
        //bulletSound->Play();  /* Commented out because it slows down gameplay a lot */
        if( holdingFireButton == FIRING_BOTH_BULLETS )
            {
            enemyManager.addBullet( player.getLocation().x + 55,
                                    player.getLocation().y + 20, 3, 0,
                                    player.RED, BULLET_SMALL );
            enemyManager.addBullet( player.getLocation().x + 55,
                                    player.getLocation().y + 40, 3, 0,
                                    player.YELLOW, BULLET_SMALL );
            rechargeDelay = 22;
            }
        else
            {
            int yModifier = 10;
            if( player.getColor() == RED )
                yModifier = -10;
            enemyManager.addBullet( player.getLocation().x + 55,
                                    player.getLocation().y + 30 + yModifier, 3, 0,
                                    player.getColor(), BULLET_SMALL );
            rechargeDelay = 10;
            }
        // Send message to parent window, decrementing score by 5 points
        Message *msg = new Message( MSG_INCREMENT_SCORE );
        msg->AddInt32( "score", -5 );
        GetWindow()->PostMessage( msg, GetWindow() );
        }
    /* Update recharge */
    if( rechargeDelay > 0 )
        rechargeDelay--;
    /* Draw player */
    player.draw( drawingView );
    /* Update view */
    drawingView->Sync();
    memcpy( onscreenBitmap->LockRaster(),
            offscreenBitmap->LockRaster(),
            onscreenBitmap->GetBytesPerRow() *
              ((int)onscreenBitmap->GetBounds().Height() + 1) );
    Invalidate();
    Sync();
    }




void GameView :: setLevel( String level )
    {
    enemyManager.set( level );
    }




void GameView :: fireEnemyBullet( Point location, unsigned int type )
    {
    enemyManager.addBullet( location.x - 15, location.y + 63, -4, 0, type, BULLET_SMALL );
    }




// ---------- Game Window




GameWindow :: GameWindow( const Rect& r )
    : Window( r, "FirestormWindow", "Firestorm", WND_NOT_RESIZABLE |
              WND_NO_ZOOM_BUT | WND_NO_DEPTH_BUT | WND_SINGLEBUFFER,
              CURRENT_DESKTOP )
    {
    window = this;
    Rect rect( GetBounds() );
    rect.bottom = 20;
    statusView = new StatusView( rect );
    AddChild( statusView );
    rect.bottom = GetBounds().bottom;
    rect.top = 21;
    gameView = new GameView( rect );
    AddChild( gameView );
    SetFocusChild( gameView );
    thread = new HeartbeatThread();
    thread->Start();
    }




void GameWindow :: HandleMessage( Message *pcMessage )
    {
    switch( pcMessage->GetCode() )
        {
        case MSG_INCREMENT_SCORE:
        case MSG_CHANGE_HEALTH:
            {
            statusView->HandleMessage( pcMessage );
            }
            break;
        case MSG_LEVEL_COMPLETE:
            {
            if( currentLevel < levels.size() - 1 )
                {
                currentLevel++;
                gameView->setLevel( levels[currentLevel] );
                }
            }
            break;
        case MSG_FIRE_ENEMY_BULLET:
            {
            Point location;
            int32 type;
            pcMessage->FindPoint( "location", &location );
            pcMessage->FindInt32( "type", &type );
            gameView->fireEnemyBullet( location, (unsigned int)type );
            }
            break;
        case MSG_HEARTBEAT:
            gameView->GameHeartbeat();
            break;
        default:
            Window :: HandleMessage( pcMessage );
        }
    }




bool GameWindow :: OkToQuit( void )
    {
    thread->Terminate();
    Application::GetInstance()->PostMessage( M_QUIT );
    return( true );
    }



status_t GameWindow :: loadLevelSet( String level_set )
    {
    currentLevel = 0;
    levels.erase( levels.begin(), levels.end() );
    FSNode node( Directory( "^/levels" ), level_set.c_str() );
    if( ! node.IsValid() )
        {
        levels.push_back( "level1" );
        return( 1 );
        }
    FileReference ref( Directory( "^/levels" ), level_set.c_str() );
    String path;
    ref.GetPath( &path );
    std::fstream levelSetFile( path.c_str(), std::ios::in );
    if( ! levelSetFile )
        return( 1 );
    String line;
    char cline[4096];
    while( ! levelSetFile.eof() )
        {
        levelSetFile.getline( cline, 4096 );
        line = (String)cline;
        if( cline[0] != '#'  &&  line.Length() > 0 )
            {
            node.SetTo( Directory( "^/levels" ), cline );
            if( node.IsValid() )
                levels.push_back( line );
            }
        }
    levelSetFile.close();
    gameView->setLevel( levels[0] );
    return( 0 );
    }