If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle

See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// ge_engine.cpp

#include "game.h"

/*
EEngine is the master object which puts levels and air units
etc... together.
It is used by both the actual game and the level editor

Implementation is spread out into ge_air.cpp as well.
*/

EEngine *ge = 0; // only one game engine at any time

/*
==============================================================================

EEngine CORE IMPLEMENTATION

==============================================================================
*/

/*
==============
EEngine::EEngine

Initialize a null state
==============
*/
EEngine::EEngine()
    : GPanel(0, 0, 0, 640, 480)
{
    lassert(!ge); // only one engine can exist at any time
    ge = this;

    SetCanFocus(true);
    SetVisible(false);

    m_iState = ge_none;

    memset(&m_player, 0, sizeof(m_player));
    m_pLevel = 0;

    m_bPaused = false;
    m_bEndGame = false;
    m_iLevel = -1;
    m_iLevelAdjust = 0;

    // the player will be set before Begin() in PokePlayer()
    Air_Init();
}

/*
==============
EEngine::~EEngine

Cleanup anything that needs cleaning.
==============
*/
EEngine::~EEngine()
{
    Air_Shutdown();

    ge = 0;
}

/*
==============
EEngine::MouseCursor

Disable the mouse cursor while playing
==============
*/
const char *EEngine::MouseCursor()
{
    return 0;
}

/*
==============
EEngine::SetLevelAdjust
==============
*/
void EEngine::SetLevelAdjust(int lvl, int adjust)
{
    m_iLevel = lvl;
    m_iLevelAdjust = adjust;

    m_flHealthFactor = 1 + adjust * 0.2;
    m_iFrameTime = LOGICTIME - (adjust*3/2);
    if (m_iFrameTime < 1)
        m_iFrameTime = 1;
}

/*
==============
EEngine::SetLevel

Set the level that's to be used. Watch out: ownership is not transferred
==============
*/
void EEngine::SetLevel(ELevel *level, bool edit)
{
    lassert(m_iState == ge_none);

    m_pLevel = level;
}

/*
==============
EEngine::PokePlayer
EEngine::PeekPlayer

Exchange player state from/to engine
==============
*/
void EEngine::PokePlayer(const player_t *player, const LConfig *cfg)
{
    lassert(m_iState == ge_none);
    lassert(!m_player.ship);

    memcpy(&m_player.data, player, sizeof(player_t));

    m_player.ctrl.speed = 400.0;

    m_player.ctrl.up = FixKey(cfg->GetInt("up"));
    m_player.ctrl.down = FixKey(cfg->GetInt("down"));
    m_player.ctrl.left = FixKey(cfg->GetInt("left"));
    m_player.ctrl.right = FixKey(cfg->GetInt("right"));
    m_player.ctrl.fire = FixKey(cfg->GetInt("fire"));
    m_player.ctrl.weaponswitch = FixKey(cfg->GetInt("weaponswitch"));

    new AirPlayer(&m_player, 320, 480-32);
}

void EEngine::PeekPlayer(player_t *player)
{
    memcpy(player, &m_player.data, sizeof(player_t));
}

/*
==============
EEngine::Begin

Setup everything and set the engine state to running
==============
*/
void EEngine::Begin()
{
    lassert(m_iState == ge_none);
    lassert(m_player.ship);

    gametime = 0;
    curframe = 0;
    m_iBossDestroyed = 0;
    m_iPrewinTime = TOTALPREWINTIME;
    m_iFadeoutTime = 0;

    m_pLevel->BeginPlay(m_iLevelAdjust);

    Hud_Load();

    m_iState = ge_running;
}

/*
==============
EEngine::End

Cleanup after the game ends
==============
*/
void EEngine::End()
{
    Hud_Unload();

    m_pLevel->EndPlay();

    Air_Shutdown();
}

/*
==============
EEngine::Logic

Run the game
==============
*/
void EEngine::Logic()
{
    if (m_bPaused && m_iState == ge_running)
        return;

    if (m_iState != ge_abort) {
        gametime += hal->frametime;

        while(curframe < gametime) {
            m_pLevel->Logic();

            Air_Logic();

            curframe += m_iFrameTime;
        }
    }

    if (m_pLevel->m_iNumBosses && m_iBossDestroyed >= m_pLevel->m_iNumBosses)
        m_iPrewinTime -= hal->frametime;

    if (m_iState != ge_running) {
        m_iFadeoutTime += hal->frametime;

        if (m_iFadeoutTime >= TOTALFADETIME) {
            if (m_player.ship && m_player.ship->m_flHealth <= 0)
                m_iState = ge_loose;

            if (m_iState == ge_loose)
                EndModal(ENGINE_LOOSE);
            else if (m_iState == ge_win)
                EndModal(ENGINE_WIN);
            else
                EndModal(ENGINE_ABORT);

            m_bEndGame = true;
            return;
        }
    }
}

