// Hyperbolic Rogue -- menus // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file menus.cpp * \brief generic menus (start menu, main menu, Overview, etc.) */ // -- overview -- #include "hyper.h" #define BLACKISH 0x404040 #define REDDISH 0x400000 namespace hr { EX ld whatever[16]; EX int whateveri[16]; int PREC(ld x) { ld sh = shiftmul; if(sh > -.2 && sh < .2) x = 10.01; return int(shiftmul * x); } EX void showOverview() { cmode = sm::ZOOMABLE | sm::OVERVIEW; DEBBI(DF_GRAPH, ("show overview")); if(dialog::infix != "") mouseovers = dialog::infix; else { mouseovers = XLAT("world overview"); mouseovers += " "; mouseovers += XLAT(" kills: %1/%2", its(tkills()), its(killtypes())); mouseovers += XLAT(" $$$: %1", its(gold())); if(randomPatternsMode) ; else if(landUnlocked(laHell)) { int i1, i2; countHyperstoneQuest(i1, i2); mouseovers += XLAT(" Hyperstone: %1/%2", its(i1), its(i2)); } else mouseovers += XLAT(" Hell: %1/%2", its(orbsUnlocked()), its(lands_for_hell())); } bool pages = false; { dynamicval ds(dual::state, dual::state ? 2 : 0); generateLandList(isLandIngame); } bool not_in_game = false; if(dialog::infix != "") { auto land_matches = [] (eLand l) { string s = dnameof(l); s += "@"; s += dnameof(treasureType(l)); s += "@"; s += dnameof(nativeOrbType(l)); return dialog::hasInfix(s); }; vector filtered; for(eLand l: landlist) if(land_matches(l)) filtered.push_back(l); if(filtered.size()) landlist = filtered; else { for(int i=0; i 30) { pages = true; lstart += dialog::handlePage(nl, nlm, (nl+1)/2); } else nlm = nl; int vf = min((vid.yres-64-vid.fsize*2) / nlm + (not_in_game ? 1 : 0), vid.xres/40); eLand curland = getLandForList(cwt.at); getcstat = '0'; if(not_in_game) { int i0 = 56 + vid.fsize + nl * vf; displayfrZ(64, i0, 1, vf-4, "(these lands are not in game)", forecolor, 0); } for(int i=0; i= 25) col = 0xFFD500; else if(lv && it == itSavedPrincess) col = 0xFFD500; else if(lv >= 10) col = 0x00D500; else if(items[it]) col = 0xC0C0C0; else col = BLACKISH; int c8 = (vf+2)/3; if(displayfrZH(xr*24-c8*6, i0, 1, vf-4, (required_for_hyperstones(it) ? "" : "*") + its(items[it]), col, 16)) getcstat = 2000+it; if(!cheater) if(displayfrZH(xr*24, i0, 1, vf-4, its(hiitems[modecode()][it]), col, 16)) getcstat = 2000+it; if(items[it]) col = iinf[it].color; else col = BLACKISH; if(displayfrZH(xr*24+c8*4, i0, 1, vf-4, s0 + iinf[it].glyph, col, 16)) getcstat = 2000+it; if(displayfrZH(xr*24+c8*5, i0, 1, vf-4, XLAT1(iinf[it].name), col, 0)) getcstat = 2000+it; eItem io = nativeOrbType(l); if(io == itShard) { if(items[it] >= 10) col = winf[waMirror].color; else col = BLACKISH; if(displayfrZH(xr*46, i0, 1, vf-4, XLAT1(winf[waMirror].name), col, 0)) getcstat = 3000+waMirror; if(getcstat == 3000+waMirror) { string olrdesc = olrDescriptions[getOLR(io, cwt.at->land)]; mouseovers = XLAT(olrdesc, cwt.at->land, it, treasureTypeUnlock(curland, io)); } } else if(io) { if(lv >= 25) col = 0xFFD500; else if(lv >= 10) col = 0xC0C0C0; else col = BLACKISH; if(displayfrZH(xr*46-c8*4, i0, 1, vf-4, its(items[io]), col, 16)) getcstat = 2000+io; if(lv >= 10) col = iinf[io].color; else col = BLACKISH; if(displayfrZH(xr*46-c8, i0, 1, vf-4, s0 + iinf[io].glyph, col, 16)) getcstat = 2000+io; if(displayfrZH(xr*46, i0, 1, vf-4, XLAT1(iinf[io].name), col, 0)) getcstat = 2000+io; if(getcstat == 2000+io) { string olrdesc = olrDescriptions[getOLR(io, curland)]; mouseovers = XLAT(olrdesc, curland, it, treasureTypeUnlock(curland, io)); } } } dialog::displayPageButtons(3, pages); keyhandler = [] (int sym, int uni) { int umod = uni % 1000; int udiv = uni / 1000; if(udiv == 1 && umod < landtypes) { const eLand l = eLand(umod); gotoHelp(""); gotoHelpFor(l); if(cheater) { help_extension hex; hex.key = 't'; hex.text = XLAT("teleport"); hex.action = [l] () { cheater++; bool princ = (l == laPrincessQuest); if(princ) { if(kills[moVizier] == 0) kills[moVizier] = 1; princess::forceMouse = true; princess::gotoPrincess = true; cheatMoveTo(laPalace); } else cheatMoveTo(l); canmove = true; if(princ) fullcenter(); popScreen(); popScreen(); }; help_extensions.push_back(hex); } } else if(udiv == 2 && umod < ittypes) { gotoHelp(generateHelpForItem(eItem(umod))); if(cheater) { dialog::helpToEdit(items[umod], 0, 200, 10, 10); dialog::get_ne().reaction = [] () { if(hardcore) canmove = true; else checkmove(); cheater++; }; dialog::bound_low(0); } } else if(udiv == 3 && umod < walltypes) gotoHelp(generateHelpForWall(eWall(umod))); else if(uni == SDLK_F1) gotoHelp( "This displays all lands available in the game. " "Bonus lands (available only in separate challenges) " "are not included. Lands written in dark have to be " "unlocked, and lands in dark red are unavailable " "because of using special options. Click on any " "land or item to get information about it. Hover over " "an Orb to know its relation to the current land. " "Cheaters can click to move between lands, and use the " "mousewheel to gain or lose treasures and orbs quickly (Ctrl = precise, Shift = reverse)." ); else if(dialog::handlePageButtons(uni)) ; else if(dialog::editInfix(uni)) dialog::list_skip = 0; else if(doexiton(sym, uni)) popScreen(); }; } // -- display modes -- EX void editScale() { dialog::editNumber(vpconf.scale, .001, 1000, .1, 1, XLAT("scale factor"), XLAT("Scale the displayed model.")); dialog::scaleSinh(); } EX const char *wdmodes[7] = {"ASCII", "black", "plain", "Escher", "plain/3D", "Escher/3D", "ASCII/3D"}; EX const char *mdmodes[6] = {"ASCII", "items only", "items and monsters", "3D", "?", "?"}; EX const char *hlmodes[3] = {"press Alt", "highlight", "super-highlight"}; EX void showGraphQuickKeys() { cmode = sm::SIDE | sm::MAYDARK; gamescreen(); dialog::init(XLAT("quick options")); dialog::addItem("quick projection", '1'); dialog::addSelItem(XLAT("wall display mode"), XLAT(wdmodes[vid.wallmode]), '5'); dialog::addSelItem(XLAT("monster display mode"), XLAT(mdmodes[vid.monmode]), '8'); dialog::addSelItem(XLAT("highlight stuff"), XLAT(hlmodes[vid.highlightmode]), 508); dialog::addBoolItem(XLAT("draw the grid"), (vid.grid), '6'); dialog::addBoolItem(XLAT("mark heptagons"), (vid.darkhepta), '7'); dialog::addBreak(50); dialog::addInfo(XLAT("Hint: these keys usually work during the game")); dialog::addInfo(XLAT("also hold Alt during the game to toggle high contrast")); dialog::addBreak(50); dialog::addBack(); dialog::display(); keyhandler = [] (int sym, int uni) { dialog::handleNavigation(sym, uni); if(gmodekeys(sym, uni)) ; else if(doexiton(sym, uni)) popScreen(); }; } EX void enable_cheat() { if(tactic::on && gold()) { addMessage(XLAT("Not available in the pure tactics mode!")); } else if(daily::on) { addMessage(XLAT("Not available in the daily challenge!")); } else if(!cheater) dialog::cheat_if_confirmed([] { cheater++; addMessage(XLAT("You activate your demonic powers!")); #if !ISMOBILE addMessage(XLAT("Shift+F, Shift+O, Shift+T, Shift+L, Shift+U, etc.")); #endif popScreen(); }); else { popScreen(); specialland = firstland = princess::challenge ? laPalace : laIce; restart_game(); } } // -- game modes -- EX void switchHardcore() { if(hardcore && !canmove) { restart_game(); hardcore = false; } else if(hardcore && canmove) { hardcore = false; } else { hardcore = true; canmove = true; hardcoreAt = turncount; } if(hardcore) addMessage(XLAT("One wrong move and it is game over!")); else addMessage(XLAT("Not so hardcore?")); if(pureHardcore()) popScreenAll(); } EX void switch_casual() { if(savecount > 0) { dialog::push_confirm_dialog([] { restart_game(); casual = !casual; }, XLAT("Switching casual allowed only before saving the game. Do you want to restart?")); return; } else casual = !casual; if(casual) { addMessage(XLAT("You are in the casual mode! Achievements are disabled.")); addMessage(XLAT("Collect an Orb of Safety to save a checkpoint.")); } popScreenAll(); } EX void showCreative() { cmode = sm::SIDE | sm::MAYDARK; gamescreen(); dialog::init(XLAT("creative mode")); #if CAP_EDIT dialog::addItem(XLAT("map editor"), 'm'); dialog::add_action([] { dialog::cheat_if_confirmed([] { cheater++; pushScreen(mapeditor::showMapEditor); lastexplore = turncount; addMessage(XLAT("You activate your terraforming powers!")); }); }); #endif #if CAP_EDIT dialog::addItem(XLAT("shape editor"), 'g'); dialog::add_action([] { mapeditor::drawing_tool = false; mapeditor::intexture = false; pushScreen(mapeditor::showDrawEditor); mapeditor::initdraw(cwt.at); }); #endif #if CAP_EDIT dialog::addItem(XLAT("drawing tool"), 'd'); dialog::add_action([] { dialog::cheat_if_confirmed([] { mapeditor::drawing_tool = true; mapeditor::intexture = false; pushScreen(mapeditor::showDrawEditor); mapeditor::initdraw(cwt.at); }); }); #endif // display modes #if CAP_MODEL if(GDIM == 2) { dialog::addItem(XLAT("paper model creator"), 'n'); dialog::add_action([] { netgen::run(); }); } #endif #if CAP_SHOT dialog::addItem(XLAT("screenshots"), 's'); dialog::add_action_push(shot::menu); #endif #if CAP_ANIMATIONS dialog::addBoolItem(XLAT("animations"), anims::any_on(), 'a'); dialog::add_action_push(anims::show); #endif dialog::addBoolItem(XLAT("history mode"), history::on || history::includeHistory, 'h'); dialog::add_action_push(history::history_menu); #if CAP_TEXTURE if(GDIM == 2) { dialog::addBoolItem(XLAT("texture mode"), texture::config.tstate == texture::tsActive, 't'); dialog::add_action_push(texture::showMenu); } #endif // dialog::addBoolItem(XLAT("expansion"), viewdists, 'x'); dialog::addBreak(50); dialog::addBack(); dialog::display(); } EX void show_achievement_eligibility() { #if CAP_ACHIEVE dialog::addBreak(100); dialog::addInfo(XLAT("achievement/leaderboard eligiblity:"), 0xFF8000); if(!wrongMode(0)) { if(inv::on || bow::crossbow_mode()) dialog::addInfo(XLAT("eligible for most -- leaderboards separate"), 0x80FF00); else dialog::addInfo(XLAT("eligible for most"), 0x00FF00); } else if(!wrongMode(rg::racing)) dialog::addInfo(XLAT("eligible for racing"), 0xFFFF00); else if(!wrongMode(rg::shmup)) dialog::addInfo(XLAT("eligible for shmup"), 0xFFFF00); else if(!wrongMode(rg::multi)) dialog::addInfo(XLAT("eligible for multiplayer"), 0xFFFF00); else if(!wrongMode(rg::chaos)) dialog::addInfo(XLAT("eligible for Chaos mode"), 0xFFFF00); else if(!wrongMode(rg::princess)) dialog::addInfo(XLAT("eligible for Princess Challenge"), 0xFFFF00); else if(!wrongMode(specgeom_heptagonal())) dialog::addInfo(XLAT("eligible for heptagonal"), 0xFFFF00); else if(!wrongMode(any_specgeom())) /* the player probably knows what they are aiming at */ dialog::addInfo(XLAT("eligible for special geometry"), 0xFFFF00); #if CAP_DAILY else if(daily::on && !daily::historical) dialog::addInfo(XLAT("eligible for Strange Challenge"), 0xFFFF00); #endif else if(cheater) dialog::addInfo(XLAT("disabled in cheat mode"), 0xC00000); else if(casual) dialog::addInfo(XLAT("disabled in casual mode"), 0xC00000); else if(ineligible_starting_land) dialog::addInfo(XLAT("this starting land is not eligible for achievements"), 0xC00000); else dialog::addInfo(XLAT("not eligible due to current mode settings"), 0XC00000); #else dialog::addInfo(XLAT("no achievements/leaderboards in this version"), 0XFF8000); #endif } EX void show_chaos() { cmode = sm::SIDE | sm::MAYDARK; gamescreen(); dialog::init(XLAT("land structure")); chaosUnlocked = chaosUnlocked || autocheat; dialog::addHelp( XLAT("In the Chaos mode, lands change very often, and " "there are no walls between them. " "Some lands are incompatible with this." "\n\nYou need to reach Crossroads IV to unlock the Chaos mode.") ); dialog::addBreak(100); char key = 'a'; for(int i=0; i dls(land_structure); auto li = eLandStructure(i); land_structure = li; fix_land_structure_choice(); if(ls::any_chaos() && !chaosUnlocked) continue; if(li == lsNoWalls && geometry == gNormal && !chaosUnlocked) continue; if(land_structure == i) { dialog::addBoolItem(land_structure_name(false), land_structure == dls.backup, key + i); dialog::add_action(dual::mayboth([li] { dialog::do_if_confirmed([li] { stop_game(); land_structure = li; start_game(); }); })); } } dialog::addBreak(100); dialog::addSelItem(XLAT("land"), XLAT1(linf[specialland].name), 'l'); dialog::add_action(activate_ge_land_selection); show_achievement_eligibility(); dialog::addBreak(100); if(ls::horodisk_structure()) add_edit(horodisk_from); else if(land_structure == lsChaosRW) add_edit(randomwalk_size); else if(land_structure == lsLandscape) add_edit(landscape_div); else if(land_structure == lsCursedWalls) add_edit(curse_percentage); else dialog::addBreak(100); dialog::addBack(); dialog::display(); } EX string custom_welcome; string customfile = "custom.hrm"; EX void show_custom() { cmode = sm::SIDE | sm::MAYDARK; gamescreen(); dialog::init(XLAT("custom mode")); if(custom_welcome != "") { dialog::addInfo("custom welcome message:"); dialog::addInfo(custom_welcome); dialog::addItem("edit", '/'); } else { dialog::addItem("custom welcome message", '/'); } dialog::add_action([] () { dialog::edit_string(custom_welcome, "custom welcome message", ""); }); dialog::addBreak(100); dialog::addItem("save custom mode", 's'); dialog::add_action([] { dialog::openFileDialog(customfile, XLAT("file to save:"), ".hrm", [] () { try { save_mode_to_file(customfile); addMessage(XLAT("Mode saved to %1", customfile)); return true; } catch(hstream_exception& e) { addMessage(XLAT("Failed to save mode to %1", customfile)); return false; } }); }); dialog::addItem("load custom mode", 'l'); dialog::add_action([] { dialog::openFileDialog(customfile, XLAT("file to load:"), ".hrm", [] () { try { load_mode_from_file(customfile); addMessage(XLAT("Loaded mode from %1", customfile)); return true; } catch(hstream_exception& e) { addMessage(XLAT("Failed to load mode from %1", customfile)); return false; } }); }); dialog::addBack(); dialog::display(); } EX void mode_higlights() { cmode = sm::NOSCR; gamescreen(); dialog::init(XLAT("highlights & achievements")); dialog::addBigItem(XLATN("Space Rocks"), 'r'); dialog::add_action(dialog::add_confirmation([] { popScreenAll(); stop_game(); resetModes(); specialland = laAsteroids; set_geometry(gKleinQuartic); set_variation(eVariation::bitruncated); land_structure = lsSingle; shmup::on = true; start_game(); })); dialog::addInfo(XLAT("classic game except hyperbolic")); dialog::extend(); dialog::addBigItem(XLATN("hyperbolic Minesweeper"), 'e'); dialog::add_action([] { dialog::do_if_confirmed([] { resetModes(); specialland = firstland = laMinefield; if(!closed_or_bounded) { set_geometry(gBring); gp::param = gp::loc(2, 1); set_variation(eVariation::goldberg); mine_adjacency_rule = true; bounded_mine_percentage = .2; } start_game(); popScreenAll(); }); }); dialog::addInfo(XLAT("yet another classic game")); dialog::extend(); #if CAP_RACING && MAXMDIM >= 4 dialog::addBigItem(XLAT("Racing in Thurston geometries"), 't'-96); dialog::add_action(dialog::add_confirmation(racing::start_thurston)); dialog::addInfo(XLAT("race through a maze in exotic 3D geometry!")); dialog::extend(); #endif dialog::addBigItem(XLAT1("Halloween"), 'Z'); dialog::add_action(dialog::add_confirmation(halloween::start_all)); dialog::addInfo(XLAT("Halloween mini-game")); dialog::extend(); dialog::addBigItem(XLAT1("heptagonal mode"), 'H'); dialog::add_action(dialog::add_confirmation([] { popScreenAll(); resetModes('7'); land_structure = lsNiceWalls; start_game(); clearMessages(); welcomeMessage(); })); dialog::addInfo(XLAT("can you find the Heptagonal Grail?")); dialog::extend(); dialog::addBreak(100); dialog::addBigItem(XLAT1("other achievements:"), 0); dialog::addItem(XLAT("General Euclid"), 'e'); dialog::add_action(dialog::add_confirmation([] { popScreenAll(); resetModes(); set_geometry(gEuclid); firstland = specialland = laMirrorOld; land_structure = lsSingle; start_game(); clearMessages(); welcomeMessage(); })); dialog::addItem(XLAT("Worm of the World"), 'w'); dialog::add_action(dialog::add_confirmation([] { popScreenAll(); resetModes(); set_geometry(gZebraQuotient); firstland = specialland = laDesert; land_structure = lsSingle; start_game(); clearMessages(); welcomeMessage(); })); dialog::addItem(XLAT("Lovász Conjecture"), 'L'); dialog::add_action(dialog::add_confirmation([] { popScreenAll(); resetModes(); set_geometry(gKleinQuartic); gp::param = gp::loc(1, 1); set_variation(eVariation::untruncated); firstland = specialland = laMotion; land_structure = lsSingle; start_game(); clearMessages(); welcomeMessage(); })); #if CAP_CRYSTAL if(hiitemsMax(itHolyGrail) || cheater || autocheat) { dialog::addItem(XLAT("Knight of the 16-Cell Table"), '1'); dialog::add_action(dialog::add_confirmation([] { popScreenAll(); resetModes(); crystal::set_crystal(8); firstland = specialland = laCamelot; land_structure = lsSingle; start_game(); clearMessages(); welcomeMessage(); pushScreen(crystal::crystal_knight_help); })); dialog::addItem(XLAT("Knight of the 3-Spherical Table"), '3'); dialog::add_action(dialog::add_confirmation([] { popScreenAll(); resetModes(); crystal::set_crystal(8); set_variation(eVariation::bitruncated); set_variation(eVariation::bitruncated); land_structure = lsSingle; firstland = specialland = laCamelot; start_game(); clearMessages(); welcomeMessage(); pushScreen(crystal::crystal_knight_help); })); } else { dialog::addItem("(locked until you find a Holy Grail)", 0); } #endif #if MAXMDIM >= 4 dialog::addBreak(100); dialog::addBigItem(XLAT1("some cool visualizations"), 0); dialog::addItem(XLAT("Emerald Mine in {5,3,4}"), '5'); dialog::add_action(dialog::add_confirmation([] { popScreenAll(); resetModes(); specialland = laEmerald; land_structure = lsSingle; set_geometry(gSpace534); check_cgi(); cgi.require_basics(); cgi.require_shapes(); fieldpattern::field_from_current(); set_geometry(gFieldQuotient); int p = 2; for(;; p++) { currfp.Prime = p; currfp.force_hash = 0x72414D0C; if(!currfp.solve()) break; } start_game(); clearMessages(); welcomeMessage(); })); #endif dialog::addBreak(100); dialog::addBack(); dialog::display(); } EX eLandStructure default_land_structure() { if(closed_or_bounded) return lsSingle; if(tactic::on || princess::challenge) return lsSingle; if(yendor::on) return yendor::get_land_structure(); if(specialland == laCanvas) return lsSingle; if(nice_walls_available()) return lsNiceWalls; return lsNoWalls; } EX void menuitem_land_structure(char key) { if(default_land_structure() == land_structure && !ineligible_starting_land) dialog::addBoolItem(XLAT("land structure"), false, key); else dialog::addSelItem(XLAT("land structure"), land_structure_name(true), key); dialog::add_action_push(show_chaos); } EX void showChangeMode() { cmode = sm::SIDE | sm::MAYDARK; gamescreen(); dialog::init(XLAT("special modes")); dialog::addBoolItem(XLAT("experiment with geometry"), geometry || CHANGED_VARIATION || viewdists, 'e'); dialog::add_action(runGeometryExperiments); dialog::addBoolItem(XLAT(SHMUPTITLE), shmup::on, 's'); dialog::add_action_confirmed(shmup::switch_shmup); dialog::addBoolItem(XLAT("multiplayer"), multi::players > 1, 'm'); dialog::add_action_push(multi::showConfigureMultiplayer); #if CAP_SAVE dialog::addSelItem(XLAT("casual mode"), ONOFF(casual), 'C'); dialog::add_action(switch_casual); #endif if(!shmup::on) { dialog::addSelItem(XLAT("hardcore mode"), hardcore && !pureHardcore() ? XLAT("PARTIAL") : ONOFF(hardcore), 'h'); dialog::add_action(switchHardcore); } if(getcstat == 'h') mouseovers = XLAT("One wrong move and it is game over!"); multi::cpid = 0; menuitem_land_structure('l'); dialog::addBoolItem(XLAT("custom land list"), use_custom_land_list, 'L'); dialog::add_action_push(customize_land_list); dialog::addBoolItem(XLAT("weapon selection"), bow::weapon, 'b'); dialog::add_action_push(bow::showMenu); dialog::addBoolItem(XLAT("puzzle/exploration mode"), peace::on, 'p'); dialog::add_action_push(peace::showMenu); dialog::addBoolItem(XLAT("Orb Strategy mode"), (inv::on), 'i'); dialog::add_action_confirmed([] { restart_game(rg::inv); }); dialog::addBoolItem(XLAT("random pattern mode"), (randomPatternsMode), 'r'); dialog::add_action_confirmed([] { stop_game(); firstland = laIce; restart_game(rg::randpattern); }); #if CAP_ARCM && !ISWEB if(multi::players == 1) { dialog::addBoolItem(XLAT("dual geometry mode"), dual::state, 'D'); dialog::add_action_confirmed([] { restart_game(rg::dualmode); }); } #endif dialog::addBoolItem(XLAT("cheat mode"), (cheater), 'c'); dialog::add_action(enable_cheat); dialog::addBreak(50); #if CAP_TOUR dialog::addBoolItem(XLAT("guided tour"), tour::on, 'T'); dialog::add_action_confirmed(tour::start); #endif #if CAP_DAILY dialog::addBoolItem(XLAT("Strange Challenge"), daily::on, 'z'); dialog::add_action_push(daily::showMenu); #endif dialog::addBoolItem(XLAT("%1 Challenge", moPrincess), (princess::challenge), 'P'); dialog::add_action_confirmed([] { if(!princess::everSaved && !autocheat) addMessage(XLAT("Save %the1 first to unlock this challenge!", moPrincess)); else restart_game(rg::princess); }); dialog::addBoolItem(XLAT("pure tactics mode"), (tactic::on), 't'); dialog::add_action(tactic::start); dialog::addBoolItem(XLAT("Yendor Challenge"), (yendor::on), 'y'); dialog::add_action([] { clearMessages(); if(yendor::everwon || autocheat) pushScreen(yendor::showMenu); else gotoHelp(yendor::chelp); }); #if CAP_RACING dialog::addBoolItem(XLAT("racing mode"), racing::on, 'R'); dialog::add_action(racing::configure_race); #endif show_achievement_eligibility(); dialog::addBreak(50); dialog::addItem(XLAT("highlights & achievements"), 'h'); dialog::add_action_push(mode_higlights); dialog::addItem(XLAT("custom mode manager"), 'm'); dialog::add_action_push(show_custom); dialog::addBack(); dialog::display(); } EX bool showstartmenu; EX bool showHalloween() { time_t t = time(NULL); struct tm tm = *localtime(&t); int month = tm.tm_mon + 1; int day = tm.tm_mday; if(month == 10 && day >= 24) return true; if(month == 11 && day <= 7) return true; return false; } int daily_mode; void announce_random() { dialog::addBreak(100); dialog::addTitle("(random option)", 0x808080, 50); } void announce_nothing() { dialog::addBreak(100); dialog::addTitle("", 0x808080, 50); } void announce_seasonal() { dialog::addBreak(100); dialog::addTitle("(seasonal option)", 0x808080, 50); } EX void showStartMenu() { if(!daily_mode) { daily_mode = hrand(10) + 1; if(showHalloween()) daily_mode = 20; } getcstat = ' '; #if CAP_STARTANIM cmode = sm::DARKEN; startanims::display(); #endif dialog::init(); dialog::addInfo(XLAT("Welcome to HyperRogue!")); dialog::addBreak(100); dialog::addBigItem(XLAT("HyperRogue classic"), 'c'); dialog::addInfo(XLAT("explore the world, collect treasures")); dialog::addInfo(XLAT("do not get checkmated")); #if CAP_INV dialog::addBreak(100); dialog::addBigItem(XLAT("Orb Strategy mode"), 'i'); dialog::addInfo(XLAT("use your Orbs in tough situations")); #endif #if CAP_TOUR dialog::addBreak(100); dialog::addBigItem(XLAT("guided tour"), 't'); dialog::addInfo(XLAT("learn about hyperbolic geometry!")); #endif if(have_current_settings()) { dialog::addBreak(100); dialog::addBigItem(XLAT1("use current/saved settings"), SDLK_ESCAPE); } if(have_current_graph_settings()) { dialog::addBreak(100); dialog::addBigItem(XLAT1("reset the graphics settings"), 'r'); dialog::add_action([] () { reset_graph_settings(); }); } dialog::addBreak(100); dialog::addBigItem(XLAT("main menu"), 'm'); dialog::addInfo(XLAT("more options")); switch(daily_mode) { case 1: #if CAP_SHMUP_GOOD announce_random(); dialog::addBigItem(XLAT("shoot'em up mode"), 's'); dialog::addInfo(XLAT("continuous spacetime")); #if CAP_ACHIEVE dialog::addInfo(XLAT("(most achievements are not available)")); #endif #endif break; case 2: announce_random(); dialog::addBigItem(XLAT("heptagonal mode"), '7'); dialog::addInfo(XLAT("more curvature")); dialog::addInfo(XLAT("(most achievements are not available)")); break; case 3: announce_random(); dialog::addBigItem(XLAT("experiment with geometry"), 'g'); dialog::addInfo(XLAT("(most achievements are not available)")); break; case 4: if(chaosUnlocked) { announce_random(); dialog::addBigItem(XLAT("Chaos mode"), 'C'); dialog::addInfo(XLAT("(most achievements are not available)")); } break; #if CAP_RUG case 5: announce_random(); dialog::addBigItem(XLAT("hypersian rug mode"), 'M'); dialog::addInfo(XLAT("see the true form")); break; #endif #if CAP_TEXTURE && CAP_EDIT case 6: announce_random(); dialog::addBigItem(XLAT("texture mode"), 'T'); dialog::addInfo(XLAT("paint pictures")); break; #endif #if CAP_DAILY case 7: announce_random(); dialog::addBigItem(XLAT("Strange Challenge"), 'z'); dialog::addInfo(XLAT("compete with other players on random lands in random geometries")); break; #endif #if CAP_RACING case 8: announce_random(); dialog::addBigItem(XLAT("Racing"), 'r'-96); dialog::addInfo(XLAT("how fast can you reach the end?")); break; case 9: announce_random(); dialog::addBigItem(XLAT("Racing in Thurston geometries"), 't'-96); dialog::addInfo(XLAT("race through a maze in exotic 3D geometry!")); break; #endif case 20: announce_seasonal(); dialog::addBigItem(XLAT1("Halloween"), 'Z'); dialog::addInfo(XLAT("Halloween mini-game")); break; default: announce_nothing(); dialog::addBigItem("", 0); dialog::addInfo(""); break; } dialog::display(); clearMessages(); timerstart = time(NULL); keyhandler = [] (int sym, int uni) { dialog::handleNavigation(sym, uni); if(uni == 'o') uni = 'i'; #if CAP_STARTANIM else if(uni == startanims::EXPLORE_START_ANIMATION) startanims::explore(); #endif #if CAP_RUG else if(uni == 'M') { rug::init(); popScreenAll(); resetModes('c'); clearMessages(); welcomeMessage(); vid.wallmode = 3; vid.monmode = 2; rug::model_distance *= 2; rug::init(); } #endif #if CAP_TEXTURE && CAP_EDIT else if(uni == 'T') { popScreenAll(); pushScreen(texture::showMenu); resetModes('c'); stop_game(); enable_canvas(); cheater = true; patterns::canvasback = 0xFFFFFF; mapeditor::drawplayer = false; start_game(); clearMessages(); welcomeMessage(); calcparam(); drawthemap(); texture::start_editor(); } #endif else if(uni == 'g') { popScreenAll(); resetModes('c'); clearMessages(); welcomeMessage(); pushScreen(showEuclideanMenu); } else if(uni == 'c' || uni == 'i' || uni == 's' || uni == 'C' || uni == '7') { if(uni == 'C' && !chaosUnlocked) { return; } popScreenAll(); resetModes(uni); clearMessages(); welcomeMessage(); progress_warning(); stampbase = ticks; if(uni == 's') multi::configure(); } else if(uni == 'Z') halloween::start_all(); #if CAP_RACING && MAXMDIM >= 4 else if(uni == 'r' - 96) { popScreenAll(); resetModes(); stop_game(); switch_game_mode(rg::racing); specialland = racing::race_lands[rand() % isize(racing::race_lands)]; start_game(); pmodel = mdBand; pconf.mori() = racing::race_angle; racing::race_advance = 1; vid.yshift = 0; pconf.cam() = Id; pconf.xposition = 0; pconf.yposition = 0; pconf.scale = 1; vid.use_smart_range = 1; vid.smart_range_detail = 3; } else if(uni == 't' - 96) racing::start_thurston(); #endif #if CAP_TOUR else if(uni == 't') { popScreenAll(); resetModes('c'); tour::start(); } #endif #if CAP_DAILY else if(uni == 'z') { popScreenAll(); pushScreen(daily::showMenu); } #endif else if(uni == 'm') { popScreen(); pushScreen(showGameMenu); } else if(sym == SDLK_F10) quitmainloop = true; else if(sym == SDLK_F1) gotoHelp("@"); else if(sym == SDLK_ESCAPE || uni == ' ') { popScreen(); timerstart = time(NULL); stampbase = ticks; clearMessages(); welcomeMessage(); progress_warning(); } else if(sym == SDLK_F5) { #if CAP_STARTANIM startanims::pick(); #endif daily_mode = 0; } }; } // -- overview -- #if HDR struct named_functionality { std::string first; reaction_t second; explicit named_functionality() = default; explicit named_functionality(std::string s, reaction_t r) : first(std::move(s)), second(std::move(r)) {} friend bool operator==(const named_functionality& a, const named_functionality& b) { return a.first == b.first; } friend bool operator!=(const named_functionality& a, const named_functionality& b) { return a.first != b.first; } }; inline named_functionality named_dialog(string x, reaction_t dialog) { return named_functionality(x, [dialog] () { pushScreen(dialog); }); } #endif #if HDR using o_funcs = vector; #endif EX hookset hooks_o_key; EX named_functionality get_o_key() { vector res; callhooks(hooks_o_key, res); if(in_full_game() && !yendor::on) res.push_back(named_dialog(XLAT("world overview"), showOverview)); #if CAP_DAILY if(daily::on) res.push_back(named_functionality(XLAT("Strange Challenge"), [] () { achievement_final(false); pushScreen(daily::showMenu); })); #endif if(viewdists) res.push_back(named_functionality(XLAT("experiment with geometry"), runGeometryExperiments)); if(tactic::on) res.push_back(named_dialog(XLAT("pure tactics mode"), tactic::showMenu)); if(yendor::on) res.push_back(named_dialog(XLAT("Yendor Challenge"), yendor::showMenu)); if(peace::on) res.push_back(named_dialog(XLAT("puzzles and exploration"), peace::showMenu)); #if CAP_TEXTURE if(texture::config.tstate) res.push_back(named_dialog(XLAT("texture mode"), texture::showMenu)); #endif dialog::infix = ""; if((geometry != gNormal || NONSTDVAR || disksize) && !daily::on) res.push_back(named_functionality(XLAT("experiment with geometry"), runGeometryExperiments)); if(res.empty()) return named_dialog(XLAT("world overview"), showOverview); if(isize(res) == 1) return res[0]; return named_dialog(res[0].first + "/...", [res] { emptyscreen(); dialog::init(); char id = 'o'; for(auto& r: res) { dialog::addItem(r.first, id++); dialog::add_action([r] { popScreen(); r.second(); }); } dialog::display(); }); } EX int messagelogpos; EX int timeformat; EX int stampbase; EX string gettimestamp(msginfo& m) { char buf[128]; switch(timeformat) { case 0: return its(m.turnstamp); case 1: strftime(buf, 128, "%H:%M:%S", localtime(&m.rtstamp)); return buf; case 2: snprintf(buf, 128, "%2d:%02d", m.gtstamp/60, m.gtstamp % 60); return buf; case 3: { int t = m.stamp - stampbase; bool sign = t < 0; if(sign) t = -t; snprintf(buf, 128, "%2d:%02d.%03d", t/60000, (t/1000) % 60, t % 1000); string s = buf; if(sign) s = "-"+s; return s; } } return ""; } EX void showMessageLog() { DEBBI(DF_GRAPH, ("show message log")); int lines = vid.yres / vid.fsize - 2; int maxpos = isize(gamelog) - lines; messagelogpos = min(messagelogpos, maxpos); messagelogpos = max(messagelogpos, 0); for(int y=0; y