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>
*/
// game.c

#include "rtts.h"

#include "rttspanels.h"

#include "ff_savegame.h"

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

HANGAR

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

/*
Stuff supporting the actual game, such as briefing screens,
character save / load, etc...
*/

typedef struct buyitem_s {
    char        *name;
    int         id;
    sprite_t    *sprite;
    char        *icon;
    int         cash_buy, cash_sell;

    int (*Inventory)(player_t *player, int id, bool *pbBuy, bool *pbSell);
    void (*Action)(player_t *player, int id, bool bBuy);
} buyitem_t;

game_t game;

int gs_buyitem_num;

/*
==============
Energy_Inventory

Energy cells (shield)
==============
*/
int Energy_Inventory(player_t *player, int id, bool *pbBuy, bool *pbSell)
{
    if (player->health <= 25)
        *pbSell = false;
    else
        *pbSell = true;

    if (player->health >= 100)
        *pbBuy = false;
    else
        *pbBuy = true;

    return player->health;
}

/*
==============
Energy_Action

Buy or sell shield (money is taken care of by the caller)
==============
*/
void Energy_Action(player_t *player, int id, bool bBuy)
{
    if (bBuy) {
        player->health += 25;
        if (player->health > 100)
            player->health = 100;
    } else {
        player->health -= 25;
        if (player->health < 1)
            player->health = 1;
    }
}

/*
==============
Weapon_Inventory
==============
*/
int Weapon_Inventory(player_t *player, int id, bool *pbBuy, bool *pbSell)
{
    if (player->weapons[id]) {
        *pbBuy = false;
        *pbSell = true;
    } else {
        *pbBuy = true;
        *pbSell = false;
    }

    return player->weapons[id];
}

/*
==============
Weapon_Action
==============
*/
void Weapon_Action(player_t *player, int id, bool bBuy)
{
    if (bBuy) {
        if (player->weapons[id] < 1)
            player->weapons[id]++;
    } else {
        if (player->weapons[id] > 0)
            player->weapons[id]--;
    }
}

