500 lines
18 KiB
C++
500 lines
18 KiB
C++
/*
|
|
* Copyright (C) 2002,2003,2004,2005,2006 Daniel Heck
|
|
*
|
|
* This program 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 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "gui/LevelMenu.hh"
|
|
|
|
#include "gui/HelpMenu.hh"
|
|
#include "gui/LevelPackMenu.hh"
|
|
|
|
#include "ecl.hh"
|
|
#include "game.hh"
|
|
#include "main.hh"
|
|
#include "nls.hh"
|
|
#include "options.hh"
|
|
#include "server.hh"
|
|
#include "sound.hh"
|
|
#include "StateManager.hh"
|
|
#include "video.hh"
|
|
#include "lev/Index.hh"
|
|
|
|
using namespace std;
|
|
using namespace ecl;
|
|
|
|
|
|
namespace enigma { namespace gui {
|
|
/* -------------------- Level Menu -------------------- */
|
|
|
|
struct LevelMenuConfig {
|
|
int buttonw, ibuttonw, buttonh;
|
|
int lbuttonw, lbuttonh;
|
|
ecl::Rect previewarea;
|
|
int thumbsy; // y coordinate of thumbnail window
|
|
int leftborder;
|
|
|
|
LevelMenuConfig (const ecl::Rect &screen)
|
|
: buttonw (140), ibuttonw (90), buttonh (35),
|
|
lbuttonw (140), lbuttonh (100),
|
|
previewarea (10, 60, screen.w-50, screen.h-130),
|
|
thumbsy (60),
|
|
leftborder (10)
|
|
{}
|
|
};
|
|
|
|
LevelMenu::LevelMenu()
|
|
: but_advancemode (new AdvanceModeButton),
|
|
but_next (new ImageButton("ic-next", "ic-next1", this)),
|
|
but_back (new StaticTextButton(N_("Main Menu"), this)),
|
|
but_difficulty (new DifficultyButton),
|
|
but_levelpack (new StaticTextButton(N_("Level Pack"), this)),
|
|
lbl_lpinfo (new Label("")),
|
|
lbl_statistics (new Label("")),
|
|
lbl_levelname (new Label("", HALIGN_LEFT)),
|
|
lbl_levelinfo (new Label("", HALIGN_LEFT)),
|
|
shown_text_ttl(-1.0), main_quit (false)
|
|
{
|
|
HList *hl, *hll, *hlr ;
|
|
|
|
const video::VMInfo &vminfo = *video::GetInfo();
|
|
|
|
|
|
// Levelmenu configuration
|
|
const int Y2 = 10; // y position for information area
|
|
const int Y3 = vminfo.height-50; // y position for bottom button row
|
|
LevelMenuConfig c (Rect (0, 0, vminfo.width, vminfo.height));
|
|
|
|
but_difficulty->set_listener (this);
|
|
|
|
|
|
// Create buttons
|
|
hll = new HList;
|
|
hll->set_spacing (10);
|
|
hll->set_alignment (HALIGN_CENTER, VALIGN_TOP);
|
|
hll->set_default_size (c.ibuttonw, c.buttonh);
|
|
hll->add_back (but_advancemode);
|
|
hll->add_back (but_next);
|
|
hll->add_back (but_difficulty);
|
|
|
|
hlr = new HList;
|
|
hlr->set_spacing (10);
|
|
hlr->set_alignment (HALIGN_CENTER, VALIGN_TOP);
|
|
hlr->set_default_size (c.buttonw, c.buttonh);
|
|
hlr->add_back (but_levelpack);
|
|
hlr->add_back (but_back);
|
|
|
|
hl = new HList;
|
|
hl->set_spacing (10);
|
|
hl->set_alignment (HALIGN_CENTER, VALIGN_TOP);
|
|
hl->set_default_size (2*c.buttonw + 10, c.buttonh);
|
|
hl->add_back (hll);
|
|
hl->add_back (hlr);
|
|
this->add (hl, Rect(c.leftborder, Y3, vminfo.width-20, c.buttonh));
|
|
|
|
// Add navigation buttons
|
|
pgup = new ImageButton("ic-up", "ic-up1", this);
|
|
pgdown = new ImageButton("ic-down", "ic-down1", this);
|
|
start = new ImageButton("ic-top", "ic-top1", this);
|
|
end = new ImageButton("ic-bottom", "ic-bottom1", this);
|
|
|
|
Rect r(vminfo.width-30, c.thumbsy, 20, 50);
|
|
r.y = c.thumbsy;
|
|
add (pgup, r);
|
|
r.y += 60;
|
|
add (pgdown, r);
|
|
r.y = c.thumbsy + 240;
|
|
add (start, r);
|
|
r.y += 60;
|
|
add (end, r);
|
|
|
|
// Information area
|
|
hl = new HList;
|
|
hl->add_back (lbl_levelname, List::EXPAND);
|
|
hl->add_back (lbl_lpinfo, List::TIGHT);
|
|
this->add (hl, Rect (5, Y2, vminfo.width - 10, 28));
|
|
|
|
hl_info_stat = new HList;
|
|
hl_info_stat->add_back (lbl_levelinfo, List::EXPAND); //Rect (c.leftborder, Y2+20,305, 28));
|
|
hl_info_stat->add_back (lbl_statistics, List::TIGHT);
|
|
this->add (hl_info_stat, Rect (5, Y2+20, vminfo.width - 10, 28));
|
|
|
|
// Prepare level selection widget
|
|
levelwidget = new LevelWidget();
|
|
levelwidget->set_listener(this);
|
|
levelwidget->realize (c.previewarea);
|
|
levelwidget->set_area (c.previewarea);
|
|
|
|
this->add (levelwidget);
|
|
|
|
updateIndex();
|
|
}
|
|
|
|
void LevelMenu::tick(double dtime)
|
|
{
|
|
levelwidget->tick(dtime);
|
|
static double timeaccu = 0.0;
|
|
|
|
// info texts disappear after some time
|
|
if (shown_text_ttl>0.0) {
|
|
shown_text_ttl -= dtime;
|
|
if (shown_text_ttl <= 0.0)
|
|
shown_text = "";
|
|
}
|
|
timeaccu += dtime;
|
|
if (timeaccu > 0.1) {
|
|
update_info();
|
|
timeaccu = 0.0;
|
|
}
|
|
|
|
}
|
|
|
|
static const char *helptext_levelmenu[] = {
|
|
N_("Escape:"), N_("Skip to main menu"),
|
|
"F1:", N_("Show this help"),
|
|
"F5:", 0, // see below
|
|
N_("Arrows:"), N_("Select level"),
|
|
N_("Return:"), N_("Play selected level"),
|
|
N_("Back/Space:"), N_("Previous/next levelpack"),
|
|
"u", N_("Mark current level as Unsolved"),
|
|
// "s", N_("Mark current level as Solved"),
|
|
N_("Alt+Return:"), N_("Switch between fullscreen and window"),
|
|
N_("Left click:"), N_("Play selected level"),
|
|
N_("Right or control click:"), N_("Inspect selected level"),
|
|
0
|
|
};
|
|
|
|
bool LevelMenu::on_event (const SDL_Event &e)
|
|
{
|
|
// Pass all events to the level widget first
|
|
bool handled=levelwidget->on_event(e);
|
|
|
|
if (!handled) {
|
|
if (e.type == SDL_KEYDOWN) {
|
|
handled=true;
|
|
switch (e.key.keysym.sym) {
|
|
case SDLK_SPACE: next_levelpack(); break;
|
|
case SDLK_BACKSPACE: previous_levelpack(); break;
|
|
case SDLK_F1:
|
|
if (app.state->getInt("NextLevelMode") == lev::NEXT_LEVEL_NOT_BEST)
|
|
helptext_levelmenu[5] = N_("Select next level for world record hunt");
|
|
else
|
|
helptext_levelmenu[5] = N_("Select next unsolved level");
|
|
|
|
displayHelp(helptext_levelmenu, 200);
|
|
draw_all();
|
|
break;
|
|
case SDLK_F5:
|
|
next_unsolved();
|
|
break;
|
|
case SDLK_u: {
|
|
lev::ScoreManager::instance()->markUnsolved(lev::Index::getCurrentProxy(),
|
|
app.state->getInt("Difficulty"));
|
|
invalidate_all();
|
|
break;
|
|
}
|
|
case SDLK_s:
|
|
lev::ScoreManager::instance()->markSolved(lev::Index::getCurrentProxy(),
|
|
app.state->getInt("Difficulty"));
|
|
invalidate_all();
|
|
break;
|
|
default: handled=false; break;
|
|
}
|
|
}
|
|
else
|
|
handled = Menu::on_event (e);
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
void LevelMenu::on_action(Widget *w)
|
|
{
|
|
if (w==levelwidget) {
|
|
lev::Index *ind = lev::Index::getCurrentIndex();
|
|
int ilevel = ind->getCurrentPosition();
|
|
if (w->lastModifierKeys() & KMOD_CTRL && w->lastModifierKeys() & KMOD_SHIFT) {
|
|
// force a reload from file
|
|
lev::Proxy * curProxy = lev::Proxy::loadedLevel();
|
|
if (curProxy != NULL)
|
|
curProxy->release();
|
|
}
|
|
|
|
if ((unsigned)ilevel < ind->size()) {
|
|
if (ind->mayPlayLevel(ilevel+1)) {
|
|
game::StartGame();
|
|
ilevel = ind->getCurrentPosition();
|
|
invalidate_all();
|
|
ind->setCurrentPosition(ilevel);
|
|
levelwidget->syncFromIndexMgr();
|
|
}
|
|
else
|
|
show_text(_("You are not allowed to play this level yet."));
|
|
}
|
|
} else if (w == but_back) {
|
|
main_quit = true;
|
|
Menu::quit();
|
|
} else if (w == pgup) {
|
|
levelwidget->page_up();
|
|
} else if (w == pgdown) {
|
|
levelwidget->page_down();
|
|
} else if (w == start) {
|
|
levelwidget->start();
|
|
} else if (w == end) {
|
|
levelwidget->end();
|
|
} else if (w == but_next) {
|
|
next_unsolved();
|
|
} else if (w == but_levelpack) {
|
|
main_quit = false;
|
|
Menu::quit();
|
|
} else if (w == but_difficulty) {
|
|
but_difficulty->on_action(w);
|
|
invalidate_all();
|
|
}
|
|
}
|
|
|
|
void LevelMenu::update_info() {
|
|
// Note: all format strings have to be translated directly
|
|
// as the formatted strings can no longer be translated.
|
|
// The instant language change is guaranteed by the frequent
|
|
// call of is method!
|
|
|
|
lev::Index *ind = lev::Index::getCurrentIndex();
|
|
int size = ind->size();
|
|
lev::ScoreManager *scm = lev::ScoreManager::instance();
|
|
lev::Proxy *curProxy = ind->getCurrent();
|
|
int difficulty = app.state->getInt("Difficulty");
|
|
|
|
lbl_lpinfo->set_text(ecl::strf(_("%s: %d levels"),
|
|
ind->getName().c_str(), size));
|
|
|
|
if (size == 0) {
|
|
// empty level pack
|
|
lbl_statistics->set_text ("-");
|
|
lbl_levelname->set_text ("-");
|
|
lbl_levelinfo->set_text ("-");
|
|
}
|
|
else {
|
|
int iselected = ind->getCurrentPosition();
|
|
|
|
// Display levelpack statistics (percentage of solved levels)
|
|
|
|
if (app.state->getInt("NextLevelMode") == lev::NEXT_LEVEL_NOT_BEST) {
|
|
int pct = 100* scm->countBestScore(ind, difficulty)/ size;
|
|
lbl_statistics->set_text(ecl::strf(_("%d%% best"), pct));
|
|
}
|
|
else if (app.state->getInt("NextLevelMode") == lev::NEXT_LEVEL_OVER_PAR) {
|
|
int pct = 100* scm->countParScore(ind, difficulty)/ size;
|
|
double hcp = scm->calcHCP(ind, difficulty);
|
|
lbl_statistics->set_text(ecl::strf(_("%d%% par, hcp %.1f"), pct, hcp));
|
|
}
|
|
else {
|
|
int pct = 100* scm->countSolved(ind, difficulty) / size;
|
|
lbl_statistics->set_text(ecl::strf(_("%d%% solved"), pct));
|
|
}
|
|
|
|
// Display level name
|
|
if (enigma::WizardMode) {
|
|
// add level path info - we just can display the normalized path
|
|
// as we did not yet locate the absolute path - the user can
|
|
// use the inspector to check the absolute path!
|
|
lbl_levelname->set_text(ecl::strf("#%d: %s (%s)",
|
|
ind->getCurrentLevel(), curProxy->getTitle().c_str(),
|
|
curProxy->getNormLevelPath().c_str()));
|
|
} else {
|
|
lbl_levelname->set_text(ecl::strf("#%d: %s",
|
|
ind->getCurrentLevel(), curProxy->getTitle().c_str()));
|
|
}
|
|
|
|
// Display best time
|
|
if (shown_text.length()) {
|
|
lbl_levelinfo->set_text(shown_text);
|
|
}
|
|
else {
|
|
// TODO prepare for scores that are not time based!
|
|
char txt[200];
|
|
lev::RatingManager *ratingMgr = lev::RatingManager::instance();
|
|
int wr_time = ratingMgr->getBestScore(curProxy, difficulty);
|
|
int par_time = ratingMgr->getParScore(curProxy, difficulty);
|
|
bool is_par = scm->parScoreReached(curProxy, difficulty);
|
|
int best_user_time = scm->getBestUserScore(curProxy, difficulty);
|
|
string wr_name = ratingMgr->getBestScoreHolder(curProxy, difficulty);
|
|
bool wr_name_displayed = false;
|
|
|
|
string your_time;
|
|
string wr_text;
|
|
|
|
if (best_user_time>0) {
|
|
your_time = strf(_("Your time: %d:%02d"),
|
|
best_user_time/60, best_user_time%60);
|
|
|
|
if (wr_time>0) {
|
|
int below = wr_time - best_user_time;
|
|
if (below == 0)
|
|
wr_text = _("That's world record.");
|
|
else if (below>0)
|
|
wr_text = strf(_("That's %d:%02d below world record."),
|
|
below/60, below%60);
|
|
}
|
|
}
|
|
|
|
if (wr_text.length() == 0 && wr_time>0) {
|
|
if (wr_name.length()) {
|
|
wr_name_displayed = true;
|
|
} else
|
|
if (is_par || par_time < 0)
|
|
wr_text = strf(_("World record: %d:%02d"), wr_time/60, wr_time%60);
|
|
else
|
|
wr_text = strf(_("Par: %d:%02d World record: %d:%02d"),
|
|
par_time/60, par_time%60, wr_time/60, wr_time%60);
|
|
}
|
|
|
|
if (!your_time.empty())
|
|
your_time += " ";
|
|
|
|
int wr_cut = 0;
|
|
do {
|
|
if (wr_name_displayed) {
|
|
std::string tmp = ratingMgr->getBestScoreHolder(curProxy, difficulty, wr_cut++);
|
|
if (!tmp.empty())
|
|
wr_name = tmp;
|
|
if (is_par || par_time < 0)
|
|
wr_text = strf(_("World record by %s: %d:%02d"),
|
|
wr_name.c_str(), wr_time/60, wr_time%60);
|
|
else
|
|
wr_text = strf(_("Par: %d:%02d World record by %s: %d:%02d"),
|
|
par_time/60, par_time%60, wr_name.c_str(), wr_time/60, wr_time%60);
|
|
}
|
|
lbl_levelinfo->set_text(your_time + wr_text);
|
|
} while (!hl_info_stat->fits() && wr_name_displayed && (wr_cut < 20));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LevelMenu::updateIndex()
|
|
{
|
|
levelwidget->syncFromIndexMgr();
|
|
|
|
update_info();
|
|
}
|
|
|
|
void LevelMenu::draw_background(ecl::GC &gc)
|
|
{
|
|
video::SetCaption(("Enigma - Level Menu"));
|
|
sound::PlayMusic (options::GetString("MenuMusicFile"));
|
|
|
|
blit(gc, 0,0, enigma::GetImage("menu_bg", ".jpg"));
|
|
}
|
|
|
|
void LevelMenu::next_unsolved()
|
|
{
|
|
lev::Index *ind = lev::Index::getCurrentIndex();
|
|
if (ind->advanceLevel(lev::ADVANCE_NEXT_MODE)) {
|
|
levelwidget->syncFromIndexMgr();
|
|
} else
|
|
show_text(_("No further unsolved level available!"));
|
|
}
|
|
|
|
void LevelMenu::next_levelpack()
|
|
{
|
|
lev::Index::setCurrentIndex(lev::Index::nextGroupIndex()->getName());
|
|
updateIndex();
|
|
}
|
|
|
|
void LevelMenu::previous_levelpack() {
|
|
lev::Index::setCurrentIndex(lev::Index::previousGroupIndex()->getName());
|
|
updateIndex();
|
|
}
|
|
|
|
void LevelMenu::show_text(const string& text) {
|
|
shown_text = text;
|
|
shown_text_ttl = 2.0; // show for two seconds
|
|
}
|
|
|
|
bool LevelMenu::isMainQuit() {
|
|
return main_quit;
|
|
}
|
|
|
|
/* -------------------- DifficultyButton -------------------- */
|
|
|
|
DifficultyButton::DifficultyButton() : ImageButton("ic-easymode","ic-easymode",this) {
|
|
update();
|
|
}
|
|
|
|
void DifficultyButton::update() {
|
|
if (app.state->getInt("Difficulty") == DIFFICULTY_EASY)
|
|
ImageButton::set_images("ic-easymode","ic-normalmode");
|
|
else
|
|
ImageButton::set_images("ic-normalmode","ic-easymode");
|
|
}
|
|
|
|
void DifficultyButton::on_action(Widget *)
|
|
{
|
|
int newdifficulty = (DIFFICULTY_EASY+DIFFICULTY_HARD) - app.state->getInt("Difficulty");
|
|
app.state->setProperty("Difficulty", newdifficulty); update();
|
|
invalidate();
|
|
}
|
|
|
|
void DifficultyButton::draw(ecl::GC &gc, const ecl::Rect &r) {
|
|
update();
|
|
ImageButton::draw(gc, r);
|
|
}
|
|
|
|
/* -------------------- AdvanceModeButton -------------------- */
|
|
|
|
AdvanceModeButton::AdvanceModeButton() : ImageButton("","",this) {
|
|
update();
|
|
}
|
|
|
|
void AdvanceModeButton::update() {
|
|
switch (app.state->getInt("NextLevelMode")) {
|
|
case lev::NEXT_LEVEL_UNSOLVED :
|
|
ImageButton::set_images("ic-unsolved", "par");
|
|
break;
|
|
case lev::NEXT_LEVEL_OVER_PAR :
|
|
ImageButton::set_images("par", "ic-worldrecord");
|
|
break;
|
|
case lev::NEXT_LEVEL_NOT_BEST :
|
|
ImageButton::set_images("ic-worldrecord", "ic-strictlynext");
|
|
break;
|
|
case lev::NEXT_LEVEL_STRICTLY : // use as default, too
|
|
default:
|
|
ImageButton::set_images("ic-strictlynext","ic-unsolved");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AdvanceModeButton::on_action(Widget *)
|
|
{
|
|
switch (app.state->getInt("NextLevelMode")) {
|
|
case lev::NEXT_LEVEL_STRICTLY :
|
|
app.state->setProperty("NextLevelMode", lev::NEXT_LEVEL_UNSOLVED);
|
|
break;
|
|
case lev::NEXT_LEVEL_UNSOLVED :
|
|
app.state->setProperty("NextLevelMode", lev::NEXT_LEVEL_OVER_PAR);
|
|
break;
|
|
case lev::NEXT_LEVEL_OVER_PAR :
|
|
app.state->setProperty("NextLevelMode", lev::NEXT_LEVEL_NOT_BEST);
|
|
break;
|
|
case lev::NEXT_LEVEL_NOT_BEST :
|
|
default:
|
|
app.state->setProperty("NextLevelMode", lev::NEXT_LEVEL_STRICTLY);
|
|
}
|
|
update();
|
|
invalidate();
|
|
}
|
|
|
|
}} // namespace enigma::gui
|