diff --git a/files/lang/cs.po b/files/lang/cs.po index dd11f80d86..2877ffbd6f 100644 --- a/files/lang/cs.po +++ b/files/lang/cs.po @@ -16,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2010-05-27 09:12+0000\n" -"X-Generator: Poedit 3.6\n" +"X-Generator: Poedit 3.2.2\n" msgid "SELECT" msgstr "ZVOLIT" @@ -220,9 +220,7 @@ msgstr "" msgid "" "BATTLE\n" "ONLY" -msgstr "" -"VOLNÝ\n" -"BOJ" +msgstr "VOLNÝ BOJ" msgid "" "CAMPAIGN\n" @@ -236,9 +234,8 @@ msgid "" "PLAYER\n" "GAME" msgstr "" -"HRA\n" -"VÍCE\n" -"HRÁČŮ" +"HRA PRO\n" +"VÍCE HRÁČŮ" msgid "CONFIG" msgstr "CONFIG" @@ -258,7 +255,9 @@ msgstr "" "DATADISKU" msgid "HOT SEAT" -msgstr "HORKÉ KŘESLO" +msgstr "" +"HORKÉ\n" +"KŘESLO" msgid "2 PLAYERS" msgstr "2 HRÁČI" diff --git a/files/lang/es.po b/files/lang/es.po index 3cf95c91be..eb4c27c3d8 100644 --- a/files/lang/es.po +++ b/files/lang/es.po @@ -259,32 +259,34 @@ msgstr "" "SIÓN" msgid "HOT SEAT" -msgstr "ASIENTO CALIENTE" +msgstr "" +"ASIENTO\n" +"CALIENTE" msgid "2 PLAYERS" msgstr "" -"2 JUGA-\n" -"DORES" +"2\n" +"JUGADORES" msgid "3 PLAYERS" msgstr "" -"3 JUGA-\n" -"DORES" +"3\n" +"JUGADORES" msgid "4 PLAYERS" msgstr "" -"4 JUGA-\n" -"DORES" +"4\n" +"JUGADORES" msgid "5 PLAYERS" msgstr "" -"5 JUGA-\n" -"DORES" +"5\n" +"JUGADORES" msgid "6 PLAYERS" msgstr "" -"6 JUGA-\n" -"DORES" +"6\n" +"JUGADORES" msgid "DIFFICULTY" msgstr "DIFICULTAD" diff --git a/files/lang/nb.po b/files/lang/nb.po index b56f18e110..84e13fd385 100644 --- a/files/lang/nb.po +++ b/files/lang/nb.po @@ -229,15 +229,15 @@ msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" -"KRIGEN\n" -"OM\n" -"TRONEN" +"TRONE-\n" +"KRIGENE" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" -"PRISEN FOR\n" +"PRISEN\n" +"FOR\n" "LOJALITET" msgid "HOT SEAT" @@ -2051,9 +2051,8 @@ msgstr "Roland" msgid "Archibald" msgstr "Archibald" -#, fuzzy msgid "Price of Loyalty" -msgstr "Prisen for trofasthet" +msgstr "Prisen for lojalitet" msgid "Voyage Home" msgstr "Ferden hjem" @@ -3762,22 +3761,21 @@ msgstr "Størrelsesikon" msgid "Map Type" msgstr "Kart-type" -#, fuzzy msgid "" "Indicates whether the map is made for \"The Succession Wars\", \"The Price " "of Loyalty\" or \"Resurrection\" version of the game." msgstr "" -"Viser om kartet er laget for spillutgaven \"Krigen om tronen\" eller " -"\"Prisen for trofasthet\"." +"Viser om kartet er laget for spillutgaven \"Tronekrigene\" eller \"Prisen " +"for lojalitet\"." msgid "Map Type:\n" msgstr "Kart-type:\n" msgid "The Succession Wars" -msgstr "Krigen om tronen" +msgstr "Tronekrigene" msgid "The Price of Loyalty" -msgstr "Prisen for trofasthet" +msgstr "Prisen for lojalitet" #, fuzzy msgid "Resurrection" @@ -5262,7 +5260,7 @@ msgid "Original project before 0.7" msgstr "Opprinnelig prosjekt før 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" -msgstr "Teamet bak Heroes of Might and Magic II" +msgstr "Teamet bak Heroes of Might and Magic II: Tronekrigene" msgid "Designed and Directed" msgstr "Utforming og ledelse" @@ -5478,10 +5476,10 @@ msgid "hotkey|battle only game" msgstr "Kampparti" msgid "hotkey|choose the original campaign" -msgstr "Velg Krigen om tronen" +msgstr "Velg Tronekrigene" msgid "hotkey|choose the expansion campaign" -msgstr "Velg Prisen for Lojalitet" +msgstr "Velg Prisen for lojalitet" msgid "hotkey|map editor main menu" msgstr "Karttegnerens hovedmeny" @@ -5520,9 +5518,8 @@ msgstr "Rolands felttog" msgid "hotkey|archibald campaign" msgstr "Archibalds felttog" -#, fuzzy msgid "hotkey|price of loyalty campaign" -msgstr "Felttoget Prisen for trofasthet" +msgstr "Felttoget Prisen for lojalitet" msgid "hotkey|voyage home campaign" msgstr "Felttoget Ferden hjem" @@ -5795,12 +5792,11 @@ msgstr "Sist støttede utgave: " msgid "This file contains a save with an invalid game type." msgstr "Denne filen inneholder et lagringspunkt med en ugyldig spillversjon." -#, fuzzy msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" -"Denne filen ble lagret i spillutgaven \"Prisen for trofasthet\", men de " +"Denne filen ble lagret i spillutgaven \"Prisen for lojalitet\", men de " "tilhørende utvidelsesfilene mangler." msgid "This saved game is localized to '" @@ -5918,7 +5914,7 @@ msgstr "Utvidelsesfelttog" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" -"En av de fire nye utvidelsesfelttogene fra utvidelsen Prisen for trofasthet." +"En av de fire nye utvidelsesfelttogene fra utvidelsen Prisen for lojalitet." msgid "Loading video. Please wait..." msgstr "Laster video. Vennligst vent..." diff --git a/files/lang/pl.po b/files/lang/pl.po index 3f1c6d201b..0286f86d8a 100644 --- a/files/lang/pl.po +++ b/files/lang/pl.po @@ -11,8 +11,7 @@ msgstr "" "POT-Creation-Date: 2025-06-06 02:30+0000\n" "PO-Revision-Date: 2024-07-20 23:19+0200\n" "Last-Translator: fheroes2 team \n" -"Language-Team: https://discord.com/" -"channels/733093692860137523/817386375888371772\n" +"Language-Team: \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -20,7 +19,7 @@ msgstr "" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2013-10-09 02:01+0000\n" -"X-Generator: Poedit 3.4.4\n" +"X-Generator: Poedit 3.2.2\n" msgid "SELECT" msgstr "WYBIERZ" @@ -193,8 +192,8 @@ msgid "" "STANDARD\n" "GAME" msgstr "" -"GRA\n" -"STANDARDOWA" +"GRA STAN-\n" +"DARDOWA" msgid "" "BATTLE\n" @@ -213,9 +212,8 @@ msgid "" "PLAYER\n" "GAME" msgstr "" -"GRA\n" -"DLA WIELU\n" -"OSÓB" +"GRA DLA\n" +"WIELU OSÓB" msgid "CONFIG" msgstr "KONFIGURACJA" @@ -231,8 +229,9 @@ msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" -"KAMPANIA Z\n" -"ROZSZERZENIA" +"KAMPANIA\n" +"Z ROZSZE-\n" +"RZENIA" msgid "HOT SEAT" msgstr "" diff --git a/files/lang/pt.po b/files/lang/pt.po index 5cf15cf3f4..581536603b 100644 --- a/files/lang/pt.po +++ b/files/lang/pt.po @@ -16,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Launchpad-Export-Date: 2010-05-27 09:12+0000\n" -"X-Generator: Poedit 3.5\n" +"X-Generator: Poedit 3.2.2\n" msgid "SELECT" msgstr "SELEC." @@ -225,9 +225,7 @@ msgstr "" msgid "" "CAMPAIGN\n" "GAME" -msgstr "" -"CAMPA-\n" -"NHA" +msgstr "CAMPANHA" msgid "" "MULTI-\n" diff --git a/files/lang/sk.po b/files/lang/sk.po index 9218db509d..085c848902 100644 --- a/files/lang/sk.po +++ b/files/lang/sk.po @@ -217,9 +217,7 @@ msgstr "INFO" msgid "" "STANDARD\n" "GAME" -msgstr "" -"BEŽNÁ\n" -"HRA" +msgstr "BEŽNÁ HRA" msgid "" "BATTLE\n" @@ -238,8 +236,7 @@ msgid "" "PLAYER\n" "GAME" msgstr "" -"HRA\n" -"VIAC\n" +"HRA VIAC\n" "HRÁČOV" msgid "CONFIG" diff --git a/files/lang/sv.po b/files/lang/sv.po index e415d657ed..9d07336e67 100644 --- a/files/lang/sv.po +++ b/files/lang/sv.po @@ -231,9 +231,7 @@ msgid "" "MULTI-\n" "PLAYER\n" "GAME" -msgstr "" -"MULTI-\n" -"SPEL" +msgstr "MULTISPEL" msgid "CONFIG" msgstr "" @@ -255,7 +253,9 @@ msgstr "" "KAMPANJ" msgid "HOT SEAT" -msgstr "HETA STOLEN" +msgstr "" +"HETA\n" +"STOLEN" msgid "2 PLAYERS" msgstr "2 SPELARE" diff --git a/files/lang/tr.po b/files/lang/tr.po index 56187cec4e..37db6b435e 100644 --- a/files/lang/tr.po +++ b/files/lang/tr.po @@ -16,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Launchpad-Export-Date: 2010-05-27 09:12+0000\n" -"X-Generator: Poedit 3.5\n" +"X-Generator: Poedit 3.2.2\n" msgid "SELECT" msgstr "SEÇ" @@ -253,7 +253,9 @@ msgstr "" "HİKAYESİ" msgid "HOT SEAT" -msgstr "SIRAYLA OYNAMA" +msgstr "" +"SIRAYLA\n" +"OYNAMA" msgid "2 PLAYERS" msgstr "2 OYUNCU" diff --git a/src/fheroes2/agg/agg_image.cpp b/src/fheroes2/agg/agg_image.cpp index 3baeed2cd1..e3d302fd49 100644 --- a/src/fheroes2/agg/agg_image.cpp +++ b/src/fheroes2/agg/agg_image.cpp @@ -124,22 +124,10 @@ namespace ICN::BUTTON_MAPSIZE_ALL, ICN::BUTTON_MAP_SELECT_GOOD, ICN::BUTTON_MAP_SELECT_EVIL, - ICN::BUTTON_STANDARD_GAME, - ICN::BUTTON_CAMPAIGN_GAME, - ICN::BUTTON_MULTIPLAYER_GAME, - ICN::BUTTON_LARGE_CANCEL, - ICN::BUTTON_LARGE_CONFIG, - ICN::BUTTON_ORIGINAL_CAMPAIGN, - ICN::BUTTON_EXPANSION_CAMPAIGN, - ICN::BUTTON_HOT_SEAT, - ICN::BUTTON_2_PLAYERS, - ICN::BUTTON_3_PLAYERS, - ICN::BUTTON_4_PLAYERS, - ICN::BUTTON_5_PLAYERS, - ICN::BUTTON_6_PLAYERS, - ICN::BUTTON_BATTLE_ONLY, - ICN::BUTTON_NEW_MAP, - ICN::BUTTON_LOAD_MAP, + ICN::BUTTONS_NEW_GAME_MENU_GOOD, + ICN::BUTTONS_NEW_GAME_MENU_EVIL, + ICN::BUTTONS_EDITOR_MENU_GOOD, + ICN::BUTTONS_EDITOR_MENU_EVIL, ICN::BUTTON_GIFT_GOOD, ICN::BUTTON_GIFT_EVIL, ICN::UNIFORM_EVIL_MAX_BUTTON, @@ -383,10 +371,10 @@ namespace } // NOTE: Do not call this with an evil style button's ICN ID. - void convertToEvilButtonBackground( fheroes2::Sprite & released, fheroes2::Sprite & pressed, const int goodButtonIcnId ) + void convertToEvilButtonBackground( fheroes2::Sprite & released, fheroes2::Sprite & pressed, const int goodButtonIcnId, const uint32_t index ) { - released = fheroes2::AGG::GetICN( goodButtonIcnId, 0 ); - pressed = fheroes2::AGG::GetICN( goodButtonIcnId, 1 ); + released = fheroes2::AGG::GetICN( goodButtonIcnId, index ); + pressed = fheroes2::AGG::GetICN( goodButtonIcnId, index + 1 ); const std::vector & goodToEvilPalette = PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_BUTTON ); fheroes2::ApplyPalette( released, goodToEvilPalette ); @@ -1263,11 +1251,12 @@ namespace case ICN::BUTTON_SMALL_MAX_EVIL: case ICN::BUTTON_SMALL_MIN_EVIL: case ICN::BUTTON_SMALL_RESTART_EVIL: + case ICN::BUTTONS_NEW_GAME_MENU_EVIL: + case ICN::BUTTONS_EDITOR_MENU_EVIL: case ICN::UNIFORM_EVIL_MAX_BUTTON: case ICN::UNIFORM_EVIL_MIN_BUTTON: { - // We do palette swaps for these due to two reasons: to generate completely new buttons, or to preserve + // We do palette swaps for these due to either one of two reasons: to generate completely new buttons, or to preserve // language-specific generations from generateLanguageSpecificImages(). - _icnVsSprite[id].resize( 2 ); int goodButtonIcnID = ICN::UNKNOWN; if ( id == ICN::BUTTON_MAP_SELECT_EVIL ) { @@ -1306,16 +1295,27 @@ namespace else if ( id == ICN::BUTTON_LOAD_MAP_EVIL ) { goodButtonIcnID = ICN::BUTTON_LOAD_MAP_GOOD; } + else if ( id == ICN::BUTTONS_NEW_GAME_MENU_EVIL ) { + goodButtonIcnID = ICN::BUTTONS_NEW_GAME_MENU_GOOD; + } + else if ( id == ICN::BUTTONS_EDITOR_MENU_EVIL ) { + goodButtonIcnID = ICN::BUTTONS_EDITOR_MENU_GOOD; + } + _icnVsSprite[id].resize( fheroes2::AGG::GetICNCount( goodButtonIcnID ) ); + const size_t icnSize = _icnVsSprite[id].size(); + for ( size_t i = 0; i < ( icnSize / 2 ); ++i ) { + convertToEvilButtonBackground( _icnVsSprite[id][i * 2], _icnVsSprite[id][i * 2 + 1], goodButtonIcnID, static_cast( i * 2 ) ); + } - convertToEvilButtonBackground( _icnVsSprite[id][0], _icnVsSprite[id][1], goodButtonIcnID ); break; } case ICN::BUTTONS_FILE_DIALOG_EVIL: case ICN::BUTTONS_FILE_DIALOG_GOOD: { _icnVsSprite[id].resize( 8 ); + const bool isEvilInterface = ( id == ICN::BUTTONS_FILE_DIALOG_EVIL ); if ( useOriginalResources() ) { - const int buttonIcnID = id == ICN::BUTTONS_FILE_DIALOG_EVIL ? ICN::CPANELE : ICN::CPANEL; + const int buttonIcnID = isEvilInterface ? ICN::CPANELE : ICN::CPANEL; for ( size_t i = 0; i < _icnVsSprite[id].size(); ++i ) { _icnVsSprite[id][i] = fheroes2::AGG::GetICN( buttonIcnID, static_cast( i ) ); } @@ -1328,7 +1328,7 @@ namespace fheroes2::getSupportedText( gettext_noop( "LOAD\nGAME" ), buttonFontType ), fheroes2::getSupportedText( gettext_noop( "SAVE\nGAME" ), buttonFontType ), fheroes2::getSupportedText( gettext_noop( "QUIT" ), buttonFontType ) }, - 80 ); + isEvilInterface, 80 ); break; } @@ -1419,205 +1419,112 @@ namespace break; } - case ICN::BUTTON_2_PLAYERS: - case ICN::BUTTON_3_PLAYERS: - case ICN::BUTTON_4_PLAYERS: - case ICN::BUTTON_5_PLAYERS: - case ICN::BUTTON_6_PLAYERS: - case ICN::BUTTON_BATTLE_ONLY: - case ICN::BUTTON_NEW_MAP: - case ICN::BUTTON_LOAD_MAP: - case ICN::BUTTON_CAMPAIGN_GAME: - case ICN::BUTTON_EXPANSION_CAMPAIGN: - case ICN::BUTTON_HOT_SEAT: - case ICN::BUTTON_LARGE_CANCEL: - case ICN::BUTTON_LARGE_CONFIG: - case ICN::BUTTON_MULTIPLAYER_GAME: - case ICN::BUTTON_ORIGINAL_CAMPAIGN: - case ICN::BUTTON_STANDARD_GAME: { - _icnVsSprite[id].resize( 2 ); + case ICN::BUTTONS_NEW_GAME_MENU_GOOD: { + // Set the size depending on whether PoL assets are present or not, in which case add 4 more for campaign buttons. + const bool isPoLPresent = !::AGG::getDataFromAggFile( ICN::getIcnFileName( ICN::X_TRACK1 ), false ).empty(); + if ( isPoLPresent ) { + _icnVsSprite[id].resize( 28 ); + } + else { + _icnVsSprite[id].resize( 24 ); + } - if ( useOriginalResources() && id != ICN::BUTTON_BATTLE_ONLY ) { - int buttonIcnID = ICN::UNKNOWN; - std::pair icnIndex; - switch ( id ) { - case ICN::BUTTON_CAMPAIGN_GAME: { - buttonIcnID = ICN::BTNNEWGM; - icnIndex = { 2, 3 }; - break; - } - case ICN::BUTTON_MULTIPLAYER_GAME: { - buttonIcnID = ICN::BTNNEWGM; - icnIndex = { 4, 5 }; - break; - } - case ICN::BUTTON_LARGE_CANCEL: { - buttonIcnID = ICN::BTNNEWGM; - icnIndex = { 6, 7 }; - break; - } - case ICN::BUTTON_LARGE_CONFIG: { - buttonIcnID = ICN::BTNDCCFG; - icnIndex = { 4, 5 }; - break; - } - case ICN::BUTTON_ORIGINAL_CAMPAIGN: { - buttonIcnID = ICN::X_LOADCM; - icnIndex = { 0, 1 }; - break; - } - case ICN::BUTTON_EXPANSION_CAMPAIGN: { - buttonIcnID = ICN::X_LOADCM; - icnIndex = { 2, 3 }; - break; - } - case ICN::BUTTON_HOT_SEAT: { - buttonIcnID = ICN::BTNMP; - icnIndex = { 0, 1 }; - break; - } - case ICN::BUTTON_2_PLAYERS: { - buttonIcnID = ICN::BTNHOTST; - icnIndex = { 0, 1 }; - break; - } - case ICN::BUTTON_3_PLAYERS: { - buttonIcnID = ICN::BTNHOTST; - icnIndex = { 2, 3 }; - break; - } - case ICN::BUTTON_4_PLAYERS: { - buttonIcnID = ICN::BTNHOTST; - icnIndex = { 4, 5 }; - break; - } - case ICN::BUTTON_5_PLAYERS: { - buttonIcnID = ICN::BTNHOTST; - icnIndex = { 6, 7 }; - break; - } - case ICN::BUTTON_6_PLAYERS: { - buttonIcnID = ICN::BTNHOTST; - icnIndex = { 8, 9 }; - break; - } - case ICN::BUTTON_NEW_MAP: { - buttonIcnID = ICN::BTNEMAIN; - icnIndex = { 0, 1 }; - break; - } - case ICN::BUTTON_LOAD_MAP: { - buttonIcnID = ICN::BTNEMAIN; - icnIndex = { 2, 3 }; - break; - } - default: - // All other buttons share the same settings. - buttonIcnID = ICN::BTNNEWGM; - icnIndex = { 0, 1 }; - break; + if ( useOriginalResources() ) { + for ( size_t i = 0; i < 3; ++i ) { + _icnVsSprite[id][i * 2] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, static_cast( i * 2 ) ); + _icnVsSprite[id][i * 2 + 1] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, static_cast( i * 2 + 1 ) ); + } + // Add generated buttons. + const fheroes2::FontType buttonFontType = fheroes2::FontType::buttonReleasedWhite(); + const fheroes2::Size buttonSize{ _icnVsSprite[id][0].width() - 10, _icnVsSprite[id][0].height() }; + fheroes2::makeButtonSprites( _icnVsSprite[id][6], _icnVsSprite[id][7], fheroes2::getSupportedText( gettext_noop( "BATTLE\nONLY" ), buttonFontType ), + buttonSize, false, ICN::STONEBAK ); + fheroes2::makeButtonSprites( _icnVsSprite[id][8], _icnVsSprite[id][9], fheroes2::getSupportedText( gettext_noop( "SETTINGS" ), buttonFontType ), + buttonSize, false, ICN::STONEBAK ); + // Add cancel button. + _icnVsSprite[id][10] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 6 ); + _icnVsSprite[id][11] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 7 ); + // Add hot seat button. + _icnVsSprite[id][12] = fheroes2::AGG::GetICN( ICN::BTNMP, 0 ); + _icnVsSprite[id][13] = fheroes2::AGG::GetICN( ICN::BTNMP, 1 ); + // Add player count buttons. + for ( size_t i = 0; i < 5; ++i ) { + _icnVsSprite[id][( i + 7 ) * 2] = fheroes2::AGG::GetICN( ICN::BTNHOTST, static_cast( i * 2 ) ); + _icnVsSprite[id][( i + 7 ) * 2 + 1] = fheroes2::AGG::GetICN( ICN::BTNHOTST, static_cast( i * 2 + 1 ) ); + } + + if ( isPoLPresent ) { + _icnVsSprite[id][24] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 0 ); + _icnVsSprite[id][25] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 1 ); + _icnVsSprite[id][26] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 2 ); + _icnVsSprite[id][27] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 3 ); } + break; + } + const fheroes2::FontType buttonFontType = fheroes2::FontType::buttonReleasedWhite(); + std::vector texts = { fheroes2::getSupportedText( gettext_noop( "STANDARD\nGAME" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "CAMPAIGN\nGAME" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "MULTI-\nPLAYER\nGAME" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "BATTLE\nONLY" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "SETTINGS" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "CANCEL" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "HOT SEAT" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "2 PLAYERS" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "3 PLAYERS" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "4 PLAYERS" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "5 PLAYERS" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "6 PLAYERS" ), buttonFontType ) }; + if ( isPoLPresent ) { + texts.emplace_back( fheroes2::getSupportedText( gettext_noop( "ORIGINAL\nCAMPAIGN" ), buttonFontType ) ); + texts.emplace_back( fheroes2::getSupportedText( gettext_noop( "EXPANSION\nCAMPAIGN" ), buttonFontType ) ); + } + + fheroes2::makeSymmetricBackgroundSprites( _icnVsSprite[id], texts, false, 80 ); + break; + } + case ICN::BUTTONS_EDITOR_MENU_GOOD: { + _icnVsSprite[id].resize( 20 ); - _icnVsSprite[id][0] = fheroes2::AGG::GetICN( buttonIcnID, icnIndex.first ); - _icnVsSprite[id][1] = fheroes2::AGG::GetICN( buttonIcnID, icnIndex.second ); - if ( id == ICN::BUTTON_CAMPAIGN_GAME ) { - // Fix the disabled state. - const fheroes2::Sprite & released = fheroes2::AGG::GetICN( buttonIcnID, icnIndex.first ); - const fheroes2::Sprite & pressed = fheroes2::AGG::GetICN( buttonIcnID, icnIndex.second ); - fheroes2::Image common = fheroes2::ExtractCommonPattern( { &released, &pressed } ); - common = fheroes2::FilterOnePixelNoise( common ); - common = fheroes2::FilterOnePixelNoise( common ); - common = fheroes2::FilterOnePixelNoise( common ); - fheroes2::Blit( common, _icnVsSprite[id][0] ); + if ( useOriginalResources() ) { + for ( size_t i = 0; i < 2; ++i ) { + _icnVsSprite[id][i * 2] = fheroes2::AGG::GetICN( ICN::BTNEMAIN, static_cast( i * 2 ) ); + _icnVsSprite[id][i * 2 + 1] = fheroes2::AGG::GetICN( ICN::BTNEMAIN, static_cast( i * 2 + 1 ) ); + } + // Add generated Main Menu button. + const fheroes2::FontType buttonFontType = fheroes2::FontType::buttonReleasedWhite(); + const fheroes2::Size buttonSize{ _icnVsSprite[id][0].width() - 10, _icnVsSprite[id][0].height() }; + fheroes2::makeButtonSprites( _icnVsSprite[id][4], _icnVsSprite[id][5], fheroes2::getSupportedText( gettext_noop( "MAIN\nMENU" ), buttonFontType ), + buttonSize, false, ICN::STONEBAK ); + // Add generated Back button. + fheroes2::makeButtonSprites( _icnVsSprite[id][6], _icnVsSprite[id][7], + fheroes2::getSupportedText( gettext_noop( "EditorMainMenu|BACK" ), buttonFontType ), buttonSize, false, ICN::STONEBAK ); + + // Add From Scratch and Random buttons. + for ( size_t i = 0; i < 2; ++i ) { + _icnVsSprite[id][( i + 4 ) * 2] = fheroes2::AGG::GetICN( ICN::BTNENEW, static_cast( i * 2 ) ); + _icnVsSprite[id][( i + 4 ) * 2 + 1] = fheroes2::AGG::GetICN( ICN::BTNENEW, static_cast( i * 2 + 1 ) ); + } + // Add map size buttons. + for ( size_t i = 0; i < 4; ++i ) { + _icnVsSprite[id][( i + 6 ) * 2] = fheroes2::AGG::GetICN( ICN::BTNESIZE, static_cast( i * 2 ) ); + _icnVsSprite[id][( i + 6 ) * 2 + 1] = fheroes2::AGG::GetICN( ICN::BTNESIZE, static_cast( i * 2 + 1 ) ); } break; } - const char * text = gettext_noop( "STANDARD\nGAME" ); - switch ( id ) { - case ICN::BUTTON_BATTLE_ONLY: { - text = gettext_noop( "BATTLE\nONLY" ); - break; - } - case ICN::BUTTON_CAMPAIGN_GAME: { - text = gettext_noop( "CAMPAIGN\nGAME" ); - break; - } - case ICN::BUTTON_MULTIPLAYER_GAME: { - text = gettext_noop( "MULTI-\nPLAYER\nGAME" ); - break; - } - case ICN::BUTTON_LARGE_CANCEL: { - text = gettext_noop( "CANCEL" ); - break; - } - case ICN::BUTTON_LARGE_CONFIG: { - text = gettext_noop( "CONFIG" ); - break; - } - case ICN::BUTTON_ORIGINAL_CAMPAIGN: { - text = gettext_noop( "ORIGINAL\nCAMPAIGN" ); - break; - } - case ICN::BUTTON_EXPANSION_CAMPAIGN: { - text = gettext_noop( "EXPANSION\nCAMPAIGN" ); - break; - } - case ICN::BUTTON_HOT_SEAT: { - text = gettext_noop( "HOT SEAT" ); - break; - } - case ICN::BUTTON_2_PLAYERS: { - text = gettext_noop( "2 PLAYERS" ); - break; - } - case ICN::BUTTON_3_PLAYERS: { - text = gettext_noop( "3 PLAYERS" ); - break; - } - case ICN::BUTTON_4_PLAYERS: { - text = gettext_noop( "4 PLAYERS" ); - break; - } - case ICN::BUTTON_5_PLAYERS: { - text = gettext_noop( "5 PLAYERS" ); - break; - } - case ICN::BUTTON_6_PLAYERS: { - text = gettext_noop( "6 PLAYERS" ); - break; - } - case ICN::BUTTON_NEW_MAP: { - text = gettext_noop( "NEW\nMAP" ); - break; - } - case ICN::BUTTON_LOAD_MAP: { - text = gettext_noop( "LOAD\nMAP" ); - break; - } - default: - break; - } - - text = fheroes2::getSupportedText( text, fheroes2::FontType::buttonReleasedWhite() ); - fheroes2::makeButtonSprites( _icnVsSprite[id][0], _icnVsSprite[id][1], text, { 117, 56 }, false, ICN::STONEBAK ); - - fheroes2::Sprite & released = _icnVsSprite[id][0]; - fheroes2::Sprite & pressed = _icnVsSprite[id][1]; - // Add original shadow. - const fheroes2::Sprite & originalShadow = fheroes2::AGG::GetICN( ICN::BTNCOM, 0 ); - fheroes2::Image temp( originalShadow.width(), originalShadow.height() ); - - Copy( released, 0, 0, temp, 5, 0, released.width(), released.height() ); - fheroes2::Copy( originalShadow, 0, 0, temp, 0, 0, 5, originalShadow.height() ); - fheroes2::Copy( originalShadow, 5, originalShadow.height() - 6, temp, 5, originalShadow.height() - 6, originalShadow.width() - 5, 6 ); - fheroes2::Copy( temp, released ); - - Copy( pressed, 0, 0, temp, 5, 0, released.width(), released.height() ); - fheroes2::Copy( originalShadow, 0, 0, temp, 0, 0, 5, originalShadow.height() ); - fheroes2::Copy( originalShadow, 5, originalShadow.height() - 6, temp, 5, originalShadow.height() - 6, originalShadow.width() - 5, 6 ); - fheroes2::Copy( temp, pressed ); - + const fheroes2::FontType buttonFontType = fheroes2::FontType::buttonReleasedWhite(); + const std::vector texts = { fheroes2::getSupportedText( gettext_noop( "NEW\nMAP" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "LOAD\nMAP" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "MAIN\nMENU" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "editorMainMenu|BACK" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "newMap|FROM\nSCRATCH" ), buttonFontType ), + fheroes2::getSupportedText( gettext_noop( "newMap|RANDOM" ), buttonFontType ), + "36 X 36", + "72 X 72", + "108 X 108", + "144 X 144" }; + + fheroes2::makeSymmetricBackgroundSprites( _icnVsSprite[id], texts, false, 80 ); break; } case ICN::GOOD_CAMPAIGN_BUTTONS: @@ -1805,26 +1712,6 @@ namespace bool generateGermanSpecificImages( const int id ) { switch ( id ) { - case ICN::BUTTON_BATTLE_ONLY: - _icnVsSprite[id].resize( 2 ); - for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { - fheroes2::Sprite & out = _icnVsSprite[id][i]; - out = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 6 + i ); - // Clean the button - Fill( out, 26 - i, 23 + i, 84, 11, getButtonFillingColor( i == 0 ) ); - // Add 'K' - Copy( fheroes2::AGG::GetICN( ICN::BTNDCCFG, 4 + i ), 34 - i, 23, out, 40 - i, 23, 12, 14 ); - //'Add 'A' - Copy( fheroes2::AGG::GetICN( ICN::BTNNEWGM, 4 + i ), 56 - i, 23, out, 52 - i, 23, 13, 14 ); - Copy( out, 20, 20, out, 52 - i + 12, 25, 3, 3 ); - // Add 'M' - Copy( fheroes2::AGG::GetICN( ICN::BTNNEWGM, 4 + i ), 39 - i, 8, out, 65 - i, 23, 14, 14 ); - // Add 'F' - Copy( fheroes2::AGG::GetICN( ICN::BTNDCCFG, 4 + i ), 70 - i, 23, out, 87 - i, 23, 10, 14 ); - // Add 'P' - Copy( fheroes2::AGG::GetICN( ICN::BTNNEWGM, 4 + i ), 36 - i, 23, out, 78 - i, 23, 10, 14 ); - } - return true; case ICN::BUTTON_SMALL_DECLINE_GOOD: case ICN::BUTTON_SMALL_ACCEPT_GOOD: { _icnVsSprite[id].resize( 2 ); @@ -1870,40 +1757,72 @@ namespace bool generateFrenchSpecificImages( const int id ) { switch ( id ) { - case ICN::BUTTON_BATTLE_ONLY: { - _icnVsSprite[id].resize( 2 ); - for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { - fheroes2::Sprite & out = _icnVsSprite[id][i]; + case ICN::BUTTONS_NEW_GAME_MENU_GOOD: { + const bool isPoLPresent = !::AGG::getDataFromAggFile( ICN::getIcnFileName( ICN::X_TRACK1 ), false ).empty(); + if ( isPoLPresent ) { + _icnVsSprite[id].resize( 28 ); + } + else { + _icnVsSprite[id].resize( 24 ); + } + + for ( size_t i = 0; i < 3; ++i ) { + _icnVsSprite[id][i * 2] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, static_cast( i * 2 ) ); + _icnVsSprite[id][i * 2 + 1] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, static_cast( i * 2 + 1 ) ); + } + // Add battle only button. + for ( int32_t i = 0; i < 2; ++i ) { + fheroes2::Sprite & out = _icnVsSprite[id][6 + i]; out = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 6 + i ); // Clean the button - Fill( out, 32 - i, 21 + i, 77, 14, getButtonFillingColor( i == 0 ) ); + Fill( out, 27 - i, 21 + i, 77, 14, getButtonFillingColor( i == 0 ) ); const int32_t secondLine = 28; // Add 'MODE' - Copy( fheroes2::AGG::GetICN( ICN::BTNNEWGM, 4 + i ), 40 - i, 13, out, 45 - i, 13, 50, 15 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNNEWGM, 4 + i ), 35 - i, 13, out, 40 - i, 13, 50, 15 ); // Clean up 'MODE' - Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 114 - i, 18, out, 94 - i, 18, 1, 10 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 109 - i, 18, out, 89 - i, 18, 1, 10 ); // Add 'BA' - Copy( fheroes2::AGG::GetICN( ICN::BTNBAUD, 2 + i ), 42 - i, 28, out, 28 - i, secondLine, 22, 15 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNBAUD, 2 + i ), 42 - i, 28, out, 23 - i, secondLine, 22, 15 ); // Clean up 'BA' - Copy( fheroes2::AGG::GetICN( ICN::BTNBAUD, 2 + i ), 42 - i, 31, out, 39 - i, secondLine, 1, 1 ); - Copy( fheroes2::AGG::GetICN( ICN::BTNBAUD, 2 + i ), 39 - i, 31, out, 49 - i, secondLine + 4, 1, 2 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNBAUD, 2 + i ), 42 - i, 31, out, 34 - i, secondLine, 1, 1 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNBAUD, 2 + i ), 39 - i, 31, out, 44 - i, secondLine + 4, 1, 2 ); // Add 'T' - Copy( fheroes2::AGG::GetICN( ICN::BTNDC, 2 + i ), 89 - i, 21, out, 49 - i, secondLine, 12, 15 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNDC, 2 + i ), 89 - i, 21, out, 44 - i, secondLine, 12, 15 ); // Clean up 'AT' - Copy( fheroes2::AGG::GetICN( ICN::BTNDC, 2 + i ), 89 - i, 18, out, 50 - i, secondLine, 1, 1 ); - Copy( fheroes2::AGG::GetICN( ICN::BTNDC, 2 + i ), 92 - ( 5 * i ), 27 - i, out, 49 - i, secondLine + 4 + i, 1, 3 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNDC, 2 + i ), 89 - i, 18, out, 45 - i, secondLine, 1, 1 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNDC, 2 + i ), 92 - ( 5 * i ), 27 - i, out, 44 - i, secondLine + 4 + i, 1, 3 ); // Add 'AI'. - Copy( fheroes2::AGG::GetICN( ICN::BTNMP, 6 + i ), 56 - i, 13, out, 62 - i, secondLine, 18, 15 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNMP, 6 + i ), 51 - i, 13, out, 57 - i, secondLine, 18, 15 ); // Clean up 'TA' - Copy( fheroes2::AGG::GetICN( ICN::BTNBAUD, 2 + i ), 51 - i, 40, out, 60 - i, secondLine + 12, 3, 3 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNBAUD, 2 + i ), 51 - i, 40, out, 55 - i, secondLine + 12, 3, 3 ); // Add 'LLE' - Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 85 - i, 13, out, 81 - i, secondLine, 31, 15 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 80 - i, 13, out, 76 - i, secondLine, 31, 15 ); // Clean up "IL" - Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 85 - i, 18, out, 81 - i, secondLine + 7, 1, 1 ); - Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 94 - i, 17, out, 80 - i, secondLine + 4, 2, 2 ); - Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 93 - i, 25, out, 79 - i, secondLine + 12, 3, 3 ); - Copy( fheroes2::AGG::GetICN( ICN::BTNDC, 4 + i ), 23 - i, 8, out, 79 - i, secondLine + 5, 1, 10 ); - Copy( fheroes2::AGG::GetICN( ICN::BTNMP, 6 + i ), 73 - i, 22, out, 79 - i, secondLine + 9, 1, 1 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 80 - i, 18, out, 76 - i, secondLine + 7, 1, 1 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 89 - i, 17, out, 75 - i, secondLine + 4, 2, 2 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 88 - i, 25, out, 74 - i, secondLine + 12, 3, 3 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNDC, 4 + i ), 23 - i, 8, out, 74 - i, secondLine + 5, 1, 10 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNMP, 6 + i ), 68 - i, 16, out, 74 - i, secondLine + 9, 1, 1 ); + } + // Add config button. + _icnVsSprite[id][8] = fheroes2::AGG::GetICN( ICN::BTNDCCFG, 4 ); + _icnVsSprite[id][9] = fheroes2::AGG::GetICN( ICN::BTNDCCFG, 5 ); + // Add cancel button. + _icnVsSprite[id][10] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 6 ); + _icnVsSprite[id][11] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 7 ); + // Add hot seat button. + _icnVsSprite[id][12] = fheroes2::AGG::GetICN( ICN::BTNMP, 0 ); + _icnVsSprite[id][13] = fheroes2::AGG::GetICN( ICN::BTNMP, 1 ); + // Add player count buttons. + for ( size_t i = 0; i < 5; ++i ) { + _icnVsSprite[id][( i + 7 ) * 2] = fheroes2::AGG::GetICN( ICN::BTNHOTST, static_cast( i * 2 ) ); + _icnVsSprite[id][( i + 7 ) * 2 + 1] = fheroes2::AGG::GetICN( ICN::BTNHOTST, static_cast( i * 2 + 1 ) ); + } + if ( isPoLPresent ) { + _icnVsSprite[id][24] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 0 ); + _icnVsSprite[id][25] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 1 ); + _icnVsSprite[id][26] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 2 ); + _icnVsSprite[id][27] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 3 ); } return true; } @@ -2046,7 +1965,7 @@ namespace // Doing a palette swap fixes black pixels in the lower right corner of the original button backgrounds. _icnVsSprite[id].resize( 2 ); const int goodButtonIcnID = id == ICN::BUTTON_SMALL_OKAY_EVIL ? ICN::BUTTON_SMALL_OKAY_GOOD : ICN::BUTTON_SMALL_CANCEL_GOOD; - convertToEvilButtonBackground( _icnVsSprite[id][0], _icnVsSprite[id][1], goodButtonIcnID ); + convertToEvilButtonBackground( _icnVsSprite[id][0], _icnVsSprite[id][1], goodButtonIcnID, 0 ); return true; } default: @@ -2058,23 +1977,55 @@ namespace bool generatePolishSpecificImages( const int id ) { switch ( id ) { - case ICN::BUTTON_BATTLE_ONLY: { - _icnVsSprite[id].resize( 2 ); - for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { - fheroes2::Sprite & out = _icnVsSprite[id][i]; + case ICN::BUTTONS_NEW_GAME_MENU_GOOD: { + const bool isPoLPresent = !::AGG::getDataFromAggFile( ICN::getIcnFileName( ICN::X_TRACK1 ), false ).empty(); + if ( isPoLPresent ) { + _icnVsSprite[id].resize( 28 ); + } + else { + _icnVsSprite[id].resize( 24 ); + } + + for ( size_t i = 0; i < 3; ++i ) { + _icnVsSprite[id][i * 2] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, static_cast( i * 2 ) ); + _icnVsSprite[id][i * 2 + 1] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, static_cast( i * 2 + 1 ) ); + } + // Add battle only button. + for ( int32_t i = 0; i < 2; ++i ) { + fheroes2::Sprite & out = _icnVsSprite[id][6 + i]; out = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 6 + i ); // clean the button - Fill( out, 41 - i, 23 + i, 57, 11, getButtonFillingColor( i == 0 ) ); - const int32_t offsetX = 46; + Fill( out, 36 - i, 23 + i, 57, 11, getButtonFillingColor( i == 0 ) ); + const int32_t offsetX = 41; const int32_t offsetY = 23; // Add 'BI' Copy( fheroes2::AGG::GetICN( ICN::BTNMCFG, 2 + i ), 58 - i, 29, out, offsetX - i, offsetY, 14, 11 ); // Add 'T' - Copy( fheroes2::AGG::GetICN( ICN::BTNNEWGM, 0 + i ), 24 - i, 29, out, offsetX + 14 - i, offsetY, 9, 11 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNNEWGM, 0 + i ), 19 - i, 29, out, offsetX + 14 - i, offsetY, 9, 11 ); // Add 'WA' - Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 45 - i, 23, out, offsetX + 23 - i, offsetY, 24, 11 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 40 - i, 23, out, offsetX + 23 - i, offsetY, 24, 11 ); // Add pixel to 'W' - Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 47 - i, 23 + i, out, offsetX + 38 - i, offsetY + i, 1, 1 ); + Copy( fheroes2::AGG::GetICN( ICN::BTNEMAIN, 0 + i ), 42 - i, 23 + i, out, offsetX + 38 - i, offsetY + i, 1, 1 ); + } + // Add config button. + _icnVsSprite[id][8] = fheroes2::AGG::GetICN( ICN::BTNDCCFG, 4 ); + _icnVsSprite[id][9] = fheroes2::AGG::GetICN( ICN::BTNDCCFG, 5 ); + // Add cancel button. + _icnVsSprite[id][10] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 6 ); + _icnVsSprite[id][11] = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 7 ); + // Add hot seat button. + _icnVsSprite[id][12] = fheroes2::AGG::GetICN( ICN::BTNMP, 0 ); + _icnVsSprite[id][13] = fheroes2::AGG::GetICN( ICN::BTNMP, 1 ); + // Add player count buttons. + for ( size_t i = 0; i < 5; ++i ) { + _icnVsSprite[id][( i + 7 ) * 2] = fheroes2::AGG::GetICN( ICN::BTNHOTST, static_cast( i * 2 ) ); + _icnVsSprite[id][( i + 7 ) * 2 + 1] = fheroes2::AGG::GetICN( ICN::BTNHOTST, static_cast( i * 2 + 1 ) ); + } + if ( isPoLPresent ) { + _icnVsSprite[id][24] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 0 ); + _icnVsSprite[id][25] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 1 ); + _icnVsSprite[id][26] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 2 ); + _icnVsSprite[id][27] = fheroes2::AGG::GetICN( ICN::X_LOADCM, 3 ); } return true; } @@ -2498,6 +2449,50 @@ namespace ApplyPalette( out, indexes ); } return true; + case ICN::BTNDCCFG: + case ICN::BTNEMAIN: + case ICN::BTNESIZE: + case ICN::BTNHOTST: + case ICN::BTNMP: + case ICN::BTNNEWGM: + case ICN::X_LOADCM: { + LoadOriginalICN( id ); + // Remove embedded shadows and backgrounds because we generate our own. We can safely divide by two because every button has 2 states. + for ( size_t i = 0; i < _icnVsSprite[id].size() / 2; ++i ) { + fheroes2::Sprite & released = _icnVsSprite[id][i * 2]; + fheroes2::Sprite & pressed = _icnVsSprite[id][i * 2 + 1]; + + fheroes2::Sprite tempReleased; + fheroes2::Sprite tempPressed; + + const int32_t removedShadowOffsetWidth = 5; + const int32_t removedShadowOffsetHeight = 6; + + tempReleased.resize( released.width() - removedShadowOffsetWidth, released.height() - removedShadowOffsetHeight ); + tempPressed.resize( pressed.width() - removedShadowOffsetWidth, pressed.height() - removedShadowOffsetHeight ); + tempReleased.reset(); + tempPressed.reset(); + + fheroes2::Copy( released, removedShadowOffsetWidth, 0, tempReleased, 0, 0, released.width() - removedShadowOffsetWidth, + released.height() - removedShadowOffsetHeight ); + fheroes2::Copy( pressed, removedShadowOffsetWidth, 0, tempPressed, 0, 0, pressed.width() - removedShadowOffsetWidth, + pressed.height() - removedShadowOffsetHeight ); + released = tempReleased; + pressed = tempPressed; + + setButtonCornersTransparent( released ); + } + if ( id == ICN::BTNNEWGM ) { + // Fix the disabled state for campaign button. + fheroes2::Image common = fheroes2::ExtractCommonPattern( { &_icnVsSprite[id][2], &_icnVsSprite[id][3] } ); + common = fheroes2::FilterOnePixelNoise( common ); + common = fheroes2::FilterOnePixelNoise( common ); + common = fheroes2::FilterOnePixelNoise( common ); + fheroes2::Blit( common, _icnVsSprite[id][2] ); + } + + return true; + } case ICN::GOLEM: case ICN::GOLEM2: LoadOriginalICN( id ); @@ -2583,6 +2578,22 @@ namespace } } return true; + case ICN::EVIL_DIALOG_PLAIN_CORNERS: { + _icnVsSprite[id].resize( 1 ); + + fheroes2::Sprite & cornerSprite = _icnVsSprite[id][0]; + const int32_t cornerSideLength = 43; + cornerSprite.resize( cornerSideLength * 2, cornerSideLength * 2 ); + const fheroes2::Sprite & originalGoodDialog = fheroes2::AGG::GetICN( ICN::WINLOSE, 0 ); + Copy( originalGoodDialog, 0, 0, cornerSprite, 0, 0, cornerSideLength, cornerSideLength ); + Copy( originalGoodDialog, originalGoodDialog.width() - cornerSideLength, 0, cornerSprite, cornerSideLength, 0, cornerSideLength, cornerSideLength ); + Copy( originalGoodDialog, 0, originalGoodDialog.height() - cornerSideLength, cornerSprite, 0, cornerSideLength, cornerSideLength, cornerSideLength ); + Copy( originalGoodDialog, originalGoodDialog.width() - cornerSideLength, originalGoodDialog.height() - cornerSideLength, cornerSprite, cornerSideLength, + cornerSideLength, cornerSideLength, cornerSideLength ); + fheroes2::ApplyPalette( cornerSprite, PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ) ); + + return true; + } case ICN::MONS32: LoadOriginalICN( id ); diff --git a/src/fheroes2/agg/icn.h b/src/fheroes2/agg/icn.h index e202cfdc63..78ce0b7a33 100644 --- a/src/fheroes2/agg/icn.h +++ b/src/fheroes2/agg/icn.h @@ -911,7 +911,6 @@ namespace ICN YELLOW_FONT, YELLOW_SMALLFONT, BUTTON_WELL_MAX, - BUTTON_BATTLE_ONLY, BUTTON_GIFT_GOOD, BUTTON_GIFT_EVIL, CSLMARKER, @@ -1007,22 +1006,10 @@ namespace ICN EMPTY_INTERFACE_BUTTON_GOOD, EMPTY_INTERFACE_BUTTON_EVIL, - BUTTON_STANDARD_GAME, - BUTTON_CAMPAIGN_GAME, - BUTTON_MULTIPLAYER_GAME, - BUTTON_LARGE_CANCEL, - BUTTON_LARGE_CONFIG, - BUTTON_ORIGINAL_CAMPAIGN, - BUTTON_EXPANSION_CAMPAIGN, - BUTTON_HOT_SEAT, - BUTTON_2_PLAYERS, - BUTTON_3_PLAYERS, - BUTTON_4_PLAYERS, - BUTTON_5_PLAYERS, - BUTTON_6_PLAYERS, - - BUTTON_NEW_MAP, - BUTTON_LOAD_MAP, + BUTTONS_NEW_GAME_MENU_GOOD, + BUTTONS_NEW_GAME_MENU_EVIL, + BUTTONS_EDITOR_MENU_GOOD, + BUTTONS_EDITOR_MENU_EVIL, BUTTONS_FILE_DIALOG_GOOD, BUTTONS_FILE_DIALOG_EVIL, @@ -1111,6 +1098,8 @@ namespace ICN SCENIBKG_EVIL, + EVIL_DIALOG_PLAIN_CORNERS, + // IMPORTANT! Put any new entry just above this one. LASTICN }; diff --git a/src/fheroes2/battle/battle_interface.cpp b/src/fheroes2/battle/battle_interface.cpp index 8c01638940..35ebe5a7bd 100644 --- a/src/fheroes2/battle/battle_interface.cpp +++ b/src/fheroes2/battle/battle_interface.cpp @@ -3256,6 +3256,8 @@ void Battle::Interface::OpenAutoModeDialog( const Unit & unit, Actions & actions fheroes2::StandardWindow background( autoButtons, false, titleYOffset + title.height() ); + background.renderSymmetricButtons( autoButtons, titleYOffset + title.height(), false ); + fheroes2::Button buttonCancel; const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); diff --git a/src/fheroes2/dialog/dialog_file.cpp b/src/fheroes2/dialog/dialog_file.cpp index 5704cadaa9..ad3f476b9d 100644 --- a/src/fheroes2/dialog/dialog_file.cpp +++ b/src/fheroes2/dialog/dialog_file.cpp @@ -48,6 +48,7 @@ namespace const int bigButtonsICN = isEvilInterface ? ICN::BUTTONS_FILE_DIALOG_EVIL : ICN::BUTTONS_FILE_DIALOG_GOOD; fheroes2::ButtonGroup optionButtons( bigButtonsICN ); fheroes2::StandardWindow background( optionButtons, false, 0, display ); + background.renderSymmetricButtons( optionButtons, 0, false ); fheroes2::ButtonBase & newGameButton = optionButtons.button( 0 ); fheroes2::ButtonBase & loadGameButton = optionButtons.button( 1 ); @@ -67,10 +68,7 @@ namespace // dialog menu loop while ( le.HandleEvents() ) { - newGameButton.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( newGameButton.area() ) ); - loadGameButton.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( loadGameButton.area() ) ); - saveGameButton.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( saveGameButton.area() ) ); - quitButton.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( quitButton.area() ) ); + optionButtons.drawOnState( le ); buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); if ( le.MouseClickLeft( newGameButton.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_NEW_GAME ) ) { diff --git a/src/fheroes2/editor/editor_mainmenu.cpp b/src/fheroes2/editor/editor_mainmenu.cpp index cc335e4f6c..1bb49144de 100644 --- a/src/fheroes2/editor/editor_mainmenu.cpp +++ b/src/fheroes2/editor/editor_mainmenu.cpp @@ -37,21 +37,23 @@ #include "game_mainmenu_ui.h" #include "game_mode.h" #include "icn.h" +#include "image.h" #include "localevent.h" #include "logging.h" #include "maps.h" #include "maps_fileinfo.h" -#include "math_base.h" #include "mus.h" +#include "screen.h" +#include "settings.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_tool.h" +#include "ui_window.h" namespace { - const int32_t buttonYStep = 66; const size_t mapSizeCount = 4; const std::array mapSizeHotkeys = { Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_SMALL, Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_MEDIUM, Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_LARGE, Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_EXTRA_LARGE }; @@ -97,201 +99,232 @@ namespace COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to go back to New Map menu." ) } - void showWIPInfo() + fheroes2::GameMode editNewMapFromScratch( const Maps::MapSize & mapSize ) { - fheroes2::showStandardTextMessage( _( "Warning" ), "The Map Editor is still in development. This function is not available yet.", Dialog::OK ); + fheroes2::fadeOutDisplay(); + Game::setDisplayFadeIn(); + + Interface::EditorInterface & editorInterface = Interface::EditorInterface::Get(); + if ( editorInterface.generateNewMap( mapSize ) ) { + return editorInterface.startEdit(); + } + return fheroes2::GameMode::EDITOR_NEW_MAP; } +} - Maps::MapSize selectMapSize() +namespace Editor +{ + fheroes2::GameMode menuMain( const bool straightToSelectMapSize ) { - outputEditorMapSizeMenuInTextSupportMode(); + // Stop all sounds, but not the music + AudioManager::stopSounds(); + AudioManager::PlayMusicAsync( MUS::MAINMENU, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); + + // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawEditorMainMenuScreen(); - const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); - - std::array buttons; - - for ( uint32_t i = 0; i < mapSizeCount; ++i ) { - buttons[i].setICNInfo( ICN::BTNESIZE, 0 + i * 2, 1 + i * 2 ); - buttons[i].setPosition( buttonPos.x, buttonPos.y + buttonYStep * static_cast( i ) ); - buttons[i].draw(); + // Setup main dialog buttons. + const int menuButtonsIcnIndex = Settings::Get().isEvilInterfaceEnabled() ? ICN::BUTTONS_EDITOR_MENU_EVIL : ICN::BUTTONS_EDITOR_MENU_GOOD; + fheroes2::ButtonGroup mainModeButtons; + // Only add the buttons needed for the initial state of the dialog. + for ( int32_t i = 0; i < 2; ++i ) { + mainModeButtons.createButton( 0, 0, menuButtonsIcnIndex, i * 2, i * 2 + 1, i ); } - fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); - - buttonCancel.draw(); + const fheroes2::ButtonBase & buttonNewMap = mainModeButtons.button( 0 ); + const fheroes2::ButtonBase & buttonLoadMap = mainModeButtons.button( 1 ); - fheroes2::validateFadeInAndRender(); + // Generate dialog background with extra space for cancel button and empty space for 3 buttons to match the original dialog's height. + const int32_t spaceBetweenButtons = 10; + fheroes2::StandardWindow background( mainModeButtons, true, ( buttonNewMap.area().height + spaceBetweenButtons ) * 4 ); - LocalEvent & le = LocalEvent::Get(); + background.applyGemDecoratedCorners(); - while ( le.HandleEvents() ) { - for ( size_t i = 0; i < mapSizeCount; ++i ) { - buttons[i].drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttons[i].area() ) ); + fheroes2::Display & display = fheroes2::Display::instance(); - if ( le.MouseClickLeft( buttons[i].area() ) || Game::HotKeyPressEvent( mapSizeHotkeys[i] ) ) { - return mapSizes[i]; - } + fheroes2::ImageRestorer emptyDialog( display, background.activeArea().x, background.activeArea().y, background.activeArea().width, + background.activeArea().height ); - if ( le.isMouseRightButtonPressedInArea( buttons[i].area() ) ) { - std::string mapSize = std::to_string( mapSizes[i] ); - std::string message = _( "Create a map that is %{size} squares wide and %{size} squares high." ); - StringReplace( message, "%{size}", mapSize ); - mapSize += " x " + mapSize; - fheroes2::showStandardTextMessage( mapSize, message, Dialog::ZERO ); - } - } + background.renderSymmetricButtons( mainModeButtons, 0, true ); - buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); + fheroes2::Button buttonMainMenu( buttonNewMap.area().x, + background.activeArea().y * 2 + background.activeArea().height - buttonNewMap.area().y - buttonNewMap.area().height, + menuButtonsIcnIndex, 4, 5 ); - if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { - return Maps::ZERO; - } + fheroes2::Button buttonBack( buttonMainMenu.area().x, buttonMainMenu.area().y, menuButtonsIcnIndex, 6, 7 ); - if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { - fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the New Map menu." ), Dialog::ZERO ); - } + // Add From Scratch and Random buttons. Currently unused until Random map generator has been implemented. + fheroes2::ButtonGroup mapCreationModeButtons; + for ( int32_t i = 0; i < 2; ++i ) { + mapCreationModeButtons.createButton( buttonNewMap.area().x, buttonNewMap.area().y + i * ( buttonNewMap.area().height + spaceBetweenButtons ), + menuButtonsIcnIndex, ( i + 4 ) * 2, ( i + 4 ) * 2 + 1, i ); } + mapCreationModeButtons.disable(); - return Maps::ZERO; - } -} + const fheroes2::ButtonBase & buttonScratchMap = mapCreationModeButtons.button( 0 ); + const fheroes2::ButtonBase & buttonRandomMap = mapCreationModeButtons.button( 1 ); -namespace Editor -{ - fheroes2::GameMode menuMain() - { - outputEditorMainMenuInTextSupportMode(); - - // Stop all sounds, but not the music - AudioManager::stopSounds(); + // Add map size buttons + fheroes2::ButtonGroup mapSizeButtons; + for ( int32_t i = 0; i < static_cast( mapSizeCount ); ++i ) { + mapSizeButtons.createButton( buttonNewMap.area().x, buttonNewMap.area().y + i * ( buttonNewMap.area().height + spaceBetweenButtons ), menuButtonsIcnIndex, + ( i + 6 ) * 2, ( i + 6 ) * 2 + 1, i ); + } - AudioManager::PlayMusicAsync( MUS::MAINMENU, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); + // TODO: Change to From Scratch and Random buttons when random map generator has been implemented. + if ( !straightToSelectMapSize ) { + outputEditorMainMenuInTextSupportMode(); - // setup cursor - const CursorRestorer cursorRestorer( true, Cursor::POINTER ); + mapSizeButtons.disable(); + buttonBack.disable(); - fheroes2::drawEditorMainMenuScreen(); + buttonMainMenu.draw(); + buttonMainMenu.drawShadow( display ); + } + else { + outputEditorMapSizeMenuInTextSupportMode(); - const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); + mainModeButtons.disable(); + buttonMainMenu.disable(); + emptyDialog.restore(); - fheroes2::Button buttonNewMap( buttonPos.x, buttonPos.y, ICN::BUTTON_NEW_MAP, 0, 1 ); - fheroes2::Button buttonLoadMap( buttonPos.x, buttonPos.y + buttonYStep, ICN::BUTTON_LOAD_MAP, 0, 1 ); - fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + mapSizeButtons.draw(); + mapSizeButtons.drawShadows( display ); - buttonNewMap.draw(); - buttonLoadMap.draw(); - buttonCancel.draw(); + buttonBack.draw(); + buttonBack.drawShadow( display ); + } fheroes2::validateFadeInAndRender(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - buttonNewMap.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonNewMap.area() ) ); - buttonLoadMap.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonLoadMap.area() ) ); - buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); + if ( buttonNewMap.isEnabled() ) { + mainModeButtons.drawOnState( le ); + buttonMainMenu.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonMainMenu.area() ) ); - if ( le.MouseClickLeft( buttonNewMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_NEW_MAP_MENU ) ) { - return fheroes2::GameMode::EDITOR_NEW_MAP; - } - if ( le.MouseClickLeft( buttonLoadMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_LOAD_MAP_MENU ) ) { - return fheroes2::GameMode::EDITOR_LOAD_MAP; - } - if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { - return fheroes2::GameMode::MAIN_MENU; - } + if ( le.MouseClickLeft( buttonNewMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_NEW_MAP_MENU ) ) { + mainModeButtons.disable(); + buttonMainMenu.disable(); - if ( le.isMouseRightButtonPressedInArea( buttonNewMap.area() ) ) { - // TODO: update this text once random map generator is ready. - // The original text should be "Create a new map, either from scratch or using the random map generator." - fheroes2::showStandardTextMessage( _( "New Map" ), _( "Create a new map from scratch." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonLoadMap.area() ) ) { - fheroes2::showStandardTextMessage( _( "Load Map" ), _( "Load an existing map." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { - fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); - } - } + emptyDialog.restore(); + // TODO: Change to mapCreationModeButtons when Random map generator has been implemented. + mapSizeButtons.enable(); + mapSizeButtons.draw(); + mapSizeButtons.drawShadows( display ); - return fheroes2::GameMode::MAIN_MENU; - } + buttonBack.enable(); + buttonBack.draw(); + buttonBack.drawShadow( display ); - fheroes2::GameMode menuNewMap() - { - outputEditorNewMapMenuInTextSupportMode(); + display.render( background.activeArea() ); - const CursorRestorer cursorRestorer( true, Cursor::POINTER ); + // TODO: Change to outputEditorNewMapMenuInTextSupportMode() when Random map generator has been implemented. + outputEditorMapSizeMenuInTextSupportMode(); - fheroes2::drawEditorMainMenuScreen(); + continue; + } + if ( le.MouseClickLeft( buttonLoadMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_LOAD_MAP_MENU ) ) { + return fheroes2::GameMode::EDITOR_LOAD_MAP; + } + if ( le.MouseClickLeft( buttonMainMenu.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { + return fheroes2::GameMode::MAIN_MENU; + } - const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); + if ( le.isMouseRightButtonPressedInArea( buttonNewMap.area() ) ) { + // TODO: update this text once random map generator is ready. + // The original text should be "Create a new map, either from scratch or using the random map generator." + fheroes2::showStandardTextMessage( _( "New Map" ), _( "Create a new map from scratch." ), Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( buttonLoadMap.area() ) ) { + fheroes2::showStandardTextMessage( _( "Load Map" ), _( "Load an existing map." ), Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( buttonMainMenu.area() ) ) { + fheroes2::showStandardTextMessage( _( "Main Menu" ), _( "Exit the Editor and return to the game's Main Menu." ), Dialog::ZERO ); + } + } + else if ( mapCreationModeButtons.button( 0 ).isEnabled() ) { + mapCreationModeButtons.drawOnState( le ); + buttonBack.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonBack.area() ) ); - fheroes2::Button buttonScratchMap( buttonPos.x, buttonPos.y, ICN::BTNENEW, 0, 1 ); - fheroes2::Button buttonRandomMap( buttonPos.x, buttonPos.y + buttonYStep, ICN::BTNENEW, 2, 3 ); - fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + // TODO: Remove this call once random map generator has been added. This serves only to silence clang check for unused functions. + outputEditorNewMapMenuInTextSupportMode(); - // TODO: enable it back once random map generator is ready. - buttonRandomMap.disable(); + if ( le.MouseClickLeft( buttonScratchMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_FROM_SCRATCH_MAP_MENU ) ) { + mapCreationModeButtons.disable(); + emptyDialog.restore(); - buttonScratchMap.draw(); - buttonRandomMap.draw(); - buttonCancel.draw(); + mapSizeButtons.enable(); + mapSizeButtons.draw(); + mapSizeButtons.drawShadows( display ); - fheroes2::validateFadeInAndRender(); + display.render( background.activeArea() ); - LocalEvent & le = LocalEvent::Get(); + outputEditorMapSizeMenuInTextSupportMode(); + } - while ( le.HandleEvents() ) { - buttonScratchMap.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonScratchMap.area() ) ); + // TODO: Add buttonRandomMap left click event here once random map generator is added. - if ( buttonRandomMap.isEnabled() ) { - buttonRandomMap.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonRandomMap.area() ) ); + if ( le.isMouseRightButtonPressedInArea( buttonScratchMap.area() ) ) { + fheroes2::showStandardTextMessage( _( "From Scratch" ), _( "Start from scratch with a blank map." ), Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( buttonRandomMap.area() ) ) { + fheroes2::showStandardTextMessage( _( "Random" ), _( "Create a randomly generated map." ), Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( buttonBack.area() ) ) { + fheroes2::showStandardTextMessage( _( "Back" ), _( "Return to the Editor's main menu options." ), Dialog::ZERO ); + } } + else if ( mapSizeButtons.button( 0 ).isEnabled() ) { + mapSizeButtons.drawOnState( le ); + buttonBack.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonBack.area() ) ); + + // Loop through all map size buttons. + for ( size_t i = 0; i < mapSizeCount; ++i ) { + if ( le.MouseClickLeft( mapSizeButtons.button( i ).area() ) || Game::HotKeyPressEvent( mapSizeHotkeys[i] ) ) { + return editNewMapFromScratch( mapSizes[i] ); + } - buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); - - if ( le.MouseClickLeft( buttonScratchMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_FROM_SCRATCH_MAP_MENU ) ) { - const Maps::MapSize mapSize = selectMapSize(); - if ( mapSize != Maps::ZERO ) { - fheroes2::fadeOutDisplay(); - Game::setDisplayFadeIn(); - - Interface::EditorInterface & editorInterface = Interface::EditorInterface::Get(); - if ( editorInterface.generateNewMap( mapSize ) ) { - return editorInterface.startEdit(); + if ( le.isMouseRightButtonPressedInArea( mapSizeButtons.button( i ).area() ) ) { + std::string mapSize = std::to_string( mapSizes[i] ); + std::string message = _( "Create a map that is %{size} squares wide and %{size} squares high." ); + StringReplace( message, "%{size}", mapSize ); + mapSize += " x " + mapSize; + fheroes2::showStandardTextMessage( mapSize, message, Dialog::ZERO ); } } - return fheroes2::GameMode::EDITOR_NEW_MAP; - } + // TODO: Change this when Random map generator has been implemented. + if ( le.MouseClickLeft( buttonBack.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { + mapSizeButtons.disable(); + buttonBack.disable(); + emptyDialog.restore(); - if ( buttonRandomMap.isEnabled() && ( le.MouseClickLeft( buttonRandomMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_RANDOM_MAP_MENU ) ) ) { - if ( selectMapSize() != Maps::ZERO ) { - showWIPInfo(); - } - return fheroes2::GameMode::EDITOR_NEW_MAP; - } + mainModeButtons.enable(); + mainModeButtons.draw(); + mainModeButtons.drawShadows( display ); - if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { - return fheroes2::GameMode::EDITOR_MAIN_MENU; - } + buttonMainMenu.enable(); + buttonMainMenu.draw(); + buttonMainMenu.drawShadow( display ); - if ( le.isMouseRightButtonPressedInArea( buttonScratchMap.area() ) ) { - fheroes2::showStandardTextMessage( _( "From Scratch" ), _( "Start from scratch with a blank map." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonRandomMap.area() ) ) { - fheroes2::showStandardTextMessage( _( "Random" ), _( "Create a randomly generated map." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { - fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the Map Editor main menu." ), Dialog::ZERO ); + display.render( background.activeArea() ); + + outputEditorMainMenuInTextSupportMode(); + + continue; + } + // TODO: Change this when Random map generator has been implemented. + if ( le.isMouseRightButtonPressedInArea( buttonBack.area() ) ) { + fheroes2::showStandardTextMessage( _( "Back" ), _( "Return to the Editor's main menu options." ), Dialog::ZERO ); + } } } - return fheroes2::GameMode::EDITOR_MAIN_MENU; + return fheroes2::GameMode::MAIN_MENU; } fheroes2::GameMode menuLoadMap() @@ -323,19 +356,4 @@ namespace Editor return Interface::EditorInterface::Get().startEdit(); } - - fheroes2::GameMode menuNewFromScratchMap() - { - const Maps::MapSize mapSize = selectMapSize(); - if ( mapSize != Maps::ZERO ) { - fheroes2::fadeOutDisplay(); - Game::setDisplayFadeIn(); - - Interface::EditorInterface & editorInterface = Interface::EditorInterface::Get(); - if ( editorInterface.generateNewMap( mapSize ) ) { - return editorInterface.startEdit(); - } - } - return fheroes2::GameMode::EDITOR_MAIN_MENU; - } } diff --git a/src/fheroes2/editor/editor_mainmenu.h b/src/fheroes2/editor/editor_mainmenu.h index e20592f6b3..ca34bb94d9 100644 --- a/src/fheroes2/editor/editor_mainmenu.h +++ b/src/fheroes2/editor/editor_mainmenu.h @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2023 - 2024 * + * Copyright (C) 2023 - 2025 * * * * 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 * @@ -24,10 +24,7 @@ namespace Editor { - fheroes2::GameMode menuMain(); - fheroes2::GameMode menuNewMap(); + // TODO: The bool needs to be changed when random map generation is implemented. + fheroes2::GameMode menuMain( const bool straightToSelectMapSize ); fheroes2::GameMode menuLoadMap(); - - // TODO: this is a temporary solution before we implement random maps. - fheroes2::GameMode menuNewFromScratchMap(); } diff --git a/src/fheroes2/game/game.h b/src/fheroes2/game/game.h index a5964c9521..11ac7238e1 100644 --- a/src/fheroes2/game/game.h +++ b/src/fheroes2/game/game.h @@ -23,6 +23,7 @@ #pragma once +#include #include #include @@ -59,16 +60,12 @@ namespace Game fheroes2::GameMode LoadGame(); fheroes2::GameMode Credits(); fheroes2::GameMode NewStandard(); - fheroes2::GameMode CampaignSelection(); + fheroes2::GameMode NewHotSeat( const size_t playerCount ); fheroes2::GameMode NewSuccessionWarsCampaign(); fheroes2::GameMode NewPriceOfLoyaltyCampaign(); - fheroes2::GameMode NewMulti(); - fheroes2::GameMode NewHotSeat(); fheroes2::GameMode NewBattleOnly(); - fheroes2::GameMode NewNetwork(); // To be utilized in future. fheroes2::GameMode LoadStandard(); fheroes2::GameMode LoadCampaign(); - fheroes2::GameMode LoadMulti(); fheroes2::GameMode LoadHotseat(); fheroes2::GameMode SelectCampaignScenario( const fheroes2::GameMode prevMode, const bool allowToRestart ); fheroes2::GameMode SelectScenario( const uint8_t humanPlayerCount ); @@ -105,7 +102,6 @@ namespace Game uint32_t getGameOverScoreFactor(); uint32_t GetLostTownDays(); uint32_t GetWhirlpoolPercent(); - uint8_t SelectCountPlayers(); void PlayPickupSound(); diff --git a/src/fheroes2/game/game_loadgame.cpp b/src/fheroes2/game/game_loadgame.cpp index 573b4ce18a..f305b94edd 100644 --- a/src/fheroes2/game/game_loadgame.cpp +++ b/src/fheroes2/game/game_loadgame.cpp @@ -23,7 +23,6 @@ #include "game.h" // IWYU pragma: associated -#include #include #include #include @@ -39,9 +38,9 @@ #include "game_mainmenu_ui.h" #include "game_mode.h" #include "icn.h" +#include "image.h" #include "localevent.h" #include "logging.h" -#include "math_base.h" #include "mus.h" #include "screen.h" #include "settings.h" @@ -49,11 +48,10 @@ #include "ui_button.h" #include "ui_dialog.h" #include "ui_tool.h" +#include "ui_window.h" namespace { - const int32_t buttonYStep = 66; - void outputLoadGameInTextSupportMode() { START_TEXT_SUPPORT_MODE @@ -79,62 +77,6 @@ fheroes2::GameMode Game::LoadHotseat() return DisplayLoadGameDialog(); } -fheroes2::GameMode Game::LoadMulti() -{ - fheroes2::Display & display = fheroes2::Display::instance(); - - // setup cursor - const CursorRestorer cursorRestorer( true, Cursor::POINTER ); - - // image background - fheroes2::drawMainMenuScreen(); - - const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); - - fheroes2::Button buttonHotSeat( buttonPos.x, buttonPos.y, ICN::BUTTON_HOT_SEAT, 0, 1 ); - fheroes2::Button buttonNetwork( buttonPos.x, buttonPos.y + buttonYStep * 1, ICN::BTNMP, 2, 3 ); - fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); - - buttonHotSeat.draw(); - buttonCancel.draw(); - buttonNetwork.disable(); - - display.render(); - - LocalEvent & le = LocalEvent::Get(); - while ( le.HandleEvents() ) { - buttonHotSeat.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonHotSeat.area() ) ); - if ( buttonNetwork.isEnabled() ) { - buttonNetwork.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonNetwork.area() ) ); - } - buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); - - if ( le.MouseClickLeft( buttonHotSeat.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_HOTSEAT ) ) { - if ( ListFiles::IsEmpty( GetSaveDir(), GetSaveFileExtension( Game::TYPE_HOTSEAT ) ) ) { - fheroes2::showStandardTextMessage( _( "Load Game" ), _( "No save files to load." ), Dialog::OK ); - } - else { - return fheroes2::GameMode::LOAD_HOT_SEAT; - } - } - else if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { - return fheroes2::GameMode::LOAD_GAME; - } - - // right info - if ( le.isMouseRightButtonPressedInArea( buttonHotSeat.area() ) ) { - fheroes2::showStandardTextMessage( - _( "Hot Seat" ), _( "Play a Hot Seat game, where 2 to 6 players play around the same computer, switching into the 'Hot Seat' when it is their turn." ), - Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { - fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); - } - } - - return fheroes2::GameMode::LOAD_GAME; -} - fheroes2::GameMode Game::LoadGame() { outputLoadGameInTextSupportMode(); @@ -144,78 +86,119 @@ fheroes2::GameMode Game::LoadGame() AudioManager::PlayMusicAsync( MUS::MAINMENU, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); - // setup cursor + // Setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawMainMenuScreen(); - const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); + fheroes2::ButtonGroup gameModeButtons; + const int menuButtonsIcnIndex = Settings::Get().isEvilInterfaceEnabled() ? ICN::BUTTONS_NEW_GAME_MENU_EVIL : ICN::BUTTONS_NEW_GAME_MENU_GOOD; + for ( int32_t i = 0; i < 3; ++i ) { + gameModeButtons.createButton( 0, 0, menuButtonsIcnIndex, i * 2, i * 2 + 1, i ); + } + + const fheroes2::ButtonBase & buttonStandardGame = gameModeButtons.button( 0 ); + fheroes2::ButtonBase & buttonCampaignGame = gameModeButtons.button( 1 ); + const fheroes2::ButtonBase & buttonMultiplayerGame = gameModeButtons.button( 2 ); + + const int32_t spaceBetweenButtons = 10; - fheroes2::Button buttonStandardGame( 0, 0, ICN::BUTTON_STANDARD_GAME, 0, 1 ); - fheroes2::Button buttonCampaignGame( 0, 0, ICN::BUTTON_CAMPAIGN_GAME, 0, 1 ); - fheroes2::Button buttonMultiplayerGame( 0, 0, ICN::BUTTON_MULTIPLAYER_GAME, 0, 1 ); - fheroes2::Button buttonCancel( 0, 0, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + fheroes2::StandardWindow background( gameModeButtons, true, buttonStandardGame.area().height * 3 + spaceBetweenButtons * 3 ); - const std::array buttons{ &buttonStandardGame, &buttonCampaignGame, &buttonMultiplayerGame, &buttonCancel }; + // Make corners like in the original game. + background.applyGemDecoratedCorners(); + + // We don't need to restore the cancel button area because every state of the dialog has this button. + fheroes2::Display & display = fheroes2::Display::instance(); + fheroes2::ImageRestorer emptyDialog( display, background.activeArea().x, background.activeArea().y, background.activeArea().width, + background.activeArea().height - buttonStandardGame.area().height - spaceBetweenButtons * 2 - 2 ); if ( !isSuccessionWarsCampaignPresent() ) { buttonCampaignGame.disable(); } - static_assert( buttons.size() > 1, "The number of buttons in this dialog cannot be less than 2!" ); - for ( size_t i = 0; i < buttons.size() - 1; ++i ) { - buttons[i]->setPosition( buttonPos.x, buttonPos.y + buttonYStep * static_cast( i ) ); - buttons[i]->draw(); - } + background.renderSymmetricButtons( gameModeButtons, 0, true ); - // following the cancel button in new game - buttonCancel.setPosition( buttonPos.x, buttonPos.y + buttonYStep * 5 ); + // Add the cancel button at the bottom of the dialog. + fheroes2::Button buttonCancel( buttonStandardGame.area().x, + background.activeArea().y * 2 + background.activeArea().height - buttonStandardGame.area().y - buttonStandardGame.area().height, + menuButtonsIcnIndex, 10, 11 ); buttonCancel.draw(); + buttonCancel.drawShadow( display ); + + // Add hot seat button. + fheroes2::Button buttonHotSeat( buttonStandardGame.area().x, buttonStandardGame.area().y, menuButtonsIcnIndex, 12, 13 ); + buttonHotSeat.disable(); fheroes2::validateFadeInAndRender(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - for ( fheroes2::ButtonBase * button : buttons ) { - button->drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( button->area() ) ); - } - - if ( le.MouseClickLeft( buttonStandardGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_STANDARD ) ) { - if ( ListFiles::IsEmpty( GetSaveDir(), GetSaveFileExtension( Game::TYPE_STANDARD ) ) ) { - fheroes2::showStandardTextMessage( _( "Load Game" ), _( "No save files to load." ), Dialog::OK ); + if ( buttonStandardGame.isEnabled() ) { + gameModeButtons.drawOnState( le ); + + if ( le.MouseClickLeft( buttonStandardGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_STANDARD ) ) { + if ( ListFiles::IsEmpty( GetSaveDir(), GetSaveFileExtension( Game::TYPE_STANDARD ) ) ) { + fheroes2::showStandardTextMessage( _( "Load Game" ), _( "No save files to load." ), Dialog::OK ); + } + else { + return fheroes2::GameMode::LOAD_STANDARD; + } } - else { - return fheroes2::GameMode::LOAD_STANDARD; + else if ( buttonCampaignGame.isEnabled() && ( le.MouseClickLeft( buttonCampaignGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_CAMPAIGN ) ) ) { + if ( ListFiles::IsEmpty( GetSaveDir(), GetSaveFileExtension( Game::TYPE_CAMPAIGN ) ) ) { + fheroes2::showStandardTextMessage( _( "Load Game" ), _( "No save files to load." ), Dialog::OK ); + } + else { + return fheroes2::GameMode::LOAD_CAMPAIGN; + } } - } - else if ( buttonCampaignGame.isEnabled() && ( le.MouseClickLeft( buttonCampaignGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_CAMPAIGN ) ) ) { - if ( ListFiles::IsEmpty( GetSaveDir(), GetSaveFileExtension( Game::TYPE_CAMPAIGN ) ) ) { - fheroes2::showStandardTextMessage( _( "Load Game" ), _( "No save files to load." ), Dialog::OK ); + else if ( le.MouseClickLeft( buttonMultiplayerGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_MULTI ) ) { + for ( size_t i = 0; i < 3; ++i ) { + gameModeButtons.button( i ).disable(); + } + emptyDialog.restore(); + buttonHotSeat.enable(); + buttonHotSeat.draw(); + buttonHotSeat.drawShadow( display ); + display.render( emptyDialog.rect() ); } - else { - return fheroes2::GameMode::LOAD_CAMPAIGN; + if ( le.isMouseRightButtonPressedInArea( buttonStandardGame.area() ) ) { + fheroes2::showStandardTextMessage( _( "Standard Game" ), _( "A single player game playing out a single map." ), Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( buttonCampaignGame.area() ) ) { + fheroes2::showStandardTextMessage( _( "Campaign Game" ), _( "A single player game playing through a series of maps." ), Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( buttonMultiplayerGame.area() ) ) { + fheroes2::showStandardTextMessage( _( "Multi-Player Game" ), + _( "A multi-player game, with several human players completing against each other on a single map." ), Dialog::ZERO ); } } - else if ( le.MouseClickLeft( buttonMultiplayerGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_MULTI ) ) { - return fheroes2::GameMode::LOAD_MULTI; + if ( buttonHotSeat.isEnabled() ) { + buttonHotSeat.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonHotSeat.area() ) ); + if ( le.MouseClickLeft( buttonHotSeat.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_HOTSEAT ) ) { + if ( ListFiles::IsEmpty( GetSaveDir(), GetSaveFileExtension( Game::TYPE_HOTSEAT ) ) ) { + fheroes2::showStandardTextMessage( _( "Load Game" ), _( "No save files to load." ), Dialog::OK ); + } + else { + return fheroes2::GameMode::LOAD_HOT_SEAT; + } + } + if ( le.isMouseRightButtonPressedInArea( buttonHotSeat.area() ) ) { + fheroes2::showStandardTextMessage( + _( "Hot Seat" ), + _( "Play a Hot Seat game, where 2 to 6 players play around the same computer, switching into the 'Hot Seat' when it is their turn." ), Dialog::ZERO ); + } } + buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); + if ( le.MouseClickLeft( buttonCancel.area() ) || HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) ) { return fheroes2::GameMode::MAIN_MENU; } - if ( le.isMouseRightButtonPressedInArea( buttonStandardGame.area() ) ) { - fheroes2::showStandardTextMessage( _( "Standard Game" ), _( "A single player game playing out a single map." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCampaignGame.area() ) ) { - fheroes2::showStandardTextMessage( _( "Campaign Game" ), _( "A single player game playing through a series of maps." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonMultiplayerGame.area() ) ) { - fheroes2::showStandardTextMessage( _( "Multi-Player Game" ), - _( "A multi-player game, with several human players completing against each other on a single map." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { + if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } } diff --git a/src/fheroes2/game/game_mainmenu.cpp b/src/fheroes2/game/game_mainmenu.cpp index fc664951a8..acd52df50f 100644 --- a/src/fheroes2/game/game_mainmenu.cpp +++ b/src/fheroes2/game/game_mainmenu.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2019 - 2024 * + * Copyright (C) 2019 - 2025 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * @@ -136,21 +136,12 @@ void Game::mainGameLoop( bool isFirstGameRun, bool isProbablyDemoVersion ) case fheroes2::GameMode::NEW_STANDARD: result = Game::NewStandard(); break; - case fheroes2::GameMode::NEW_CAMPAIGN_SELECTION: - result = Game::CampaignSelection(); - break; case fheroes2::GameMode::NEW_SUCCESSION_WARS_CAMPAIGN: result = Game::NewSuccessionWarsCampaign(); break; case fheroes2::GameMode::NEW_PRICE_OF_LOYALTY_CAMPAIGN: result = Game::NewPriceOfLoyaltyCampaign(); break; - case fheroes2::GameMode::NEW_MULTI: - result = Game::NewMulti(); - break; - case fheroes2::GameMode::NEW_HOT_SEAT: - result = Game::NewHotSeat(); - break; case fheroes2::GameMode::NEW_BATTLE_ONLY: result = Game::NewBattleOnly(); break; @@ -160,9 +151,6 @@ void Game::mainGameLoop( bool isFirstGameRun, bool isProbablyDemoVersion ) case fheroes2::GameMode::LOAD_CAMPAIGN: result = Game::LoadCampaign(); break; - case fheroes2::GameMode::LOAD_MULTI: - result = Game::LoadMulti(); - break; case fheroes2::GameMode::LOAD_HOT_SEAT: result = Game::LoadHotseat(); break; @@ -193,11 +181,14 @@ void Game::mainGameLoop( bool isFirstGameRun, bool isProbablyDemoVersion ) result = Game::SelectCampaignScenario( fheroes2::GameMode::LOAD_CAMPAIGN, false ); } break; + case fheroes2::GameMode::START_BATTLE_ONLY_MODE: + result = Game::StartBattleOnly(); + break; case fheroes2::GameMode::EDITOR_MAIN_MENU: - result = Editor::menuMain(); + result = Editor::menuMain( false ); break; case fheroes2::GameMode::EDITOR_NEW_MAP: - result = Editor::menuNewFromScratchMap(); + result = Editor::menuMain( true ); break; case fheroes2::GameMode::EDITOR_LOAD_MAP: result = Editor::menuLoadMap(); diff --git a/src/fheroes2/game/game_mainmenu_ui.cpp b/src/fheroes2/game/game_mainmenu_ui.cpp index 7ecab5a3e6..0b9114f0e5 100644 --- a/src/fheroes2/game/game_mainmenu_ui.cpp +++ b/src/fheroes2/game/game_mainmenu_ui.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2021 - 2024 * + * Copyright (C) 2021 - 2025 * * * * 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 * @@ -31,7 +31,6 @@ #include "math_base.h" #include "screen.h" #include "settings.h" -#include "ui_constants.h" #include "ui_tool.h" namespace @@ -116,25 +115,6 @@ namespace fheroes2 fillScreenBorders( display, { background.x(), background.y(), background.width(), background.height() } ); } - Point drawButtonPanel() - { - const fheroes2::Sprite & back = fheroes2::AGG::GetICN( ICN::HEROES, 0 ); - const fheroes2::Sprite & panel = fheroes2::AGG::GetICN( ICN::REDBACK, 0 ); - - const int32_t panelOffset = fheroes2::Display::DEFAULT_HEIGHT - panel.height(); - const int32_t panelXPos = back.width() + back.x() - ( panel.width() + panelOffset ); - fheroes2::Blit( panel, fheroes2::Display::instance(), panelXPos, panelOffset + back.y() ); - - const int32_t buttonMiddlePos = panelXPos + fheroes2::shadowWidthPx + ( panel.width() - fheroes2::shadowWidthPx ) / 2; - - const fheroes2::Sprite & buttonSample = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 0 ); - const int32_t buttonWidth = buttonSample.width(); - const int32_t buttonXPos = buttonMiddlePos - buttonWidth / 2 - 3; // 3 is button shadow - const int32_t buttonYPos = 46 + back.y(); - - return { buttonXPos, buttonYPos }; - } - void validateFadeInAndRender() { if ( Game::validateDisplayFadeIn() ) { diff --git a/src/fheroes2/game/game_mainmenu_ui.h b/src/fheroes2/game/game_mainmenu_ui.h index 1bac71c661..6c18268a31 100644 --- a/src/fheroes2/game/game_mainmenu_ui.h +++ b/src/fheroes2/game/game_mainmenu_ui.h @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2021 - 2023 * + * Copyright (C) 2021 - 2025 * * * * 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 * @@ -20,15 +20,11 @@ #pragma once -#include "math_base.h" - namespace fheroes2 { void drawMainMenuScreen(); void drawEditorMainMenuScreen(); - Point drawButtonPanel(); - // If display fade-in state is set reset it to false and fade-in the full display image. Otherwise render full display image without fade-in. void validateFadeInAndRender(); } diff --git a/src/fheroes2/game/game_mode.h b/src/fheroes2/game/game_mode.h index 0da203574f..1f88acdf5e 100644 --- a/src/fheroes2/game/game_mode.h +++ b/src/fheroes2/game/game_mode.h @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2021 - 2024 * + * Copyright (C) 2021 - 2025 * * * * 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 * @@ -33,15 +33,11 @@ namespace fheroes2 HIGHSCORES_CAMPAIGN, CREDITS, NEW_STANDARD, - NEW_CAMPAIGN_SELECTION, NEW_SUCCESSION_WARS_CAMPAIGN, NEW_PRICE_OF_LOYALTY_CAMPAIGN, - NEW_MULTI, - NEW_HOT_SEAT, NEW_BATTLE_ONLY, LOAD_STANDARD, LOAD_CAMPAIGN, - LOAD_MULTI, LOAD_HOT_SEAT, // Do NOT change the order of the below 6 entries! SELECT_SCENARIO_ONE_HUMAN_PLAYER, @@ -51,6 +47,7 @@ namespace fheroes2 SELECT_SCENARIO_FIVE_HUMAN_PLAYERS, SELECT_SCENARIO_SIX_HUMAN_PLAYERS, START_GAME, + START_BATTLE_ONLY_MODE, SAVE_GAME, END_TURN, SELECT_CAMPAIGN_SCENARIO, diff --git a/src/fheroes2/game/game_newgame.cpp b/src/fheroes2/game/game_newgame.cpp index 9e4f907c4f..ebec58b03d 100644 --- a/src/fheroes2/game/game_newgame.cpp +++ b/src/fheroes2/game/game_newgame.cpp @@ -63,10 +63,17 @@ #include "ui_dialog.h" #include "ui_text.h" #include "ui_tool.h" +#include "ui_window.h" namespace { - const int32_t buttonYStep = 66; + const size_t playerCountOptions = 5; + const std::array playerCountHotkeys + = { fheroes2::Key::KEY_2, fheroes2::Key::KEY_3, fheroes2::Key::KEY_4, fheroes2::Key::KEY_5, fheroes2::Key::KEY_6 }; + const std::array playerCountModes + = { fheroes2::GameMode::SELECT_SCENARIO_TWO_HUMAN_PLAYERS, fheroes2::GameMode::SELECT_SCENARIO_THREE_HUMAN_PLAYERS, + fheroes2::GameMode::SELECT_SCENARIO_FOUR_HUMAN_PLAYERS, fheroes2::GameMode::SELECT_SCENARIO_FIVE_HUMAN_PLAYERS, + fheroes2::GameMode::SELECT_SCENARIO_SIX_HUMAN_PLAYERS }; std::unique_ptr getVideo( const std::string & fileName ) { @@ -92,6 +99,7 @@ namespace COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_STANDARD ) << " to choose Standard Game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_CAMPAIGN ) << " to choose Campaign Game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_MULTI ) << " to choose Multiplayer Game." ) + COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_BATTLEONLY ) << " to choose Battle Only Game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_SETTINGS ) << " to open Game Settings." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to come back to Main Menu." ) } @@ -151,94 +159,20 @@ fheroes2::GameMode Game::NewBattleOnly() { Settings & conf = Settings::Get(); conf.SetGameType( Game::TYPE_BATTLEONLY ); - - return fheroes2::GameMode::NEW_MULTI; + // Redraw the empty main menu screen to show it after the battle using screen restorer. + fheroes2::drawMainMenuScreen(); + return fheroes2::GameMode::START_BATTLE_ONLY_MODE; } -fheroes2::GameMode Game::NewHotSeat() +fheroes2::GameMode Game::NewHotSeat( const size_t playerCount ) { + assert( 1 < playerCount && playerCount < 7 ); Settings & conf = Settings::Get(); - if ( conf.isCampaignGameType() ) + if ( conf.isCampaignGameType() ) { conf.setCurrentMapInfo( {} ); - - if ( conf.IsGameType( Game::TYPE_BATTLEONLY ) ) { - // Redraw the main menu screen without multiplayer sub-menu to show it after the battle using screen restorer. - fheroes2::drawMainMenuScreen(); - - return StartBattleOnly(); - } - else { - conf.SetGameType( Game::TYPE_HOTSEAT ); - const uint8_t humanPlayerCount = SelectCountPlayers(); - - switch ( humanPlayerCount ) { - case 2: - return fheroes2::GameMode::SELECT_SCENARIO_TWO_HUMAN_PLAYERS; - case 3: - return fheroes2::GameMode::SELECT_SCENARIO_THREE_HUMAN_PLAYERS; - case 4: - return fheroes2::GameMode::SELECT_SCENARIO_FOUR_HUMAN_PLAYERS; - case 5: - return fheroes2::GameMode::SELECT_SCENARIO_FIVE_HUMAN_PLAYERS; - case 6: - return fheroes2::GameMode::SELECT_SCENARIO_SIX_HUMAN_PLAYERS; - default: - break; - } - } - return fheroes2::GameMode::MAIN_MENU; -} - -fheroes2::GameMode Game::CampaignSelection() -{ - if ( !isPriceOfLoyaltyCampaignPresent() ) { - return fheroes2::GameMode::NEW_SUCCESSION_WARS_CAMPAIGN; - } - - outputNewCampaignSelectionInTextSupportMode(); - - fheroes2::drawMainMenuScreen(); - const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); - - fheroes2::Button buttonSuccessionWars( buttonPos.x, buttonPos.y, ICN::BUTTON_ORIGINAL_CAMPAIGN, 0, 1 ); - fheroes2::Button buttonPriceOfLoyalty( buttonPos.x, buttonPos.y + buttonYStep * 1, ICN::BUTTON_EXPANSION_CAMPAIGN, 0, 1 ); - fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); - - buttonSuccessionWars.draw(); - buttonPriceOfLoyalty.draw(); - buttonCancel.draw(); - - fheroes2::Display::instance().render(); - - LocalEvent & le = LocalEvent::Get(); - while ( le.HandleEvents() ) { - buttonSuccessionWars.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonSuccessionWars.area() ) ); - buttonPriceOfLoyalty.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonPriceOfLoyalty.area() ) ); - buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); - - if ( le.MouseClickLeft( buttonSuccessionWars.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_NEW_ORIGINAL_CAMPAIGN ) ) { - return fheroes2::GameMode::NEW_SUCCESSION_WARS_CAMPAIGN; - } - if ( le.MouseClickLeft( buttonPriceOfLoyalty.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_NEW_EXPANSION_CAMPAIGN ) ) { - return fheroes2::GameMode::NEW_PRICE_OF_LOYALTY_CAMPAIGN; - } - if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { - return fheroes2::GameMode::MAIN_MENU; - } - - if ( le.isMouseRightButtonPressedInArea( buttonSuccessionWars.area() ) ) { - fheroes2::showStandardTextMessage( _( "Original Campaign" ), _( "Either Roland's or Archibald's campaign from the original Heroes of Might and Magic II." ), - Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonPriceOfLoyalty.area() ) ) { - fheroes2::showStandardTextMessage( _( "Expansion Campaign" ), _( "One of the four new campaigns from the Price of Loyalty expansion set." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { - fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); - } } - - return fheroes2::GameMode::QUIT_GAME; + conf.SetGameType( Game::TYPE_HOTSEAT ); + return playerCountModes[playerCount]; } fheroes2::GameMode Game::NewSuccessionWarsCampaign() @@ -395,7 +329,7 @@ fheroes2::GameMode Game::NewPriceOfLoyaltyCampaign() size_t highlightCampaignId = videos.size(); - fheroes2::GameMode gameChoice = fheroes2::GameMode::NEW_CAMPAIGN_SELECTION; + fheroes2::GameMode gameChoice = fheroes2::GameMode::NEW_GAME; uint64_t customDelay = 0; // Immediately indicate that the delay has passed to render first frame immediately. @@ -471,54 +405,6 @@ fheroes2::GameMode Game::NewPriceOfLoyaltyCampaign() return gameChoice; } -fheroes2::GameMode Game::NewNetwork() -{ - Settings & conf = Settings::Get(); - conf.SetGameType( conf.GameType() | Game::TYPE_NETWORK ); - - // setup cursor - const CursorRestorer cursorRestorer( true, Cursor::POINTER ); - - fheroes2::drawMainMenuScreen(); - const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); - - fheroes2::Button buttonHost( buttonPos.x, buttonPos.y, ICN::BTNNET, 0, 1 ); - fheroes2::Button buttonGuest( buttonPos.x, buttonPos.y + buttonYStep, ICN::BTNNET, 2, 3 ); - fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); - - buttonHost.draw(); - buttonGuest.draw(); - buttonCancel.draw(); - - fheroes2::Display::instance().render(); - - LocalEvent & le = LocalEvent::Get(); - while ( le.HandleEvents() ) { - buttonHost.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonHost.area() ) ); - buttonGuest.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonGuest.area() ) ); - buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); - - if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { - return fheroes2::GameMode::MAIN_MENU; - } - - // right info - if ( le.isMouseRightButtonPressedInArea( buttonHost.area() ) ) { - fheroes2::showStandardTextMessage( _( "Host" ), _( "The host sets up the game options. There can only be one host per network game." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonGuest.area() ) ) { - fheroes2::showStandardTextMessage( - _( "Guest" ), _( "The guest waits for the host to set up the game, then is automatically added in. There can be multiple guests for TCP/IP games." ), - Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { - fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); - } - } - - return fheroes2::GameMode::MAIN_MENU; -} - fheroes2::GameMode Game::NewGame( const bool isProbablyDemoVersion ) { outputNewMenuInTextSupportMode(); @@ -528,32 +414,78 @@ fheroes2::GameMode Game::NewGame( const bool isProbablyDemoVersion ) AudioManager::PlayMusicAsync( MUS::MAINMENU, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); - // reset last save name + // Reset last save name Game::SetLastSaveName( "" ); - // setup cursor + // Setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawMainMenuScreen(); - const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); - fheroes2::Button buttonStandardGame( buttonPos.x, buttonPos.y, ICN::BUTTON_STANDARD_GAME, 0, 1 ); - fheroes2::Button buttonCampaignGame( buttonPos.x, buttonPos.y + buttonYStep * 1, ICN::BUTTON_CAMPAIGN_GAME, 0, 1 ); - fheroes2::Button buttonMultiGame( buttonPos.x, buttonPos.y + buttonYStep * 2, ICN::BUTTON_MULTIPLAYER_GAME, 0, 1 ); - fheroes2::Button buttonBattleGame( buttonPos.x, buttonPos.y + buttonYStep * 3, ICN::BUTTON_BATTLE_ONLY, 0, 1 ); - fheroes2::Button buttonSettings( buttonPos.x, buttonPos.y + buttonYStep * 4, ICN::BUTTON_LARGE_CONFIG, 0, 1 ); - fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + // Setup dialog from buttons. + const int menuButtonsIcnIndex = Settings::Get().isEvilInterfaceEnabled() ? ICN::BUTTONS_NEW_GAME_MENU_EVIL : ICN::BUTTONS_NEW_GAME_MENU_GOOD; + fheroes2::ButtonGroup mainModeButtons; + // Only add the buttons needed for the initial state of the dialog. + for ( int32_t i = 0; i < 5; ++i ) { + mainModeButtons.createButton( 0, 0, menuButtonsIcnIndex, i * 2, i * 2 + 1, i ); + } + + const fheroes2::ButtonBase & buttonStandardGame = mainModeButtons.button( 0 ); + fheroes2::ButtonBase & buttonCampaignGame = mainModeButtons.button( 1 ); + const fheroes2::ButtonBase & buttonMultiGame = mainModeButtons.button( 2 ); + const fheroes2::ButtonBase & buttonBattleGame = mainModeButtons.button( 3 ); + const fheroes2::ButtonBase & buttonSettings = mainModeButtons.button( 4 ); + + // Generate dialog background with extra space added for the cancel button. + const int32_t spaceBetweenButtons = 10; + fheroes2::StandardWindow background( mainModeButtons, true, buttonStandardGame.area().height + spaceBetweenButtons ); + + // Make corners like in the original game. + background.applyGemDecoratedCorners(); + + // We don't need to restore the cancel button area because every state of the dialog has this button. + fheroes2::Display & display = fheroes2::Display::instance(); + fheroes2::ImageRestorer emptyDialog( display, background.activeArea().x, background.activeArea().y, background.activeArea().width, + background.activeArea().height - buttonStandardGame.area().height - spaceBetweenButtons * 2 - 2 ); if ( !isSuccessionWarsCampaignPresent() ) { buttonCampaignGame.disable(); } - buttonStandardGame.draw(); - buttonCampaignGame.draw(); - buttonMultiGame.draw(); - buttonBattleGame.draw(); - buttonSettings.draw(); + background.renderSymmetricButtons( mainModeButtons, 0, true ); + + // Add the cancel button at the bottom of the dialog. + fheroes2::Button buttonCancel( buttonStandardGame.area().x, + background.activeArea().y * 2 + background.activeArea().height - buttonStandardGame.area().y - buttonStandardGame.area().height, + menuButtonsIcnIndex, 10, 11 ); buttonCancel.draw(); + buttonCancel.drawShadow( display ); + + // Add extra buttons in disabled state. + fheroes2::Button buttonHotSeat( buttonStandardGame.area().x, buttonStandardGame.area().y, menuButtonsIcnIndex, 12, 13 ); + buttonHotSeat.disable(); + + fheroes2::ButtonGroup playerCountButtons; + + for ( int32_t i = 0; i < 5; ++i ) { + playerCountButtons.createButton( buttonStandardGame.area().x, buttonStandardGame.area().y + i * ( buttonStandardGame.area().height + spaceBetweenButtons ), + menuButtonsIcnIndex, ( i + 7 ) * 2, ( i + 7 ) * 2 + 1, i ); + playerCountButtons.button( i ).disable(); + } + + fheroes2::Button buttonSuccessionWars; + fheroes2::Button buttonPriceOfLoyalty; + buttonSuccessionWars.disable(); + buttonPriceOfLoyalty.disable(); + + const bool isPriceOfLoyaltyPresent = isPriceOfLoyaltyCampaignPresent(); + + if ( isPriceOfLoyaltyPresent ) { + buttonSuccessionWars.setICNInfo( menuButtonsIcnIndex, 24, 25 ); + buttonPriceOfLoyalty.setICNInfo( menuButtonsIcnIndex, 26, 27 ); + buttonSuccessionWars.setPosition( mainModeButtons.button( 0 ).area().x, mainModeButtons.button( 0 ).area().y ); + buttonPriceOfLoyalty.setPosition( mainModeButtons.button( 1 ).area().x, mainModeButtons.button( 1 ).area().y ); + } fheroes2::validateFadeInAndRender(); @@ -568,185 +500,139 @@ fheroes2::GameMode Game::NewGame( const bool isProbablyDemoVersion ) } while ( le.HandleEvents() ) { - buttonStandardGame.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonStandardGame.area() ) ); - if ( buttonCampaignGame.isEnabled() ) { - buttonCampaignGame.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCampaignGame.area() ) ); - } - buttonMultiGame.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonMultiGame.area() ) ); - buttonBattleGame.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonBattleGame.area() ) ); - buttonSettings.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonSettings.area() ) ); - buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); + if ( buttonStandardGame.isEnabled() ) { + mainModeButtons.drawOnState( le ); - if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_STANDARD ) || le.MouseClickLeft( buttonStandardGame.area() ) ) { - return fheroes2::GameMode::NEW_STANDARD; - } - if ( buttonCampaignGame.isEnabled() && ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_CAMPAIGN ) || le.MouseClickLeft( buttonCampaignGame.area() ) ) ) { - return fheroes2::GameMode::NEW_CAMPAIGN_SELECTION; - } - if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_MULTI ) || le.MouseClickLeft( buttonMultiGame.area() ) ) { - return fheroes2::GameMode::NEW_MULTI; - } - if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_SETTINGS ) || le.MouseClickLeft( buttonSettings.area() ) ) { - fheroes2::openGameSettings(); - return fheroes2::GameMode::MAIN_MENU; - } - if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { - return fheroes2::GameMode::MAIN_MENU; - } - if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_BATTLEONLY ) || le.MouseClickLeft( buttonBattleGame.area() ) ) { - return fheroes2::GameMode::NEW_BATTLE_ONLY; - } + if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_STANDARD ) || le.MouseClickLeft( buttonStandardGame.area() ) ) { + return fheroes2::GameMode::NEW_STANDARD; + } + if ( buttonCampaignGame.isEnabled() && ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_CAMPAIGN ) || le.MouseClickLeft( buttonCampaignGame.area() ) ) ) { + if ( !isPriceOfLoyaltyCampaignPresent() ) { + return fheroes2::GameMode::NEW_SUCCESSION_WARS_CAMPAIGN; + } + mainModeButtons.disable(); + emptyDialog.restore(); + buttonSuccessionWars.enable(); + buttonPriceOfLoyalty.enable(); + buttonSuccessionWars.draw(); + buttonPriceOfLoyalty.draw(); + buttonSuccessionWars.drawShadow( display ); + buttonPriceOfLoyalty.drawShadow( display ); + + outputNewCampaignSelectionInTextSupportMode(); + + display.render( emptyDialog.rect() ); + } + if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_SETTINGS ) || le.MouseClickLeft( buttonSettings.area() ) ) { + fheroes2::openGameSettings(); + return fheroes2::GameMode::MAIN_MENU; + } + if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_BATTLEONLY ) || le.MouseClickLeft( buttonBattleGame.area() ) ) { + return fheroes2::GameMode::NEW_BATTLE_ONLY; + } + if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_MULTI ) || le.MouseClickLeft( buttonMultiGame.area() ) ) { + mainModeButtons.disable(); + emptyDialog.restore(); + buttonHotSeat.enable(); + buttonHotSeat.draw(); + buttonHotSeat.drawShadow( display ); + display.render( emptyDialog.rect() ); + } - if ( le.isMouseRightButtonPressedInArea( buttonStandardGame.area() ) ) { - fheroes2::showStandardTextMessage( _( "Standard Game" ), _( "A single player game playing out a single map." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCampaignGame.area() ) ) { - fheroes2::showStandardTextMessage( _( "Campaign Game" ), _( "A single player game playing through a series of maps." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonMultiGame.area() ) ) { - fheroes2::showStandardTextMessage( _( "Multi-Player Game" ), - _( "A multi-player game, with several human players competing against each other on a single map." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonBattleGame.area() ) ) { - fheroes2::showStandardTextMessage( _( "Battle Only" ), _( "Setup and play a battle without loading any map." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonSettings.area() ) ) { - fheroes2::showStandardTextMessage( _( "Game Settings" ), _( "Change language, resolution and settings of the game." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { - fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); + if ( le.isMouseRightButtonPressedInArea( buttonStandardGame.area() ) ) { + fheroes2::showStandardTextMessage( _( "Standard Game" ), _( "A single player game playing out a single map." ), Dialog::ZERO ); + } + else if ( buttonCampaignGame.isEnabled() && le.isMouseRightButtonPressedInArea( buttonCampaignGame.area() ) ) { + fheroes2::showStandardTextMessage( _( "Campaign Game" ), _( "A single player game playing through a series of maps." ), Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( buttonMultiGame.area() ) ) { + fheroes2::showStandardTextMessage( _( "Multi-Player Game" ), + _( "A multi-player game, with several human players competing against each other on a single map." ), Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( buttonBattleGame.area() ) ) { + fheroes2::showStandardTextMessage( _( "Battle Only" ), _( "Setup and play a battle without loading any map." ), Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( buttonSettings.area() ) ) { + fheroes2::showStandardTextMessage( _( "Game Settings" ), _( "Change language, resolution and settings of the game." ), Dialog::ZERO ); + } } - } - - return fheroes2::GameMode::QUIT_GAME; -} + else if ( playerCountButtons.button( 0 ).isEnabled() ) { + playerCountButtons.drawOnState( le ); -fheroes2::GameMode Game::NewMulti() -{ - Settings & conf = Settings::Get(); - - if ( !( conf.IsGameType( Game::TYPE_BATTLEONLY ) ) ) - conf.SetGameType( Game::TYPE_STANDARD ); - - // setup cursor - const CursorRestorer cursorRestorer( true, Cursor::POINTER ); - - fheroes2::drawMainMenuScreen(); - const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); - - fheroes2::Button buttonHotSeat( buttonPos.x, buttonPos.y, ICN::BUTTON_HOT_SEAT, 0, 1 ); - fheroes2::Button buttonNetwork( buttonPos.x, buttonPos.y + buttonYStep * 1, ICN::BTNMP, 2, 3 ); - fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + // Loop through all player count buttons. + for ( size_t i = 0; i < playerCountOptions; ++i ) { + if ( le.MouseClickLeft( playerCountButtons.button( i ).area() ) || le.isKeyPressed( playerCountHotkeys[i] ) ) { + return NewHotSeat( i + 2 ); + } + } + if ( le.isMouseRightButtonPressedInArea( playerCountButtons.button( 0 ).area() ) ) { + fheroes2::showStandardTextMessage( _( "2 Players" ), _( "Play with 2 human players, and optionally, up to 4 additional computer players." ), + Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( playerCountButtons.button( 1 ).area() ) ) { + fheroes2::showStandardTextMessage( _( "3 Players" ), _( "Play with 3 human players, and optionally, up to 3 additional computer players." ), + Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( playerCountButtons.button( 2 ).area() ) ) { + fheroes2::showStandardTextMessage( _( "4 Players" ), _( "Play with 4 human players, and optionally, up to 2 additional computer players." ), + Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( playerCountButtons.button( 3 ).area() ) ) { + fheroes2::showStandardTextMessage( _( "5 Players" ), _( "Play with 5 human players, and optionally, up to 1 additional computer player." ), + Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( playerCountButtons.button( 4 ).area() ) ) { + fheroes2::showStandardTextMessage( _( "6 Players" ), _( "Play with 6 human players." ), Dialog::ZERO ); + } + } + else if ( buttonSuccessionWars.isEnabled() ) { + buttonSuccessionWars.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonSuccessionWars.area() ) ); + buttonPriceOfLoyalty.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonPriceOfLoyalty.area() ) ); - buttonHotSeat.draw(); - buttonCancel.draw(); - buttonNetwork.disable(); + if ( le.MouseClickLeft( buttonSuccessionWars.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_NEW_ORIGINAL_CAMPAIGN ) ) { + return fheroes2::GameMode::NEW_SUCCESSION_WARS_CAMPAIGN; + } + if ( le.MouseClickLeft( buttonPriceOfLoyalty.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_NEW_EXPANSION_CAMPAIGN ) ) { + return fheroes2::GameMode::NEW_PRICE_OF_LOYALTY_CAMPAIGN; + } - fheroes2::Display::instance().render(); + if ( le.isMouseRightButtonPressedInArea( buttonSuccessionWars.area() ) ) { + fheroes2::showStandardTextMessage( _( "Original Campaign" ), + _( "Either Roland's or Archibald's campaign from the original Heroes of Might and Magic II." ), Dialog::ZERO ); + } + else if ( le.isMouseRightButtonPressedInArea( buttonPriceOfLoyalty.area() ) ) { + fheroes2::showStandardTextMessage( _( "Expansion Campaign" ), _( "One of the four new campaigns from the Price of Loyalty expansion set." ), + Dialog::ZERO ); + } + } + else { + buttonHotSeat.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonHotSeat.area() ) ); + if ( le.MouseClickLeft( buttonHotSeat.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_HOTSEAT ) ) { + buttonHotSeat.disable(); + emptyDialog.restore(); + playerCountButtons.enable(); + playerCountButtons.draw(); + playerCountButtons.drawShadows( display ); + display.render( emptyDialog.rect() ); + continue; + } - LocalEvent & le = LocalEvent::Get(); - // new game loop - while ( le.HandleEvents() ) { - buttonHotSeat.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonHotSeat.area() ) ); - if ( buttonNetwork.isEnabled() ) { - buttonNetwork.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonNetwork.area() ) ); + if ( le.isMouseRightButtonPressedInArea( buttonHotSeat.area() ) ) { + fheroes2::showStandardTextMessage( + _( "Hot Seat" ), _( "Play a Hot Seat game, where 2 to 6 players play on the same device, switching into the 'Hot Seat' when it is their turn." ), + Dialog::ZERO ); + } } buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); - if ( le.MouseClickLeft( buttonHotSeat.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_HOTSEAT ) ) { - return fheroes2::GameMode::NEW_HOT_SEAT; - } if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { return fheroes2::GameMode::MAIN_MENU; } - // right info - if ( le.isMouseRightButtonPressedInArea( buttonHotSeat.area() ) ) { - fheroes2:: - showStandardTextMessage( _( "Hot Seat" ), - _( "Play a Hot Seat game, where 2 to 6 players play on the same device, switching into the 'Hot Seat' when it is their turn." ), - Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { + if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } } return fheroes2::GameMode::QUIT_GAME; } - -uint8_t Game::SelectCountPlayers() -{ - // setup cursor - const CursorRestorer cursorRestorer( true, Cursor::POINTER ); - - fheroes2::drawMainMenuScreen(); - const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); - - fheroes2::Button button2Players( buttonPos.x, buttonPos.y, ICN::BUTTON_2_PLAYERS, 0, 1 ); - fheroes2::Button button3Players( buttonPos.x, buttonPos.y + buttonYStep * 1, ICN::BUTTON_3_PLAYERS, 0, 1 ); - fheroes2::Button button4Players( buttonPos.x, buttonPos.y + buttonYStep * 2, ICN::BUTTON_4_PLAYERS, 0, 1 ); - fheroes2::Button button5Players( buttonPos.x, buttonPos.y + buttonYStep * 3, ICN::BUTTON_5_PLAYERS, 0, 1 ); - fheroes2::Button button6Players( buttonPos.x, buttonPos.y + buttonYStep * 4, ICN::BUTTON_6_PLAYERS, 0, 1 ); - fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); - - button2Players.draw(); - button3Players.draw(); - button4Players.draw(); - button5Players.draw(); - button6Players.draw(); - buttonCancel.draw(); - - fheroes2::Display::instance().render(); - - LocalEvent & le = LocalEvent::Get(); - while ( le.HandleEvents() ) { - button2Players.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( button2Players.area() ) ); - button3Players.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( button3Players.area() ) ); - button4Players.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( button4Players.area() ) ); - button5Players.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( button5Players.area() ) ); - button6Players.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( button6Players.area() ) ); - buttonCancel.drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( buttonCancel.area() ) ); - - if ( le.MouseClickLeft( button2Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_2 ) ) { - return 2; - } - if ( le.MouseClickLeft( button3Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_3 ) ) { - return 3; - } - if ( le.MouseClickLeft( button4Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_4 ) ) { - return 4; - } - if ( le.MouseClickLeft( button5Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_5 ) ) { - return 5; - } - if ( le.MouseClickLeft( button6Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_6 ) ) { - return 6; - } - if ( HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { - return 0; - } - - // right info - if ( le.isMouseRightButtonPressedInArea( button2Players.area() ) ) { - fheroes2::showStandardTextMessage( _( "2 Players" ), _( "Play with 2 human players, and optionally, up to 4 additional computer players." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( button3Players.area() ) ) { - fheroes2::showStandardTextMessage( _( "3 Players" ), _( "Play with 3 human players, and optionally, up to 3 additional computer players." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( button4Players.area() ) ) { - fheroes2::showStandardTextMessage( _( "4 Players" ), _( "Play with 4 human players, and optionally, up to 2 additional computer players." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( button5Players.area() ) ) { - fheroes2::showStandardTextMessage( _( "5 Players" ), _( "Play with 5 human players, and optionally, up to 1 additional computer player." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( button6Players.area() ) ) { - fheroes2::showStandardTextMessage( _( "6 Players" ), _( "Play with 6 human players." ), Dialog::ZERO ); - } - else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { - fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); - } - } - - return 0; -} diff --git a/src/fheroes2/gui/ui_button.cpp b/src/fheroes2/gui/ui_button.cpp index 69783b9a9d..a0bf056839 100644 --- a/src/fheroes2/gui/ui_button.cpp +++ b/src/fheroes2/gui/ui_button.cpp @@ -604,9 +604,10 @@ namespace fheroes2 ButtonGroup::ButtonGroup( const std::vector & texts ) { const size_t textCount = texts.size(); + const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); std::vector sprites; - makeSymmetricBackgroundSprites( sprites, texts, 86 ); + makeSymmetricBackgroundSprites( sprites, texts, isEvilInterface, 86 ); for ( size_t i = 0; i < textCount; ++i ) { createButton( 0, 0, std::move( sprites[i * 2] ), std::move( sprites[i * 2 + 1] ), static_cast( i ) ); @@ -655,6 +656,27 @@ namespace fheroes2 } } + void ButtonGroup::disable() + { + for ( const auto & button : _button ) { + button->disable(); + } + } + + void ButtonGroup::enable() + { + for ( const auto & button : _button ) { + button->enable(); + } + } + + void ButtonGroup::drawOnState( LocalEvent & le ) + { + for ( const auto & button : _button ) { + button->drawOnState( le.isMouseLeftButtonPressedAndHeldInArea( button->area() ) ); + } + } + int ButtonGroup::processEvents() { LocalEvent & le = LocalEvent::Get(); @@ -955,7 +977,8 @@ namespace fheroes2 renderTextOnButton( released, pressed, text, releasedOffset, pressedOffset, buttonSize, buttonFontColor ); } - void makeSymmetricBackgroundSprites( std::vector & backgroundSprites, const std::vector & texts, const int32_t minWidth ) + void makeSymmetricBackgroundSprites( std::vector & backgroundSprites, const std::vector & texts, const bool isEvilInterface, + const int32_t minWidth ) { if ( texts.size() < 2 ) { // You are trying to make a group of buttons with 0 or only one text. @@ -965,7 +988,6 @@ namespace fheroes2 backgroundSprites.resize( texts.size() * 2 ); - const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const FontType buttonFontType = { FontSize::BUTTON_RELEASED, ( isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE ) }; std::vector buttonTexts; @@ -989,9 +1011,14 @@ namespace fheroes2 maxHeight = std::max( maxHeight, text.height( finalWidth ) ); } - // Add extra vertical margin only if the button text is on two lines. - const bool isTwoLinesText = ( maxHeight == ( getFontHeight( buttonFontType.size ) * 2 ) ); - maxHeight += isTwoLinesText ? 26 : 10; + // Add extra vertical margin depending on how many lines of text there are. + if ( maxHeight > getFontHeight( buttonFontType.size ) ) { + const int32_t maxAllowedHeight = 200; + maxHeight = std::clamp( maxHeight, 56, maxAllowedHeight ); + } + else { + maxHeight += 10; + } const int backgroundIcnID = isEvilInterface ? ICN::STONEBAK_EVIL : ICN::STONEBAK; diff --git a/src/fheroes2/gui/ui_button.h b/src/fheroes2/gui/ui_button.h index 1d4e532611..5ee833f26d 100644 --- a/src/fheroes2/gui/ui_button.h +++ b/src/fheroes2/gui/ui_button.h @@ -33,6 +33,8 @@ #include "screen.h" #include "ui_base.h" +class LocalEvent; + namespace fheroes2 { enum class FontColor : uint8_t; @@ -294,6 +296,14 @@ namespace fheroes2 // Draws shadows for all the buttons in the group according to their coordinates. void drawShadows( Image & output ) const; + // Disable all the buttons in the button group. + void disable(); + + // Enable all the buttons in the button group. + void enable(); + + void drawOnState( LocalEvent & le ); + size_t getButtonsCount() const { return _button.size(); @@ -383,7 +393,8 @@ namespace fheroes2 // Generates multiple button backgrounds that have the same dimensions according to the widest and tallest texts provided. // backgroundSprites will be resized according to the number of button texts. - void makeSymmetricBackgroundSprites( std::vector & backgroundSprites, const std::vector & buttonTexts, const int32_t minWidth ); + void makeSymmetricBackgroundSprites( std::vector & backgroundSprites, const std::vector & buttonTexts, const bool isEvilInterface, + const int32_t minWidth ); void renderTextOnButton( Image & releasedState, Image & pressedState, const std::string & text, const Point & releasedTextOffset, const Point & pressedTextOffset, const Size & buttonSize, const FontColor fontColor ); diff --git a/src/fheroes2/gui/ui_window.cpp b/src/fheroes2/gui/ui_window.cpp index efae312df3..5d55dbd6e6 100644 --- a/src/fheroes2/gui/ui_window.cpp +++ b/src/fheroes2/gui/ui_window.cpp @@ -56,12 +56,12 @@ namespace const fheroes2::Rect & buttonArea = buttons.button( 0 ).area(); const int32_t buttonCount = static_cast( buttons.getButtonsCount() ); - const int32_t widthPadding = isSingleColumn ? 50 : 60; + const int32_t widthPadding = isSingleColumn ? 52 : 60; int32_t dialogWidth = widthPadding; - const int32_t heightPadding = isSingleColumn ? 39 : 26; + const int32_t heightPadding = isSingleColumn ? 43 : 26; // We assume that the cancel button height for multiple columns is 25 px because this button should contain only a single line of text. - const int32_t cancelButtonAreaHeight = isSingleColumn ? buttonArea.height + buttonsVerticalGap : 25 + buttonsVerticalGap + 10 + 1; + const int32_t cancelButtonAreaHeight = isSingleColumn ? 0 : 25 + buttonsVerticalGap + 10 + 1; int32_t dialogHeight = cancelButtonAreaHeight + heightPadding + extraHeight; // When there's an odd number of buttons we always make a dialog for a single column of buttons. @@ -77,8 +77,20 @@ namespace dialogWidth += ( buttonCount / 2 ) * buttonArea.width + ( ( buttonCount / 2 - 1 ) * buttonsHorizontalGap ); dialogHeight += buttonArea.height * 2 + ( buttonsVerticalGap + 10 ); } + fheroes2::Point placement; + if ( isSingleColumn ) { + const fheroes2::Sprite mainMenuBackground = fheroes2::AGG::GetICN( ICN::HEROES, 0 ); + const int32_t panelXPos = output.width() - mainMenuBackground.x() - ( dialogWidth + fheroes2::borderWidthPx ) - 8; + const int32_t panelYPos = mainMenuBackground.y() + fheroes2::borderWidthPx + 8; + placement.x = panelXPos; + placement.y = panelYPos; + } + else { + placement.x = ( output.width() - dialogWidth ) / 2; + placement.y = ( output.height() - dialogHeight ) / 2; + } - return { ( output.width() - dialogWidth ) / 2, ( output.height() - dialogHeight ) / 2, dialogWidth, dialogHeight }; + return { placement.x, placement.y, dialogWidth, dialogHeight }; } } @@ -120,47 +132,6 @@ namespace fheroes2 } render(); - - const int32_t buttonsWidth = buttons.button( 0 ).area().width; - const int32_t buttonsHeight = buttons.button( 0 ).area().height; - - int32_t rows = 0; - int32_t columns = 0; - Point buttonsOffset; - - const int32_t buttonCount = static_cast( buttons.getButtonsCount() ); - // An odd number of buttons will be arranged on a single column. - if ( isSingleColumn || buttonCount % 2 != 0 ) { - rows = buttonCount; - columns = 1; - buttonsOffset = { 25, 22 }; - } - else if ( buttonCount == 2 ) { - rows = 1; - columns = 2; - buttonsOffset = { 30, 15 }; - } - else { - rows = 2; - columns = buttonCount / 2; - buttonsOffset = { 30, 15 }; - } - // This assumes that the extra height always gets added above the buttons. - buttonsOffset.y += extraHeight; - - const int32_t verticalGapOffset = isSingleColumn ? buttonsVerticalGap : 2 * buttonsVerticalGap; - - size_t buttonId = 0; - for ( int32_t row = 0; row < rows; ++row ) { - for ( int32_t column = 0; column < columns; ++column ) { - buttons.button( buttonId ) - .setPosition( _activeArea.x + column * buttonsWidth + buttonsOffset.x + column * buttonsHorizontalGap, - _activeArea.y + ( row * ( buttonsHeight + verticalGapOffset ) ) + buttonsOffset.y ); - ++buttonId; - } - } - buttons.drawShadows( output ); - buttons.draw( output ); } void StandardWindow::render() @@ -384,6 +355,40 @@ namespace fheroes2 applyRectTransform( 8, 1, 5 ); } + void StandardWindow::applyGemDecoratedCorners() + { + fheroes2::Image gem; + const int32_t gemSideLength = 9; + gem.resize( gemSideLength, gemSideLength ); + gem.reset(); + const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); + if ( !isEvilInterface ) { + const fheroes2::Sprite & gemDialog = fheroes2::AGG::GetICN( ICN::REDBACK, 0 ); + Copy( gemDialog, 20, 2, gem, 0, 0, gemSideLength, gemSideLength ); + } + else { + const fheroes2::Sprite & corners = fheroes2::AGG::GetICN( ICN::EVIL_DIALOG_PLAIN_CORNERS, 0 ); + const int32_t cornerSideLength = 43; + Copy( corners, 0, 0, _output, _windowArea.x, _windowArea.y, cornerSideLength, cornerSideLength ); + Copy( corners, cornerSideLength, 0, _output, _windowArea.x + _windowArea.width - cornerSideLength, _windowArea.y, cornerSideLength, cornerSideLength ); + Copy( corners, 0, cornerSideLength, _output, _windowArea.x, _windowArea.y + _windowArea.height - cornerSideLength, cornerSideLength, cornerSideLength ); + Copy( corners, cornerSideLength, cornerSideLength, _output, _windowArea.x + _windowArea.width - cornerSideLength, + _windowArea.y + _windowArea.height - cornerSideLength, cornerSideLength, cornerSideLength ); + + const fheroes2::Sprite & gemDialog = fheroes2::AGG::GetICN( ICN::WINLOSEE, 0 ); + Copy( gemDialog, 32, 2, gem, 0, 0, gemSideLength, gemSideLength ); + FillTransform( gem, 0, 0, 1, 1, 1 ); + FillTransform( gem, gemSideLength - 1, 0, 1, 1, 1 ); + FillTransform( gem, 0, gemSideLength - 1, 1, 1, 1 ); + FillTransform( gem, gemSideLength - 1, gemSideLength - 1, 1, 1, 1 ); + } + Blit( gem, 0, 0, _output, _windowArea.x + 4, _windowArea.y + 2, gemSideLength, gemSideLength ); + Blit( gem, 0, 0, _output, _windowArea.x + _windowArea.width - 2 - gemSideLength, _windowArea.y + 2, gemSideLength, gemSideLength ); + Blit( gem, 0, 0, _output, _windowArea.x + 4, _windowArea.y + _windowArea.height - gemSideLength - 4, gemSideLength, gemSideLength ); + Blit( gem, 0, 0, _output, _windowArea.x + _windowArea.width - 2 - gemSideLength, _windowArea.y + _windowArea.height - gemSideLength - 4, gemSideLength, + gemSideLength ); + } + void StandardWindow::renderScrollbarBackground( const Rect & roi, const bool isEvilInterface ) { const Sprite & scrollBar = AGG::GetICN( isEvilInterface ? ICN::ADVBORDE : ICN::ADVBORD, 0 ); @@ -478,6 +483,50 @@ namespace fheroes2 button.draw(); } + void StandardWindow::renderSymmetricButtons( ButtonGroup & buttons, const int32_t offsetY, const bool isSingleColumn ) + { + const int32_t buttonsWidth = buttons.button( 0 ).area().width; + const int32_t buttonsHeight = buttons.button( 0 ).area().height; + + int32_t rows = 0; + int32_t columns = 0; + Point buttonsOffset; + + const int32_t buttonCount = static_cast( buttons.getButtonsCount() ); + // An odd number of buttons will be arranged on a single column. + if ( isSingleColumn || buttonCount % 2 != 0 ) { + rows = buttonCount; + columns = 1; + buttonsOffset = { 25, 22 }; + } + else if ( buttonCount == 2 ) { + rows = 1; + columns = 2; + buttonsOffset = { 30, 15 }; + } + else { + rows = 2; + columns = buttonCount / 2; + buttonsOffset = { 30, 15 }; + } + // This assumes that the extra height always gets added above the buttons. + buttonsOffset.y += offsetY; + + const int32_t verticalGapOffset = isSingleColumn ? buttonsVerticalGap : 2 * buttonsVerticalGap; + + size_t buttonId = 0; + for ( int32_t row = 0; row < rows; ++row ) { + for ( int32_t column = 0; column < columns; ++column ) { + buttons.button( buttonId ) + .setPosition( _activeArea.x + column * buttonsWidth + buttonsOffset.x + column * buttonsHorizontalGap, + _activeArea.y + ( row * ( buttonsHeight + verticalGapOffset ) ) + buttonsOffset.y ); + ++buttonId; + } + } + buttons.drawShadows( _output ); + buttons.draw( _output ); + } + Point StandardWindow::_getRenderPos( const Point & offset, const Size & itemSize, const Padding padding ) const { Point pos( _activeArea.x, _activeArea.y ); diff --git a/src/fheroes2/gui/ui_window.h b/src/fheroes2/gui/ui_window.h index 0f880d4564..5356c644c9 100644 --- a/src/fheroes2/gui/ui_window.h +++ b/src/fheroes2/gui/ui_window.h @@ -96,9 +96,13 @@ namespace fheroes2 // Renders a button background with shadow which has specified heights and widths. void renderCustomButtonSprite( ButtonSprite & button, const std::string & buttonText, const fheroes2::Size buttonSize, const Point & offset, const Padding padding ); + // Renders the buttons in a symmetric button group in either a grid or a single column. The y offset parameter will move all the buttons. + void renderSymmetricButtons( ButtonGroup & buttons, const int32_t offsetY, const bool isSingleColumn ); void applyTextBackgroundShading( const Rect & roi ); static void applyTextBackgroundShading( Image & output, const Rect & roi ); + // Apply corners with gems. + void applyGemDecoratedCorners(); static void renderBackgroundImage( fheroes2::Image & output, const Rect & roi, const int32_t borderOffset, const bool isEvilInterface );