buyitem_t gs_buyitems[] = {
    { "ENERGY CELL", 0,     0, "item00", 10000, 5000, Energy_Inventory, Energy_Action },

    { "ROCKETS", WEAP_ROCKET, 0, "item01", 32000, 16000, Weapon_Inventory, Weapon_Action },
    { "DUMBFIRE", WEAP_DUMBFIRE, 0, "item02", 96000, 48000, Weapon_Inventory, Weapon_Action },
    { "ATG", WEAP_ATG, 0, "atg", 156000, 78000, Weapon_Inventory, Weapon_Action },
    { "SMALL LASER", WEAP_SMLASER, 0, "smlaser", 340000, 170000, Weapon_Inventory, Weapon_Action },

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


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

HANGAR

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

class Hangar : public GLabel {
public:
    int         m_iBuyItem;

    GLabel      *m_pLevel;
    GLabel      *m_pMoney;
    GLabel      *m_pBuyItem;
    GLabel      *m_pAmount;

    GButton     *m_pBuy;
    GButton     *m_pSell;

public:
    Hangar();
    ~Hangar();

    void Refresh();

    void Launch(int id);
    void SavePlayer(int id);
    void ExitHangar(int id);

    void LeftBtn(int id);
    void RightBtn(int id);

    void Buy(int id);
    void Sell(int id);

    virtual void Draw();

    virtual bool Key(int code, char c, bool down);
};

/*
==============
MakeHangar

Create a hangar object. This is in a function so that I don't need
to put the Hangar class into a header file
==============
*/
GPanel *MakeHangar()
{
    return new Hangar;
}

/*
==============
Hangar::Hangar
==============
*/
Hangar::Hangar()
    : GLabel(0, "hangar", pic_normal, 0, 0, 0, 640, 480)
{
    buyitem_t *bi;
    char path[MAX_OSPATH];
    GButton *pBtn;

    SetCanFocus(true);

    // Precache buyitem sprites
    m_iBuyItem = 0;
    for(bi = gs_buyitems, gs_buyitem_num = 0; bi->name; bi++, gs_buyitem_num++) {
        snprintf(path, sizeof(path), "items/%s", bi->icon);
        bi->sprite = SPR_Get(path);
    }

    // Create GUI elements
    m_pLevel = new GLabel(this, 5, 160, 200, 16);
    m_pLevel->SetColor(255, 255, 0);

    m_pMoney = new GLabel(this, 435, 160, 200, 16, 0, halign_right);
    m_pMoney->SetColor(255, 255, 0);

    pBtn = new RttsButton(this, 50, 205, 200, 30, "LAUNCH", valign_center);
    pBtn->Clicked.Connect(this, &Hangar::Launch);

    pBtn = new RttsButton(this, 50, 235, 200, 30, "SAVE PLAYER", valign_center);
    pBtn->Clicked.Connect(this, &Hangar::SavePlayer);

    m_pBuyItem = new GLabel(this, 244, 235, 400, 16, 0, halign_center);
    m_pBuyItem->SetColor(255, 255, 64);

    pBtn = new RttsButton(this, 290, 225, 36, 36, "\x01", halign_center|valign_center);
    pBtn->Clicked.Connect(this, &Hangar::LeftBtn);

    pBtn = new RttsButton(this, 564, 225, 36, 36, "\x02", halign_center|valign_center);
    pBtn->Clicked.Connect(this, &Hangar::RightBtn);

    m_pAmount = new GLabel(this, 272, 334, 100, 16, 0, halign_center);
    m_pAmount->SetColor(255, 255, 0);

    m_pBuy = new RttsButton(this, 300, 410, 200, 40);
    m_pBuy->Clicked.Connect(this, &Hangar::Buy);

    m_pSell = new RttsButton(this, 390, 410, 200, 40, 0, halign_right);
    m_pSell->Clicked.Connect(this, &Hangar::Sell);

    pBtn = new RttsButton(this, 50, 425, 200, 30, "EXIT HANGAR", valign_center);
    pBtn->Clicked.Connect(this, &Hangar::ExitHangar);

    Refresh();
}

/*
==============
Hangar::~Hangar

Free precached sprites
==============
*/
Hangar::~Hangar()
{
    buyitem_t *bi;

    for(bi = gs_buyitems; bi->name; bi++)
        SPR_Free(bi->sprite);
}

/*
==============
Hangar::Refresh

Update labels with the correct texts
==============
*/
void Hangar::Refresh()
{
    char buf[32];
    buyitem_t *bi;
    bool bCanBuy, bCanSell;
    int iAmount;

    // Level and money
    sprintf(buf, "Level: %04i", game.player.level+1);
    m_pLevel->SetText(buf);

    sprintf(buf, "%i\x80", game.player.money);
    m_pMoney->SetText(buf);

    // Buy item
    bi = &gs_buyitems[m_iBuyItem];

    iAmount = bi->Inventory(&game.player, bi->id, &bCanBuy, &bCanSell);
    if (game.player.money < bi->cash_buy)
        bCanBuy = false;

    m_pBuyItem->SetText(bi->name);

    sprintf(buf, "%i", iAmount);
    m_pAmount->SetText(buf);

    sprintf(buf, "BUY\n%i\x80", bi->cash_buy);
    m_pBuy->SetText(buf);
    m_pBuy->AdjustSize();
    m_pBuy->SetOblivious(!bCanBuy);

    sprintf(buf, "SELL\n%i\x80", bi->cash_sell);
    m_pSell->SetText(buf);
    m_pSell->AdjustSize();
    m_pSell->SetOblivious(!bCanSell);
}

/*
==============
Hangar::Launch
Hangar::ExitHangar
Hangar::LeftBtn
Hangar::RightBtn
Hangar::Buy
Hangar::Sell

Button action handlers
==============
*/
void Hangar::Launch(int id)
{
    EndModal(HANGAR_GAME);
}

void Hangar::SavePlayer(int id)
{
    char buf[256];

    G_SavePlayer(&game.player);

    snprintf(buf, sizeof(buf), "Player %s - %s saved!", game.player.name,
            game.player.callsign);
    RttsMessageBox::Ok(this, buf);
}

void Hangar::ExitHangar(int id)
{
    if (!RttsMessageBox::YesNo(this, "Do you really want to leave this game?"))
        return;

    G_EndGame();
    EndModal(HANGAR_MAINMENU);
}

void Hangar::LeftBtn(int id)
{
    m_iBuyItem--;
    if (m_iBuyItem < 0)
        m_iBuyItem = gs_buyitem_num-1;
    Refresh();
}

void Hangar::RightBtn(int id)
{
    m_iBuyItem++;
    if (m_iBuyItem >= gs_buyitem_num)
        m_iBuyItem = 0;
    Refresh();
}

void Hangar::Buy(int id)
{
    buyitem_t *bi;
    bool bCanBuy, bCanSell;

    bi = &gs_buyitems[m_iBuyItem];

    bi->Inventory(&game.player, bi->id, &bCanBuy, &bCanSell);
    if (!bCanBuy || game.player.money < bi->cash_buy)
        return; // can't buy

    game.player.money -= bi->cash_buy;
    bi->Action(&game.player, bi->id, true);

    Refresh();
}

void Hangar::Sell(int id)
{
    buyitem_t *bi;
    bool bCanBuy, bCanSell;

    bi = &gs_buyitems[m_iBuyItem];

    bi->Inventory(&game.player, bi->id, &bCanBuy, &bCanSell);
    if (!bCanSell)
        return; // can't buy

    game.player.money += bi->cash_sell;
    bi->Action(&game.player, bi->id, false);

    Refresh();
}

/*
==============
Hangar::Draw

Overridden to draw the buyitem sprite
==============
*/
void Hangar::Draw()
{
    buyitem_t *bi;

    GLabel::Draw();

    bi = &gs_buyitems[m_iBuyItem];
    SPR_Draw(bi->sprite, 290, 260, -1, hal->framestart);
}

/*
==============
Hangar::Key

Escape breaks to the main menu
==============
*/
bool Hangar::Key(int code, char c, bool down)
{
    if (!down)
        return false;

    switch(code) {
    case KEY_LEFT:
        LeftBtn(-1);
        break;

    case KEY_RIGHT:
        RightBtn(-1);
        break;

    case KEY_ESCAPE:
        EndModal(HANGAR_MAINMENU);
        break;

#ifdef DEBUG
    // Cheats
    case KEY_F5:
        game.player.money += 100000;
        Refresh();
        break;
    case KEY_F6:
        game.player.money -= 100000;
        if (game.player.money < 0)
            game.player.money = 0;
        Refresh();
        break;
    case KEY_F7:
        game.player.level++;
        Refresh();
        break;
    case KEY_F8:
        game.player.level--;
        if (game.player.level < 0)
            game.player.level = 0;
        Refresh();
        break;
#endif

    default:
        return GLabel::Key(code, c, down);
    }

    return true;
}

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

COMMON CODE

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

/*
==============
G_NewPlayer
==============
*/
void G_NewPlayer(player_t *player, const char *name, const char *callsign)
{
    xstrcpy(player->name, sizeof(player->name), name ? name : "<error>");
    xstrcpy(player->callsign, sizeof(player->callsign), callsign ? callsign : "<error>");
    player->level = 0;
    player->money = 10000;
    player->health = 75;
    memset(player->weapons, 0, sizeof(player->weapons));
}

/*
==============
G_NewGame

Starts a completely new game, with a new player and everything
==============
*/
void G_NewGame(const char *playername, const char *callsign)
{
    if (game.running)
        G_EndGame();

    game.running = true;

    G_NewPlayer(&game.player, playername, callsign);
}

/*
==============
G_SavePlayer

Saves the current game
==============
*/
void G_SavePlayer(const player_t *plr)
{
    char path[MAX_OSPATH];
    LFileWrite fw;
    int i;

    lassert(game.running);

    fw.Integer(SAV_MAGIC);
    fw.Integer(SAV_VERSION);
    fw.Byte(255);

    fw.CString(plr->name);
    fw.CString(plr->callsign);
    fw.Integer(plr->level);
    fw.Integer(plr->money);
    fw.Byte(plr->health);

    fw.Byte(NUM_WEAP);
    for(i = 0; i < NUM_WEAP; i++)
        fw.Byte(plr->weapons[i]);

    snprintf(path, sizeof(path), "%s.%s.sav", plr->name, plr->callsign);
    fw.Write(path);
}

/*
==============
G_LoadPlayer

Load a game (or rather, player). Returns false if the savegame is
corrupt.
==============
*/
bool G_LoadPlayer(player_t *plr, const char *filename)
{
    try {
        LFileRead fr;
        int i, num;

        fr.Open(filename);

        if (fr.Integer() != SAV_MAGIC)
            return false;
        if (fr.Integer() != SAV_VERSION)
            return false;

        if (fr.Byte() != 255)
            return false;

        xstrcpy(plr->name, sizeof(plr->name), fr.CString());
        xstrcpy(plr->callsign, sizeof(plr->callsign), fr.CString());
        plr->level = fr.Integer();
        plr->money = fr.Integer();
        plr->health = fr.Byte();

        memset(plr->weapons, 0, sizeof(plr->weapons));
        num = fr.Byte();
        for(i = 0; i < num; i++)
            plr->weapons[i] = fr.Byte();

        return true;
    }
#ifndef DEBUG
    catch(LError &) {
#else
    catch(LError &err) {
        L_Printf("Error reading savegame %s: %s\n", filename, err.Get());
#endif
        return false;
    }
}

/*
==============
G_EndGame

Ends the current game. Player data is not saved.
==============
*/
void G_EndGame()
{
    game.running = false;
}

/*
==============
G_Init
==============
*/
void G_Init()
{
    game.running = false;
}

/*
==============
G_Shutdown
==============
*/
void G_Shutdown()
{
    if (game.running)
        G_EndGame();
}