/*
==============
EEngine::Draw

Refresh the screen
==============
*/
void EEngine::Draw()
{
    char buf[32];

    oldfract = (float)(curframe - gametime) / m_iFrameTime;
    curfract = 1.0 - oldfract;

    m_pLevel->Draw();

    Air_Draw();

    if (!m_bPaused || m_iState != ge_running)
    {
        Hud_Draw();

        if (m_iLevel >= 0)
            sprintf(buf, "Level: %04i", m_iLevel+1);
        else
            strcpy(buf, "EDITOR");
        hud_pFont->DrawString(0, 0, buf, 0, 0, 0, 255, 0, 0, 255);

        if (m_iState != ge_running) {
            float time;

            time = (float)m_iFadeoutTime / TOTALFADETIME;
            if (time < 0)
                time = 0;
            else if (time > 1.0)
                time = 1.0;

            hal->FillRect(0, 0, 640, 480, 0, 0, 0, (int)(255 * time));
        }
    }
    else
    {
        float fr;
        int r, g, b;

        hal->FillRect(0, 0, 640, 480, 0, 0, 0, 96);

        fr = 0.4 * (hal->framestart % 2000) / 2000.0;
        if (fr > 0.2)
            fr = 0.4 - fr;
        fr = 1.0 - fr;

        r = (int)(fr * 255);
        g = (int)(fr * 0);
        b = (int)(fr * 0);

        hud_pFont->DrawString(320, 240, "PAUSED", halign_center|valign_center, 0, 0,
            r, g, b, 255);
    }
}

/*
==============
EEngine::Key

Process key events. Usually passed on to the player ship
==============
*/
bool EEngine::Key(int code, char c, bool down)
{
    if (down) {
        switch(code) {
        case KEY_ESCAPE:
            if (m_iState == ge_running)
                m_iState = ge_abort;
            return true;

        case KEY_p:
            SetPaused(!m_bPaused);
            return true;
        }
    }

    if (m_player.ship) {
        if (m_player.ship->Input(code, c, down))
            return true;
    }

    return false;
}

/*
==============================================================================

EEngine HUD IMPLEMENTATION

==============================================================================
*/

typedef struct hudicon_s {
    char        *name;
    sprite_t    *sprite;
} hudicon_t;

hudicon_t ge_hudicons[] = {
    { "item01sm", 0 },
    { "item02sm", 0 },
    { "atgsm", 0 },
    { "smlaser", 0 },

    // must be last!
    { 0, 0 }
};

#define HUD_WEAPONS     0

/*
==============
EEngine::Hud_Draw

Display the HUD
==============
*/
void EEngine::Hud_Draw()
{
    AirPlayer *pShip;
    char money[32];
    int max, num, i;
    int x, y;
    float r, g, b;
    float stepr, stepg, stepb;

    pShip = m_player.ship;
    if (!pShip)
        return;

    max = 480 / hud_pHealth->size[1];
    num = (int)(pShip->m_flHealth / 100.0 * max);
    if (num < 0)
        num = 0;
    else if (num > max)
        num = max;

    x = 640 - hud_pHealth->size[0];
    y = 480;

    r = 96.0;
    stepr = (255.0 - 96.0) / max;
    g = 0;
    stepg = (140.0 - 0.0) / max;
    b = 0;
    stepb = 0;

    for(i = 0; i < num; i++) {
        y -= hud_pHealth->size[1];
        hud_pHealth->Draw(x, y, (int)r, (int)g, (int)b, 255);
        r += stepr;
        g += stepg;
        b += stepb;
    }
    hal->FillRect(x, 0, hud_pHealth->size[0], y, 0, 0, 0, 255);

    sprintf(money, "%i\x80", m_player.data.money);
    hud_pFont->DrawString(320, 8, money, halign_center, 0, 0);

    if (pShip->m_iCurWeapon >= 0)
        SPR_Draw(ge_hudicons[HUD_WEAPONS + pShip->m_iCurWeapon].sprite, 600, 30, -1, 0);
}

/*
==============
EEngine::Hud_Unload

Free HUD graphics
==============
*/
void EEngine::Hud_Unload()
{
    hudicon_t *hi;

    hud_pHealth->Release();

    hud_pFont->Release();

    for(hi = ge_hudicons; hi->name; hi++)
        SPR_Free(hi->sprite);
}

/*
==============
EEngine::Hud_Load

Load HUD graphics
==============
*/
void EEngine::Hud_Load()
{
    char path[MAX_OSPATH];
    hudicon_t *hi; // (greetings)

    hud_pHealth = HPicture::Get("items/health", pic_normal|pic_nosubpixel, 0);

    hud_pFont = HFont::Get("font");

    for(hi = ge_hudicons; hi->name; hi++) {
        snprintf(path, sizeof(path), "items/%s", hi->name);
        hi->sprite = SPR_Get(path);
    }
}