From f2d23c37320cef43a901d18b07da0442f10d8385 Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Wed, 27 Nov 2024 17:07:58 +0300 Subject: [PATCH 01/30] Use the platform-dependent byte order for the SDL audio format (#9297) --- src/engine/audio.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/audio.cpp b/src/engine/audio.cpp index 5445aac0f7d..f152d36e63d 100644 --- a/src/engine/audio.cpp +++ b/src/engine/audio.cpp @@ -73,7 +73,8 @@ namespace // Notice: Value 22050 causes music distortion on Windows. int frequency = 44100; #endif - uint16_t format = AUDIO_S16; + // Signed 16-bit samples with platform-dependent byte order + uint16_t format = AUDIO_S16SYS; // Stereo audio support int channels = 2; #if defined( ANDROID ) From e774d2e01a297da5a61e8b9f0f664c74036b6b7e Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Wed, 27 Nov 2024 17:08:26 +0300 Subject: [PATCH 02/30] Enable the number input dialog for the recruitment window (#9300) --- src/fheroes2/dialog/dialog_recruit.cpp | 91 +++++++++++++++----------- 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/src/fheroes2/dialog/dialog_recruit.cpp b/src/fheroes2/dialog/dialog_recruit.cpp index a4232ba2007..ae795a3fd0c 100644 --- a/src/fheroes2/dialog/dialog_recruit.cpp +++ b/src/fheroes2/dialog/dialog_recruit.cpp @@ -51,6 +51,7 @@ #include "ui_button.h" #include "ui_constants.h" #include "ui_dialog.h" +#include "ui_keyboard.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" @@ -284,6 +285,8 @@ Troop Dialog::RecruitMonster( const Monster & monster0, const uint32_t available fheroes2::Image background( 68, 19 ); fheroes2::Copy( originalBackground, 134, 159, background, 0, 0, background.width(), background.height() ); + const fheroes2::Rect recruitCountInputArea( dialogOffset.x + 118, dialogOffset.y + 147, background.width(), background.height() ); + if ( isEvilInterface ) { fheroes2::ApplyPalette( background, PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ) ); } @@ -423,7 +426,21 @@ Troop Dialog::RecruitMonster( const Monster & monster0, const uint32_t available upgrades.emplace_back( upgrades.back().GetDowngrade() ); } - // str loop + const auto updateCurrentInfo = [&paymentMonster = std::as_const( paymentMonster ), &max = std::as_const( max ), &result = std::as_const( result ), &paymentCosts, + &buttonMax, &buttonMin, &maxmin]() { + paymentCosts = paymentMonster * result; + + if ( result == max ) { + maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); + } + else if ( result == 1 ) { + maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, false ); + } + else { + maxmin.clear(); + } + }; + while ( le.HandleEvents() ) { bool redraw = false; @@ -516,11 +533,11 @@ Troop Dialog::RecruitMonster( const Monster & monster0, const uint32_t available result = max; paymentMonster = monster.GetCost(); - paymentCosts = paymentMonster * result; - redraw = true; - maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); + updateCurrentInfo(); RedrawMonsterInfo( windowActiveArea, monster, available, true ); + + redraw = true; } if ( le.isMouseRightButtonPressedInArea( monsterArea ) ) { @@ -539,60 +556,56 @@ Troop Dialog::RecruitMonster( const Monster & monster0, const uint32_t available continue; } - int32_t temp = static_cast( result ); - if ( fheroes2::processIntegerValueTyping( 0, static_cast( max ), temp ) ) { + if ( int32_t temp = static_cast( result ); fheroes2::processIntegerValueTyping( 0, static_cast( max ), temp ) ) { result = temp; - paymentCosts = paymentMonster * result; - redraw = true; - maxmin.clear(); - if ( result == max ) { - maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); - } - else if ( result == 1 ) { - maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, false ); - } + updateCurrentInfo(); + + redraw = true; } - if ( ( le.isMouseWheelUpInArea( rtWheel ) || le.MouseClickLeft( buttonUp.area() ) || le.isKeyPressed( fheroes2::Key::KEY_UP ) || timedButtonUp.isDelayPassed() ) - && result < max ) { - ++result; - paymentCosts += paymentMonster; + if ( le.MouseClickLeft( recruitCountInputArea ) ) { + int32_t temp = static_cast( result ); + + fheroes2::openVirtualNumpad( temp, 0, static_cast( max ) ); + assert( temp >= 0 && temp <= static_cast( max ) ); + + result = temp; + + updateCurrentInfo(); + redraw = true; - maxmin.clear(); + } + else if ( ( le.isMouseWheelUpInArea( rtWheel ) || le.MouseClickLeft( buttonUp.area() ) || le.isKeyPressed( fheroes2::Key::KEY_UP ) + || timedButtonUp.isDelayPassed() ) + && result < max ) { + ++result; - if ( result == max ) { - maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); - } - else if ( result == 1 ) { - maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, false ); - } + updateCurrentInfo(); + + redraw = true; } else if ( ( le.isMouseWheelDownInArea( rtWheel ) || le.MouseClickLeft( buttonDn.area() ) || le.isKeyPressed( fheroes2::Key::KEY_DOWN ) || timedButtonDn.isDelayPassed() ) && result ) { --result; - paymentCosts -= paymentMonster; - redraw = true; - maxmin.clear(); - if ( result == max ) { - maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); - } - else if ( result == 1 ) { - maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, false ); - } + updateCurrentInfo(); + + redraw = true; } else if ( buttonMax.isEnabled() && le.MouseClickLeft( buttonMax.area() ) && result != max ) { - maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); result = max; - paymentCosts = paymentMonster * max; + + updateCurrentInfo(); + redraw = true; } else if ( buttonMin.isEnabled() && le.MouseClickLeft( buttonMin.area() ) && result != 1 ) { - maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, false ); result = 1; - paymentCosts = paymentMonster; + + updateCurrentInfo(); + redraw = true; } else if ( le.isMouseRightButtonPressedInArea( buttonOk.area() ) ) { From b0d1dddb6297a314d3e950ab42d8b11747350641 Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Wed, 27 Nov 2024 17:08:53 +0300 Subject: [PATCH 03/30] Fix a few more code smells (#9279) --- src/engine/zzlib.cpp | 245 ++++++++++++++++++------------------- src/engine/zzlib.h | 2 +- src/fheroes2/army/army.cpp | 9 +- src/fheroes2/army/army.h | 2 +- 4 files changed, 129 insertions(+), 129 deletions(-) diff --git a/src/engine/zzlib.cpp b/src/engine/zzlib.cpp index 17a8648c971..c5df174ea33 100644 --- a/src/engine/zzlib.cpp +++ b/src/engine/zzlib.cpp @@ -37,172 +37,169 @@ namespace constexpr uint16_t FORMAT_VERSION_0 = 0; } -namespace Compression +std::vector Compression::unzipData( const uint8_t * src, const size_t srcSize, size_t realSize /* = 0 */ ) { - std::vector unzipData( const uint8_t * src, const size_t srcSize, size_t realSize /* = 0 */ ) - { - if ( src == nullptr || srcSize == 0 ) { - return {}; - } - - const uLong srcSizeULong = static_cast( srcSize ); - if ( srcSizeULong != srcSize ) { - ERROR_LOG( "The size of the compressed data is too large" ) - return {}; - } + if ( src == nullptr || srcSize == 0 ) { + return {}; + } - std::vector res( realSize ); + const uLong srcSizeULong = static_cast( srcSize ); + if ( srcSizeULong != srcSize ) { + ERROR_LOG( "The size of the compressed data is too large" ) + return {}; + } - if ( realSize == 0 ) { - constexpr size_t sizeMultiplier = 7; + std::vector res( realSize ); - if ( srcSize > res.max_size() / sizeMultiplier ) { - // If the multiplicated size is too large, let's start with the original size and see how it goes - realSize = srcSize; - } - else { - realSize = srcSize * sizeMultiplier; - } + if ( realSize == 0 ) { + constexpr size_t sizeMultiplier = 7; - res.resize( realSize ); + if ( srcSize > res.max_size() / sizeMultiplier ) { + // If the multiplicated size is too large, let's start with the original size and see how it goes + realSize = srcSize; } - - uLong dstSizeULong = static_cast( res.size() ); - if ( dstSizeULong != res.size() ) { - ERROR_LOG( "The size of the decompressed data is too large" ) - return {}; + else { + realSize = srcSize * sizeMultiplier; } - int ret = Z_BUF_ERROR; - while ( Z_BUF_ERROR == ( ret = uncompress( res.data(), &dstSizeULong, src, srcSizeULong ) ) ) { - constexpr size_t sizeMultiplier = 2; + res.resize( realSize ); + } - // Avoid infinite loop due to unsigned overflow on multiplication - if ( res.size() > res.max_size() / sizeMultiplier ) { - ERROR_LOG( "The size of the decompressed data is too large" ) - return {}; - } + uLong dstSizeULong = static_cast( res.size() ); + if ( dstSizeULong != res.size() ) { + ERROR_LOG( "The size of the decompressed data is too large" ) + return {}; + } - res.resize( res.size() * sizeMultiplier ); + int ret = Z_BUF_ERROR; + while ( Z_BUF_ERROR == ( ret = uncompress( res.data(), &dstSizeULong, src, srcSizeULong ) ) ) { + constexpr size_t sizeMultiplier = 2; - dstSizeULong = static_cast( res.size() ); - if ( dstSizeULong != res.size() ) { - ERROR_LOG( "The size of the decompressed data is too large" ) - return {}; - } + // Avoid infinite loop due to unsigned overflow on multiplication + if ( res.size() > res.max_size() / sizeMultiplier ) { + ERROR_LOG( "The size of the decompressed data is too large" ) + return {}; } - if ( ret != Z_OK ) { - ERROR_LOG( "zlib error: " << ret ) + res.resize( res.size() * sizeMultiplier ); + + dstSizeULong = static_cast( res.size() ); + if ( dstSizeULong != res.size() ) { + ERROR_LOG( "The size of the decompressed data is too large" ) return {}; } + } - res.resize( dstSizeULong ); - - return res; + if ( ret != Z_OK ) { + ERROR_LOG( "zlib error: " << ret ) + return {}; } - std::vector zipData( const uint8_t * src, const size_t srcSize ) - { - if ( src == nullptr || srcSize == 0 ) { - return {}; - } + res.resize( dstSizeULong ); - const uLong srcSizeULong = static_cast( srcSize ); - if ( srcSizeULong != srcSize ) { - ERROR_LOG( "The size of the source data is too large" ) - return {}; - } + return res; +} - std::vector res( compressBound( srcSizeULong ) ); +std::vector Compression::zipData( const uint8_t * src, const size_t srcSize ) +{ + if ( src == nullptr || srcSize == 0 ) { + return {}; + } - uLong dstSizeULong = static_cast( res.size() ); - if ( dstSizeULong != res.size() ) { - ERROR_LOG( "The size of the compressed data is too large" ) - return {}; - } + const uLong srcSizeULong = static_cast( srcSize ); + if ( srcSizeULong != srcSize ) { + ERROR_LOG( "The size of the source data is too large" ) + return {}; + } - const int ret = compress( res.data(), &dstSizeULong, src, srcSizeULong ); + std::vector res( compressBound( srcSizeULong ) ); - if ( ret != Z_OK ) { - ERROR_LOG( "zlib error: " << ret ) - return {}; - } + uLong dstSizeULong = static_cast( res.size() ); + if ( dstSizeULong != res.size() ) { + ERROR_LOG( "The size of the compressed data is too large" ) + return {}; + } - res.resize( dstSizeULong ); + const int ret = compress( res.data(), &dstSizeULong, src, srcSizeULong ); - return res; + if ( ret != Z_OK ) { + ERROR_LOG( "zlib error: " << ret ) + return {}; } - bool unzipStream( IStreamBase & inputStream, OStreamBase & outputStream ) - { - const uint32_t rawSize = inputStream.get32(); - const uint32_t zipSize = inputStream.get32(); - if ( zipSize == 0 ) { - return false; - } + res.resize( dstSizeULong ); - const uint16_t version = inputStream.get16(); - if ( version != FORMAT_VERSION_0 ) { - return false; - } + return res; +} - inputStream.skip( 2 ); // Unused bytes +bool Compression::unzipStream( IStreamBase & inputStream, OStreamBase & outputStream ) +{ + const uint32_t rawSize = inputStream.get32(); + const uint32_t zipSize = inputStream.get32(); + if ( zipSize == 0 ) { + return false; + } - const std::vector zip = inputStream.getRaw( zipSize ); - const std::vector raw = unzipData( zip.data(), zip.size(), rawSize ); - if ( raw.size() != rawSize ) { - return false; - } + const uint16_t version = inputStream.get16(); + if ( version != FORMAT_VERSION_0 ) { + return false; + } - outputStream.putRaw( raw.data(), raw.size() ); + inputStream.skip( 2 ); // Unused bytes - return !outputStream.fail(); + const std::vector zip = inputStream.getRaw( zipSize ); + const std::vector raw = unzipData( zip.data(), zip.size(), rawSize ); + if ( raw.size() != rawSize ) { + return false; } - bool zipStreamBuf( IStreamBuf & inputStream, OStreamBase & outputStream ) - { - const std::vector zip = zipData( inputStream.data(), inputStream.size() ); - if ( zip.empty() ) { - return false; - } + outputStream.putRaw( raw.data(), raw.size() ); - outputStream.put32( static_cast( inputStream.size() ) ); - outputStream.put32( static_cast( zip.size() ) ); - outputStream.put16( FORMAT_VERSION_0 ); - outputStream.put16( 0 ); // Unused bytes - outputStream.putRaw( zip.data(), zip.size() ); + return !outputStream.fail(); +} - return !outputStream.fail(); +bool Compression::zipStreamBuf( const IStreamBuf & inputStream, OStreamBase & outputStream ) +{ + const std::vector zip = zipData( inputStream.data(), inputStream.size() ); + if ( zip.empty() ) { + return false; } - fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer ) - { - if ( imageData == nullptr || imageSize == 0 || width <= 0 || height <= 0 ) { - return {}; - } + outputStream.put32( static_cast( inputStream.size() ) ); + outputStream.put32( static_cast( zip.size() ) ); + outputStream.put16( FORMAT_VERSION_0 ); + outputStream.put16( 0 ); // Unused bytes + outputStream.putRaw( zip.data(), zip.size() ); - const std::vector & uncompressedData = unzipData( imageData, imageSize ); - if ( doubleLayer && ( uncompressedData.size() & 1 ) == 1 ) { - return {}; - } + return !outputStream.fail(); +} - const size_t uncompressedSize = doubleLayer ? uncompressedData.size() / 2 : uncompressedData.size(); +fheroes2::Image Compression::CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer ) +{ + if ( imageData == nullptr || imageSize == 0 || width <= 0 || height <= 0 ) { + return {}; + } - if ( static_cast( width ) * height != uncompressedSize ) { - return {}; - } + const std::vector & uncompressedData = unzipData( imageData, imageSize ); + if ( doubleLayer && ( uncompressedData.size() & 1 ) == 1 ) { + return {}; + } - fheroes2::Image out; - if ( !doubleLayer ) { - out._disableTransformLayer(); - } - out.resize( width, height ); + const size_t uncompressedSize = doubleLayer ? uncompressedData.size() / 2 : uncompressedData.size(); - std::memcpy( out.image(), uncompressedData.data(), uncompressedSize ); - if ( doubleLayer ) { - std::memcpy( out.transform(), uncompressedData.data() + uncompressedSize, uncompressedSize ); - } - return out; + if ( static_cast( width ) * height != uncompressedSize ) { + return {}; + } + + fheroes2::Image out; + if ( !doubleLayer ) { + out._disableTransformLayer(); + } + out.resize( width, height ); + + std::memcpy( out.image(), uncompressedData.data(), uncompressedSize ); + if ( doubleLayer ) { + std::memcpy( out.transform(), uncompressedData.data() + uncompressedSize, uncompressedSize ); } + return out; } diff --git a/src/engine/zzlib.h b/src/engine/zzlib.h index 6bebd643261..4e45e307ded 100644 --- a/src/engine/zzlib.h +++ b/src/engine/zzlib.h @@ -52,7 +52,7 @@ namespace Compression // Zips the contents of the buffer from the current read position to the end of the buffer and writes // it to the given output stream. The current read position of the buffer does not change. Returns // true on success and false on error. - bool zipStreamBuf( IStreamBuf & inputStream, OStreamBase & outputStream ); + bool zipStreamBuf( const IStreamBuf & inputStream, OStreamBase & outputStream ); fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer ); } diff --git a/src/fheroes2/army/army.cpp b/src/fheroes2/army/army.cpp index f123151215c..89d20513998 100644 --- a/src/fheroes2/army/army.cpp +++ b/src/fheroes2/army/army.cpp @@ -529,10 +529,13 @@ void Troops::UpgradeTroops( const Castle & castle ) const } } -Troop * Troops::GetFirstValid() +Troop * Troops::GetFirstValid() const { - iterator it = std::find_if( begin(), end(), []( const Troop * troop ) { return troop->isValid(); } ); - return it == end() ? nullptr : *it; + if ( const const_iterator iter = std::find_if( begin(), end(), []( const Troop * troop ) { return troop->isValid(); } ); iter != end() ) { + return *iter; + } + + return nullptr; } Troop * Troops::getBestMatchToCondition( const std::function & condition ) const diff --git a/src/fheroes2/army/army.h b/src/fheroes2/army/army.h index bf237913c9d..868c5ba920f 100644 --- a/src/fheroes2/army/army.h +++ b/src/fheroes2/army/army.h @@ -97,7 +97,7 @@ class Troops : protected std::vector void Clean(); void UpgradeTroops( const Castle & castle ) const; - Troop * GetFirstValid(); + Troop * GetFirstValid() const; Troop * GetWeakestTroop() const; Troop * GetSlowestTroop() const; From e5e0b88d884c148a93aa1b28f5f3883681df6b74 Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Thu, 28 Nov 2024 18:39:34 +0300 Subject: [PATCH 04/30] Put the dialog box headers in order (#9301) --- src/fheroes2/dialog/dialog_selectfile.cpp | 2 +- src/fheroes2/editor/editor_interface.cpp | 14 +++++++------- src/fheroes2/editor/editor_mainmenu.cpp | 2 +- src/fheroes2/editor/editor_save_map_window.cpp | 2 +- src/fheroes2/game/game_newgame.cpp | 4 ++-- src/fheroes2/gui/ui_keyboard.cpp | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/fheroes2/dialog/dialog_selectfile.cpp b/src/fheroes2/dialog/dialog_selectfile.cpp index 40ba0de6bf0..8badc05b085 100644 --- a/src/fheroes2/dialog/dialog_selectfile.cpp +++ b/src/fheroes2/dialog/dialog_selectfile.cpp @@ -423,7 +423,7 @@ namespace msg.append( "\n\n" ); msg.append( System::GetBasename( listbox.GetCurrent().filename ) ); - if ( Dialog::YES == fheroes2::showStandardTextMessage( _( "Warning!" ), msg, Dialog::YES | Dialog::NO ) ) { + if ( Dialog::YES == fheroes2::showStandardTextMessage( _( "Warning" ), msg, Dialog::YES | Dialog::NO ) ) { System::Unlink( listbox.GetCurrent().filename ); listbox.RemoveSelected(); diff --git a/src/fheroes2/editor/editor_interface.cpp b/src/fheroes2/editor/editor_interface.cpp index f1bf056c516..5ead6184e3d 100644 --- a/src/fheroes2/editor/editor_interface.cpp +++ b/src/fheroes2/editor/editor_interface.cpp @@ -1759,12 +1759,12 @@ namespace Interface bool EditorInterface::loadMap( const std::string & filePath ) { if ( !Maps::Map_Format::loadMap( filePath, _mapFormat ) ) { - fheroes2::showStandardTextMessage( _( "Warning!" ), "Failed to load the map.", Dialog::OK ); + fheroes2::showStandardTextMessage( _( "Error" ), "Failed to load the map.", Dialog::OK ); return false; } if ( !Maps::readMapInEditor( _mapFormat ) ) { - fheroes2::showStandardTextMessage( _( "Warning!" ), "Failed to read the map.", Dialog::OK ); + fheroes2::showStandardTextMessage( _( "Error" ), "Failed to read the map.", Dialog::OK ); return false; } @@ -1785,20 +1785,20 @@ namespace Interface void EditorInterface::saveMapToFile() { if ( !Maps::updateMapPlayers( _mapFormat ) ) { - fheroes2::showStandardTextMessage( _( "Warning!" ), _( "The map is corrupted." ), Dialog::OK ); + fheroes2::showStandardTextMessage( _( "Error" ), _( "The map is corrupted." ), Dialog::OK ); return; } const std::string dataPath = System::GetDataDirectory( "fheroes2" ); if ( dataPath.empty() ) { - fheroes2::showStandardTextMessage( _( "Warning!" ), _( "Unable to locate data directory to save the map." ), Dialog::OK ); + fheroes2::showStandardTextMessage( _( "Error" ), _( "Unable to locate data directory to save the map." ), Dialog::OK ); return; } std::string mapDirectory = System::concatPath( dataPath, "maps" ); if ( !System::IsDirectory( mapDirectory ) && !System::MakeDirectory( mapDirectory ) ) { - fheroes2::showStandardTextMessage( _( "Warning!" ), _( "Unable to create a directory to save the map." ), Dialog::OK ); + fheroes2::showStandardTextMessage( _( "Error" ), _( "Unable to create a directory to save the map." ), Dialog::OK ); return; } @@ -1807,7 +1807,7 @@ namespace Interface std::string correctedMapDirectory; if ( !System::GetCaseInsensitivePath( mapDirectory, correctedMapDirectory ) ) { - fheroes2::showStandardTextMessage( _( "Warning!" ), _( "Unable to locate a directory to save the map." ), Dialog::OK ); + fheroes2::showStandardTextMessage( _( "Error" ), _( "Unable to locate a directory to save the map." ), Dialog::OK ); return; } @@ -1849,7 +1849,7 @@ namespace Interface return; } - fheroes2::showStandardTextMessage( _( "Warning!" ), _( "Failed to save the map." ), Dialog::OK ); + fheroes2::showStandardTextMessage( _( "Error" ), _( "Failed to save the map." ), Dialog::OK ); } void EditorInterface::openMapSpecificationsDialog() diff --git a/src/fheroes2/editor/editor_mainmenu.cpp b/src/fheroes2/editor/editor_mainmenu.cpp index fbbeca2d51d..24d3501ae31 100644 --- a/src/fheroes2/editor/editor_mainmenu.cpp +++ b/src/fheroes2/editor/editor_mainmenu.cpp @@ -101,7 +101,7 @@ namespace void showWIPInfo() { - fheroes2::showStandardTextMessage( _( "Warning!" ), "The Map Editor is still in development. This function is not available yet.", Dialog::OK ); + fheroes2::showStandardTextMessage( _( "Warning" ), "The Map Editor is still in development. This function is not available yet.", Dialog::OK ); } Maps::MapSize selectMapSize() diff --git a/src/fheroes2/editor/editor_save_map_window.cpp b/src/fheroes2/editor/editor_save_map_window.cpp index 8b0937b046a..e4200eed7c6 100644 --- a/src/fheroes2/editor/editor_save_map_window.cpp +++ b/src/fheroes2/editor/editor_save_map_window.cpp @@ -404,7 +404,7 @@ namespace Editor msg.append( "\n\n" ); msg.append( System::GetBasename( listbox.GetCurrent().filename ) ); - if ( Dialog::YES == fheroes2::showStandardTextMessage( _( "Warning!" ), msg, Dialog::YES | Dialog::NO ) ) { + if ( Dialog::YES == fheroes2::showStandardTextMessage( _( "Warning" ), msg, Dialog::YES | Dialog::NO ) ) { System::Unlink( listbox.GetCurrent().filename ); listbox.RemoveSelected(); diff --git a/src/fheroes2/game/game_newgame.cpp b/src/fheroes2/game/game_newgame.cpp index bd13a3e48b4..3907505df2f 100644 --- a/src/fheroes2/game/game_newgame.cpp +++ b/src/fheroes2/game/game_newgame.cpp @@ -131,7 +131,7 @@ namespace void showMissingVideoFilesWindow() { - fheroes2::showStandardTextMessage( _( "Warning!" ), + fheroes2::showStandardTextMessage( _( "Warning" ), _( "The required video files for the campaign selection window are missing. " "Please make sure that all necessary files are present in the system." ), Dialog::OK ); @@ -557,7 +557,7 @@ fheroes2::GameMode Game::NewGame( const bool isProbablyDemoVersion ) LocalEvent & le = LocalEvent::Get(); if ( isProbablyDemoVersion ) { - fheroes2::showStandardTextMessage( _( "Warning!" ), + fheroes2::showStandardTextMessage( _( "Warning" ), _( "fheroes2 needs data files from the original Heroes of Might and Magic II to operate. " "You appear to be using the demo version of Heroes of Might and Magic II for this purpose. " "Please note that only one scenario will be available in this setup." ), diff --git a/src/fheroes2/gui/ui_keyboard.cpp b/src/fheroes2/gui/ui_keyboard.cpp index f05de0e9d0f..5ad728c09ea 100644 --- a/src/fheroes2/gui/ui_keyboard.cpp +++ b/src/fheroes2/gui/ui_keyboard.cpp @@ -862,7 +862,7 @@ namespace fheroes2 break; case DialogAction::Close: { const auto handleInvalidValue = [&action]() { - showStandardTextMessage( _( "Warning" ), _( "The entered value is invalid." ), Dialog::OK ); + showStandardTextMessage( _( "Error" ), _( "The entered value is invalid." ), Dialog::OK ); action = DialogAction::DoNothing; }; @@ -872,7 +872,7 @@ namespace fheroes2 StringReplace( errorMessage, "%{minValue}", minValue ); StringReplace( errorMessage, "%{maxValue}", maxValue ); - showStandardTextMessage( _( "Warning" ), std::move( errorMessage ), Dialog::OK ); + showStandardTextMessage( _( "Error" ), std::move( errorMessage ), Dialog::OK ); action = DialogAction::DoNothing; }; From 47e4f9abfcd9e437bbf0e79f2c2c36da4ac937fb Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Thu, 28 Nov 2024 18:42:06 +0300 Subject: [PATCH 05/30] AudioManager: simplify the code for playing ambient sounds, evaluate & remove the old TODO comment (#9288) --- src/fheroes2/audio/audio_manager.cpp | 141 +++++++++++++-------------- 1 file changed, 70 insertions(+), 71 deletions(-) diff --git a/src/fheroes2/audio/audio_manager.cpp b/src/fheroes2/audio/audio_manager.cpp index 28ae73c747d..57ddd647adc 100644 --- a/src/fheroes2/audio/audio_manager.cpp +++ b/src/fheroes2/audio/audio_manager.cpp @@ -591,21 +591,24 @@ namespace DEBUG_LOG( DBG_GAME, DBG_TRACE, "Play MIDI music track " << XMI::GetString( xmi ) ) } - void getClosestSoundIdPairByAngle( const std::vector & soundToAdd, const std::vector & soundToReplace, - size_t & bestSoundToAddId, size_t & bestSoundToReplaceId ) + std::pair findPairOfClosestSoundEffects( const std::vector & effectsToAdd, + const std::vector & effectsToReplace ) { - assert( !soundToAdd.empty() && !soundToReplace.empty() ); + assert( !effectsToAdd.empty() && !effectsToReplace.empty() ); + + std::pair result{ 0, 0 }; - bestSoundToAddId = 0; - bestSoundToReplaceId = 0; int bestAngleDiff = INT_MAX; int bestDistanceDiff = INT_MAX; - for ( size_t soundToAddId = 0; soundToAddId < soundToAdd.size(); ++soundToAddId ) { - for ( size_t soundToReplaceId = 0; soundToReplaceId < soundToReplace.size(); ++soundToReplaceId ) { - const int angleDiff = std::abs( soundToAdd[soundToAddId].angle - soundToReplace[soundToReplaceId].angle ); - const int distanceDiff - = std::abs( static_cast( soundToAdd[soundToAddId].distance ) - static_cast( soundToReplace[soundToReplaceId].distance ) ); + for ( size_t effectToAddId = 0; effectToAddId < effectsToAdd.size(); ++effectToAddId ) { + const AudioManager::AudioLoopEffectInfo & effectToAdd = effectsToAdd[effectToAddId]; + + for ( size_t effectToReplaceId = 0; effectToReplaceId < effectsToReplace.size(); ++effectToReplaceId ) { + const ChannelAudioLoopEffectInfo & effectToReplace = effectsToReplace[effectToReplaceId]; + + const int angleDiff = std::abs( effectToAdd.angle - effectToReplace.angle ); + const int distanceDiff = std::abs( static_cast( effectToAdd.distance ) - static_cast( effectToReplace.distance ) ); if ( bestAngleDiff < angleDiff ) { continue; @@ -618,10 +621,11 @@ namespace bestAngleDiff = angleDiff; bestDistanceDiff = distanceDiff; - bestSoundToAddId = soundToAddId; - bestSoundToReplaceId = soundToReplaceId; + result = { effectToAddId, effectToReplaceId }; } } + + return result; } void clearAllAudioLoopEffects() @@ -648,25 +652,18 @@ namespace clearAllAudioLoopEffects(); } - // TODO: use another container for sound effects to support more efficient sort and find operations based on the code below. - std::map> tempAudioLoopEffects; std::swap( tempAudioLoopEffects, currentAudioLoopEffects ); - // Remove all sounds which aren't currently played anymore. This might be the case when Audio::Stop() function is called. + // Remove all sounds which aren't currently played anymore. This may be the case if the sound playback was forcibly stopped. for ( auto iter = tempAudioLoopEffects.begin(); iter != tempAudioLoopEffects.end(); ) { - std::vector & existingEffects = iter->second; + auto & [dummy, effects] = *iter; - for ( auto effectIter = existingEffects.begin(); effectIter != existingEffects.end(); ) { - if ( !Mixer::isPlaying( effectIter->channelId ) ) { - effectIter = existingEffects.erase( effectIter ); - } - else { - ++effectIter; - } - } + effects.erase( std::remove_if( effects.begin(), effects.end(), + []( const ChannelAudioLoopEffectInfo & effectInfo ) { return !Mixer::isPlaying( effectInfo.channelId ); } ), + effects.end() ); - if ( existingEffects.empty() ) { + if ( effects.empty() ) { iter = tempAudioLoopEffects.erase( iter ); } else { @@ -674,59 +671,61 @@ namespace } } - // First find channels with existing sounds and just update them. + // Try to reuse existing sounds. for ( auto iter = soundEffects.begin(); iter != soundEffects.end(); ) { - const M82::SoundType soundType = iter->first; - std::vector & effectsToAdd = iter->second; + auto & [soundType, effectsToAdd] = *iter; assert( !effectsToAdd.empty() ); - auto foundSoundTypeIter = tempAudioLoopEffects.find( soundType ); - if ( foundSoundTypeIter == tempAudioLoopEffects.end() ) { - // This sound type does not exist. + auto soundTypeIter = tempAudioLoopEffects.find( soundType ); + if ( soundTypeIter == tempAudioLoopEffects.end() ) { + // There is no such sound, nothing to reuse. ++iter; continue; } - std::vector & effectsToReplace = foundSoundTypeIter->second; + std::vector & effectsToReplace = soundTypeIter->second; - // Search for an existing sound which has the exact distance and angle. - for ( auto soundToAddIter = effectsToAdd.begin(); soundToAddIter != effectsToAdd.end(); ) { - auto exactSoundEffect = std::find( effectsToReplace.begin(), effectsToReplace.end(), *soundToAddIter ); - if ( exactSoundEffect != effectsToReplace.end() ) { - currentAudioLoopEffects[soundType].emplace_back( *exactSoundEffect ); - effectsToReplace.erase( exactSoundEffect ); - - soundToAddIter = effectsToAdd.erase( soundToAddIter ); + // Find existing sounds that have exactly the same distance and angle as the ones that should be added, and reuse these sounds as is. + for ( auto effectToAddIter = effectsToAdd.begin(); effectToAddIter != effectsToAdd.end(); ) { + auto effectToReplaceIter = std::find( effectsToReplace.begin(), effectsToReplace.end(), *effectToAddIter ); + if ( effectToReplaceIter == effectsToReplace.end() ) { + ++effectToAddIter; continue; } - ++soundToAddIter; + currentAudioLoopEffects[soundType].emplace_back( *effectToReplaceIter ); + + effectsToReplace.erase( effectToReplaceIter ); + effectToAddIter = effectsToAdd.erase( effectToAddIter ); } - size_t effectsToReplaceCount = std::min( effectsToAdd.size(), effectsToReplace.size() ); + // Find the existing sounds closest to those that should be added and reuse these sounds by adjusting their positions. + { + size_t effectsToReplaceCount = std::min( effectsToAdd.size(), effectsToReplace.size() ); + + while ( effectsToReplaceCount > 0 ) { + --effectsToReplaceCount; - while ( effectsToReplaceCount > 0 ) { - --effectsToReplaceCount; + { + const auto [effectToAddId, effectToReplaceId] = findPairOfClosestSoundEffects( effectsToAdd, effectsToReplace ); + const int channelId = effectsToReplace[effectToReplaceId].channelId; - // Find the closest angles to those which are going to be added. - size_t soundToAddId = 0; - size_t soundToReplaceId = 0; - getClosestSoundIdPairByAngle( effectsToAdd, effectsToReplace, soundToAddId, soundToReplaceId ); + currentAudioLoopEffects[soundType].emplace_back( effectsToAdd[effectToAddId], channelId ); - currentAudioLoopEffects[soundType].emplace_back( effectsToReplace[soundToReplaceId] ); - effectsToReplace.erase( effectsToReplace.begin() + static_cast( soundToReplaceId ) ); + effectsToReplace.erase( effectsToReplace.begin() + static_cast( effectToReplaceId ) ); + effectsToAdd.erase( effectsToAdd.begin() + static_cast( effectToAddId ) ); + } - ChannelAudioLoopEffectInfo & currentInfo = currentAudioLoopEffects[soundType].back(); - currentInfo = { effectsToAdd[soundToAddId], currentInfo.channelId }; - effectsToAdd.erase( effectsToAdd.begin() + static_cast( soundToAddId ) ); + const ChannelAudioLoopEffectInfo & effectInfo = currentAudioLoopEffects[soundType].back(); - assert( is3DAudioEnabled || currentInfo.angle == 0 ); + assert( is3DAudioEnabled || effectInfo.angle == 0 ); - Mixer::setPosition( currentInfo.channelId, currentInfo.angle, currentInfo.distance ); + Mixer::setPosition( effectInfo.channelId, effectInfo.angle, effectInfo.distance ); + } } if ( effectsToReplace.empty() ) { - tempAudioLoopEffects.erase( foundSoundTypeIter ); + tempAudioLoopEffects.erase( soundTypeIter ); } if ( effectsToAdd.empty() ) { @@ -737,39 +736,39 @@ namespace } } - for ( const auto & audioEffectPair : tempAudioLoopEffects ) { - const std::vector & existingEffects = audioEffectPair.second; - - for ( const ChannelAudioLoopEffectInfo & info : existingEffects ) { - Mixer::Stop( info.channelId ); + // Channels with sounds that could not be reused because such sounds are missing from the list of added ones should be stopped. + { + for ( const auto & [dummy, effects] : tempAudioLoopEffects ) { + for ( const ChannelAudioLoopEffectInfo & effectInfo : effects ) { + Mixer::Stop( effectInfo.channelId ); + } } - } - tempAudioLoopEffects.clear(); + tempAudioLoopEffects.clear(); + } // Add new sound effects. - for ( const auto & audioEffectPair : soundEffects ) { - const M82::SoundType soundType = audioEffectPair.first; - const std::vector & effects = audioEffectPair.second; + for ( const auto & [soundType, effects] : soundEffects ) { assert( !effects.empty() ); - for ( const AudioManager::AudioLoopEffectInfo & info : effects ) { - // It is a new sound effect. Register and play it. + for ( const AudioManager::AudioLoopEffectInfo & effectInfo : effects ) { + // This is a new sound effect. Register and play it. const std::vector & audioData = GetWAV( soundType ); if ( audioData.empty() ) { // Looks like nothing to play. Ignore it. continue; } - assert( is3DAudioEnabled || info.angle == 0 ); + assert( is3DAudioEnabled || effectInfo.angle == 0 ); - const int channelId = Mixer::Play( audioData.data(), static_cast( audioData.size() ), true, std::pair{ info.angle, info.distance } ); + const int channelId + = Mixer::Play( audioData.data(), static_cast( audioData.size() ), true, std::pair{ effectInfo.angle, effectInfo.distance } ); if ( channelId < 0 ) { // Unable to play this sound. continue; } - currentAudioLoopEffects[soundType].emplace_back( info, channelId ); + currentAudioLoopEffects[soundType].emplace_back( effectInfo, channelId ); DEBUG_LOG( DBG_GAME, DBG_TRACE, "Playing sound " << M82::GetString( soundType ) ) } From 3bd24140819b097164f1f5784aa3eb3fc877cb8e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 28 Nov 2024 21:02:26 +0300 Subject: [PATCH 06/30] Update translation files (#9303) --- docs/json/lang_vi.json | 2 +- files/lang/be.po | 9 +++------ files/lang/bg.po | 12 ++++++------ files/lang/cs.po | 12 ++++++------ files/lang/de.po | 12 ++++++------ files/lang/dk.po | 12 ++++++------ files/lang/es.po | 12 ++++++------ files/lang/fr.po | 12 ++++++------ files/lang/hu.po | 12 ++++++------ files/lang/it.po | 12 ++++++------ files/lang/lt.po | 12 ++++++------ files/lang/nb.po | 12 ++++++------ files/lang/nl.po | 9 +++------ files/lang/pl.po | 12 ++++++------ files/lang/pt.po | 12 ++++++------ files/lang/ro.po | 9 +++------ files/lang/ru.po | 12 ++++++------ files/lang/sk.po | 12 ++++++------ files/lang/sv.po | 12 ++++++------ files/lang/tr.po | 9 +++------ files/lang/uk.po | 12 ++++++------ files/lang/vi.po | 12 ++++++------ 22 files changed, 115 insertions(+), 127 deletions(-) diff --git a/docs/json/lang_vi.json b/docs/json/lang_vi.json index f1a0de82820..1b68b233f76 100644 --- a/docs/json/lang_vi.json +++ b/docs/json/lang_vi.json @@ -1 +1 @@ -{"schemaVersion":1,"label":"Vietnamese","message":"84%","color":"yellow"} +{"schemaVersion":1,"label":"Vietnamese","message":"83%","color":"yellow"} diff --git a/files/lang/be.po b/files/lang/be.po index 93517710cd2..63226fbcb6a 100644 --- a/files/lang/be.po +++ b/files/lang/be.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2022-09-23 16:01+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -3289,8 +3289,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "" -msgid "Warning!" -msgstr "" +msgid "Warning" +msgstr "Увага" msgid "Click to save the current game." msgstr "" @@ -4247,9 +4247,6 @@ msgstr "Вярнуцца ў галоўнае меню." msgid "No maps available!" msgstr "" -msgid "Warning" -msgstr "Увага" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "%{color} гулец" diff --git a/files/lang/bg.po b/files/lang/bg.po index 80fdb348781..56a64657330 100644 --- a/files/lang/bg.po +++ b/files/lang/bg.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2022-11-08 16:44+0000\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -3539,8 +3539,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Сигурен ли си, че искаш да изтриеш файла:" -msgid "Warning!" -msgstr "Внимание!" +msgid "Warning" +msgstr "Внимание" msgid "Click to save the current game." msgstr "Кликни, за да запазиш текущата игра." @@ -4557,9 +4557,6 @@ msgstr "Отказ обратно към главното меню на Създ msgid "No maps available!" msgstr "Няма налични карти!" -msgid "Warning" -msgstr "Внимание" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "%{color} %{race} герой" @@ -12240,5 +12237,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Внимание!" + #~ msgid "Dummy 1" #~ msgstr "Манекен 1" diff --git a/files/lang/cs.po b/files/lang/cs.po index 78820df1f03..b01850f53ab 100644 --- a/files/lang/cs.po +++ b/files/lang/cs.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-10-21 20:45+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Czech \n" @@ -3451,8 +3451,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Opravdu chceš smazat soubor:" -msgid "Warning!" -msgstr "Varování!" +msgid "Warning" +msgstr "Varování" msgid "Click to save the current game." msgstr "Klikni pro uložení aktuální hry." @@ -4395,9 +4395,6 @@ msgstr "Zrušit a vrátit se do hlavního menu editoru map." msgid "No maps available!" msgstr "Žádné mapy k dispozici!" -msgid "Warning" -msgstr "Varování" - msgid "[%{pos}]: %{race} hero" msgstr "[%{pos}]: %{race} hrdina" @@ -11843,3 +11840,6 @@ msgstr "" "Nejnovější verzi hry nalezneš na\n" "https://github.com/ihhub/\n" "fheroes2/releases" + +#~ msgid "Warning!" +#~ msgstr "Varování!" diff --git a/files/lang/de.po b/files/lang/de.po index acff61cb418..e427d1f2033 100644 --- a/files/lang/de.po +++ b/files/lang/de.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-11-07 12:48+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -3564,8 +3564,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Seid Ihr sicher, dass Ihr die Datei löschen möchten:" -msgid "Warning!" -msgstr "Achtung!" +msgid "Warning" +msgstr "Achtung" msgid "Click to save the current game." msgstr "Klicken um das aktuelle Spiel zu speichern." @@ -4587,9 +4587,6 @@ msgstr "Abbrechen und zurück zum Hauptmenü des Karteneditors." msgid "No maps available!" msgstr "Keine Karten vorhanden!" -msgid "Warning" -msgstr "Achtung" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "Spieler %{color}" @@ -12436,5 +12433,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Achtung!" + #~ msgid "Dummy 1" #~ msgstr "Dummy 1" diff --git a/files/lang/dk.po b/files/lang/dk.po index 7f2aeca52b4..31b52b9ef56 100644 --- a/files/lang/dk.po +++ b/files/lang/dk.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-05-21 12:45+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Danish (Denmark)\n" @@ -3527,8 +3527,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Er du sikker på at du vil slette denne fil:" -msgid "Warning!" -msgstr "Advarsel!" +msgid "Warning" +msgstr "Advarsel" msgid "Click to save the current game." msgstr "Klik for at gemme igangværende spil." @@ -4485,9 +4485,6 @@ msgstr "Afbryd og gå tilbage til Korteditorens hovedmenu." msgid "No maps available!" msgstr "Intet kort tilgængelig!" -msgid "Warning" -msgstr "Advarsel" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "%{color} %{race} helt" @@ -12110,5 +12107,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Advarsel!" + #~ msgid "Dummy 1" #~ msgstr "Pladsholder 1" diff --git a/files/lang/es.po b/files/lang/es.po index 5b0902154ed..e2a08bf28a5 100644 --- a/files/lang/es.po +++ b/files/lang/es.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-10-07 15:36+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Spanish \n" @@ -3455,8 +3455,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Estas seguro de que quieres eliminar el archivo:" -msgid "Warning!" -msgstr "¡Advertencia!" +msgid "Warning" +msgstr "Advertencia" #, fuzzy msgid "Click to save the current game." @@ -4454,9 +4454,6 @@ msgstr "Cancelar y volver al menú principal." msgid "No maps available!" msgstr "¡Ningún mapa disponible!" -msgid "Warning" -msgstr "Advertencia" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "Héroe %{race} %{color}" @@ -11859,6 +11856,9 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "¡Advertencia!" + #, fuzzy #~ msgid "Dummy 1" #~ msgstr "Momia" diff --git a/files/lang/fr.po b/files/lang/fr.po index 3a9ccc80dfa..b6992fff452 100644 --- a/files/lang/fr.po +++ b/files/lang/fr.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-10-07 15:36+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: French \n" @@ -3546,8 +3546,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Voulez-vous vraiment effacer le fichier :" -msgid "Warning!" -msgstr "Attention !" +msgid "Warning" +msgstr "Attention" msgid "Click to save the current game." msgstr "Enregistrer le jeu en cours." @@ -4541,9 +4541,6 @@ msgstr "Annuler et revenir au menu principal de l'éditeur." msgid "No maps available!" msgstr "Aucune carte disponible !" -msgid "Warning" -msgstr "Attention" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "Héros %{race} %{color}" @@ -12257,5 +12254,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Attention !" + #~ msgid "Dummy 1" #~ msgstr "Emplacement 1" diff --git a/files/lang/hu.po b/files/lang/hu.po index 29848267d5e..1d08004ee71 100644 --- a/files/lang/hu.po +++ b/files/lang/hu.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2023-09-04 21:55+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Hungarian \n" @@ -3469,8 +3469,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Biztosan törlöd ezt a fájlt?:" -msgid "Warning!" -msgstr "Figyelem!" +msgid "Warning" +msgstr "Figyelmeztetés" msgid "Click to save the current game." msgstr "Kattints az aktuális játékállás mentéséhez." @@ -4415,9 +4415,6 @@ msgstr "Vissza a térképszerkesztő főmenüjébe." msgid "No maps available!" msgstr "Nincs használható térkép!" -msgid "Warning" -msgstr "Figyelmeztetés" - msgid "[%{pos}]: %{race} hero" msgstr "[%{pos}]: %{race} hős" @@ -11920,5 +11917,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Figyelem!" + #~ msgid "Dummy 1" #~ msgstr "Próba 1" diff --git a/files/lang/it.po b/files/lang/it.po index 346e225c4b4..f4f8ad73630 100644 --- a/files/lang/it.po +++ b/files/lang/it.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2022-12-03 04:36+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -3357,8 +3357,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Sei sicuro di voler cancellare il file:" -msgid "Warning!" -msgstr "Attenzione!" +msgid "Warning" +msgstr "" msgid "Click to save the current game." msgstr "Clicca per salvare la partita in corso." @@ -4348,9 +4348,6 @@ msgstr "" msgid "No maps available!" msgstr "" -msgid "Warning" -msgstr "" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "Giocatore %{color}" @@ -11224,6 +11221,9 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Attenzione!" + #, fuzzy #~ msgid "Dummy 1" #~ msgstr "Mummia" diff --git a/files/lang/lt.po b/files/lang/lt.po index 40a82277e1d..f4bef63c855 100644 --- a/files/lang/lt.po +++ b/files/lang/lt.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2022-09-23 16:14+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Lithuanian \n" @@ -3295,8 +3295,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Ar jūs tikrai norite ištrinti failą:" -msgid "Warning!" -msgstr "Įspėjimas!" +msgid "Warning" +msgstr "" msgid "Click to save the current game." msgstr "" @@ -4253,9 +4253,6 @@ msgstr "" msgid "No maps available!" msgstr "" -msgid "Warning" -msgstr "" - msgid "[%{pos}]: %{race} hero" msgstr "" @@ -10952,3 +10949,6 @@ msgid "" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" + +#~ msgid "Warning!" +#~ msgstr "Įspėjimas!" diff --git a/files/lang/nb.po b/files/lang/nb.po index b86e07c15aa..576628d42ab 100644 --- a/files/lang/nb.po +++ b/files/lang/nb.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2023-09-03 18:42+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -3581,8 +3581,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Er du sikker på at du vil slette filen:" -msgid "Warning!" -msgstr "Advarsel!" +msgid "Warning" +msgstr "Advarsel" msgid "Click to save the current game." msgstr "Klikk for å lagre det nåværende spillet." @@ -4598,9 +4598,6 @@ msgstr "Avbryt og dra tilbake til karttegnerens hovedmeny." msgid "No maps available!" msgstr "Ingen kart er tilgjengelige!" -msgid "Warning" -msgstr "Advarsel" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "%{color} spiller" @@ -12454,5 +12451,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Advarsel!" + #~ msgid "Dummy 1" #~ msgstr "Plassholder 1" diff --git a/files/lang/nl.po b/files/lang/nl.po index df2a4afc6f9..cc0e4965227 100644 --- a/files/lang/nl.po +++ b/files/lang/nl.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2022-10-25 08:52+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Dutch \n" @@ -3401,8 +3401,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "" -msgid "Warning!" -msgstr "" +msgid "Warning" +msgstr "Waarschuuwing" msgid "Click to save the current game." msgstr "" @@ -4314,9 +4314,6 @@ msgstr "" msgid "No maps available!" msgstr "" -msgid "Warning" -msgstr "Waarschuuwing" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "%{name} de %{race}" diff --git a/files/lang/pl.po b/files/lang/pl.po index 87997402a1d..1d6aae8f326 100644 --- a/files/lang/pl.po +++ b/files/lang/pl.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-07-20 23:19+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: https://discord.com/" @@ -3530,8 +3530,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Czy na pewno chcesz usunąć plik:" -msgid "Warning!" -msgstr "Uwaga!" +msgid "Warning" +msgstr "Uwaga" msgid "Click to save the current game." msgstr "Zapisz bieżącą grę." @@ -4524,9 +4524,6 @@ msgstr "Anuluj, aby wrócić do głównego menu." msgid "No maps available!" msgstr "Mapy niedostępne!" -msgid "Warning" -msgstr "Uwaga" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "%{color} gracz" @@ -12271,5 +12268,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Uwaga!" + #~ msgid "Dummy 1" #~ msgstr "Artefakt 1" diff --git a/files/lang/pt.po b/files/lang/pt.po index 99567cac362..232f77e0b68 100644 --- a/files/lang/pt.po +++ b/files/lang/pt.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-10-14 16:46-0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: Brazilian Portuguese \n" @@ -3489,8 +3489,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Você tem certeza de que deseja apagar este arquivo:" -msgid "Warning!" -msgstr "Aviso!" +msgid "Warning" +msgstr "Aviso" msgid "Click to save the current game." msgstr "Clique para salvar o jogo atual." @@ -4444,9 +4444,6 @@ msgstr "Cancelar e voltar para o menu principal do Editor de Mapa." msgid "No maps available!" msgstr "Nenhum mapa disponível!" -msgid "Warning" -msgstr "Aviso" - msgid "[%{pos}]: %{race} hero" msgstr "[%{pos}]: herói %{race}" @@ -12059,5 +12056,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Aviso!" + #~ msgid "Dummy 1" #~ msgstr "Fantoche 1" diff --git a/files/lang/ro.po b/files/lang/ro.po index ba991e5dc8b..e855a02e3bb 100644 --- a/files/lang/ro.po +++ b/files/lang/ro.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2022-10-25 08:56+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -3333,8 +3333,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "" -msgid "Warning!" -msgstr "" +msgid "Warning" +msgstr "Atenție" msgid "Click to save the current game." msgstr "" @@ -4249,9 +4249,6 @@ msgstr "" msgid "No maps available!" msgstr "" -msgid "Warning" -msgstr "Atenție" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "%{name} cel %{race}" diff --git a/files/lang/ru.po b/files/lang/ru.po index 92e4ec41b71..1f969f4eef8 100644 --- a/files/lang/ru.po +++ b/files/lang/ru.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-10-04 15:32+0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: <>\n" @@ -3497,8 +3497,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Вы действительно хотите удалить файл:" -msgid "Warning!" -msgstr "Внимание!" +msgid "Warning" +msgstr "Внимание" msgid "Click to save the current game." msgstr "Нажмите, чтобы сохранить текущую игру." @@ -4440,9 +4440,6 @@ msgstr "Вернуться в главное меню редактора кар msgid "No maps available!" msgstr "Нет доступных карт!" -msgid "Warning" -msgstr "Внимание" - msgid "[%{pos}]: %{race} hero" msgstr "[%{pos}]: герой - %{race}" @@ -11962,5 +11959,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Внимание!" + #~ msgid "Dummy 1" #~ msgstr "Болванка 1" diff --git a/files/lang/sk.po b/files/lang/sk.po index bbd8646607d..f5d59dcdb71 100644 --- a/files/lang/sk.po +++ b/files/lang/sk.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2023-09-11 20:22+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -3520,8 +3520,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Určite chceš vymazať súbor:" -msgid "Warning!" -msgstr "Pozor!" +msgid "Warning" +msgstr "Pozor" msgid "Click to save the current game." msgstr "Klikni pre uloženie terajšej hry." @@ -4523,9 +4523,6 @@ msgstr "Zrušenie a návrat do hlavnej ponuky Editora máp." msgid "No maps available!" msgstr "Žiadne dostupné mapy!" -msgid "Warning" -msgstr "Pozor" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "%{color} hráč" @@ -12140,5 +12137,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Pozor!" + #~ msgid "Dummy 1" #~ msgstr "Atrapa 1" diff --git a/files/lang/sv.po b/files/lang/sv.po index 68a856b8ef2..901fe64590d 100644 --- a/files/lang/sv.po +++ b/files/lang/sv.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-11-11 20:10+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: Swedish \n" @@ -3468,8 +3468,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Är du säker på att du vill radera filen:" -msgid "Warning!" -msgstr "Varning!" +msgid "Warning" +msgstr "Varning" msgid "Click to save the current game." msgstr "Klicka för att spara nuvarande spel." @@ -4415,9 +4415,6 @@ msgstr "Avbryt och gå tillbaka till kartredigerarens huvudmeny." msgid "No maps available!" msgstr "Inga kartor tillgängliga!" -msgid "Warning" -msgstr "Varning" - msgid "[%{pos}]: %{race} hero" msgstr "[%{pos}]: %{race}hjälte" @@ -11938,5 +11935,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Varning!" + #~ msgid "Dummy 1" #~ msgstr "Atrapp 1" diff --git a/files/lang/tr.po b/files/lang/tr.po index b43ea04b8d1..b98594b1c6b 100644 --- a/files/lang/tr.po +++ b/files/lang/tr.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2022-10-01 20:20+0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: Turkish \n" @@ -3213,8 +3213,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "" -msgid "Warning!" -msgstr "" +msgid "Warning" +msgstr "Uyarı" msgid "Click to save the current game." msgstr "" @@ -4122,9 +4122,6 @@ msgstr "" msgid "No maps available!" msgstr "" -msgid "Warning" -msgstr "Uyarı" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "%{name} %{race}" diff --git a/files/lang/uk.po b/files/lang/uk.po index ee5ed0dcb04..541cc9a0538 100644 --- a/files/lang/uk.po +++ b/files/lang/uk.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-08-23 09:35+0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -3477,8 +3477,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Ви впевнені, що хочете видалити файл:" -msgid "Warning!" -msgstr "Попередження!" +msgid "Warning" +msgstr "Попередження" msgid "Click to save the current game." msgstr "Натисніть, щоб зберегти поточну гру." @@ -4427,9 +4427,6 @@ msgstr "Повернутися до головного меню Редактор msgid "No maps available!" msgstr "Нема доступних мап!" -msgid "Warning" -msgstr "Попередження" - msgid "[%{pos}]: %{race} hero" msgstr "[%{pos}]: %{race} герой" @@ -11968,5 +11965,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Попередження!" + #~ msgid "Dummy 1" #~ msgstr "Заповнювач 1" diff --git a/files/lang/vi.po b/files/lang/vi.po index 2ddcc87d456..71b229855fd 100644 --- a/files/lang/vi.po +++ b/files/lang/vi.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-11-28 15:55+0000\n" "PO-Revision-Date: 2024-02-20 08:49+0700\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -3525,8 +3525,8 @@ msgstr "" msgid "Are you sure you want to delete file:" msgstr "Ngươi có chắc muốn xóa file:" -msgid "Warning!" -msgstr "Cảnh báo!" +msgid "Warning" +msgstr "Cảnh báo" msgid "Click to save the current game." msgstr "Nhấp để lưu trò chơi hiện tại." @@ -4520,9 +4520,6 @@ msgstr "Hủy quay lại menu chính của Biên tập Bản đồ." msgid "No maps available!" msgstr "Không có bản đồ nào!" -msgid "Warning" -msgstr "Cảnh báo" - #, fuzzy msgid "[%{pos}]: %{race} hero" msgstr "%{color} %{race} tướng" @@ -12267,5 +12264,8 @@ msgstr "" "https://github.com/ihhub/\n" "fheroes2/releases" +#~ msgid "Warning!" +#~ msgstr "Cảnh báo!" + #~ msgid "Dummy 1" #~ msgstr "Bù nhìn 1" From f3beb6f481caafd16375366cad23e5a20228ac95 Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Sat, 30 Nov 2024 05:27:43 +0300 Subject: [PATCH 07/30] Use the std::filesystem and support non-quite-POSIX-compliant filesystems (such as Amiga FS) (#9296) --- src/engine/logging.cpp | 10 +- src/engine/system.cpp | 95 +++---------------- src/engine/system.h | 10 +- .../campaign/campaign_scenariodata.cpp | 2 +- src/fheroes2/dialog/dialog_selectfile.cpp | 2 +- src/fheroes2/dialog/dialog_selectscenario.cpp | 2 +- .../editor/editor_save_map_window.cpp | 4 +- src/fheroes2/game/game_io.cpp | 2 +- src/fheroes2/maps/maps_fileinfo.cpp | 8 +- src/fheroes2/system/settings.cpp | 95 ++++++++++--------- src/fheroes2/system/settings.h | 4 +- src/tools/82m2wav.cpp | 6 +- src/tools/bin2txt.cpp | 6 +- src/tools/extractor.cpp | 6 +- src/tools/h2dmgr.cpp | 8 +- src/tools/icn2img.cpp | 6 +- src/tools/pal2img.cpp | 6 +- src/tools/til2img.cpp | 6 +- src/tools/xmi2midi.cpp | 6 +- 19 files changed, 116 insertions(+), 168 deletions(-) diff --git a/src/engine/logging.cpp b/src/engine/logging.cpp index a1dfb8f6d4a..a6dfcfe2080 100644 --- a/src/engine/logging.cpp +++ b/src/engine/logging.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2021 - 2022 * + * Copyright (C) 2021 - 2024 * * * * 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 * @@ -122,13 +122,15 @@ namespace Logging logFile.open( "fheroes2.log", std::ofstream::out ); #elif defined( _WIN32 ) const std::scoped_lock lock( logMutex ); - const std::string logPath( System::concatPath( System::GetConfigDirectory( "fheroes2" ), "fheroes2.log" ) ); - System::MakeDirectory( System::GetDirname( logPath ) ); + const std::string configDir = System::GetConfigDirectory( "fheroes2" ); - logFile.open( logPath, std::ofstream::out ); + System::MakeDirectory( configDir ); + + logFile.open( System::concatPath( configDir, "fheroes2.log" ), std::ofstream::out ); #elif defined( MACOS_APP_BUNDLE ) openlog( "fheroes2", LOG_CONS | LOG_NDELAY, LOG_USER ); + setlogmask( LOG_UPTO( LOG_WARNING ) ); #endif } diff --git a/src/engine/system.cpp b/src/engine/system.cpp index 89c4bb6e19a..9ad035a6e0e 100644 --- a/src/engine/system.cpp +++ b/src/engine/system.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -76,12 +75,6 @@ namespace { -#if defined( _WIN32 ) - constexpr char dirSep{ '\\' }; -#else - constexpr char dirSep{ '/' }; -#endif - #if !defined( __linux__ ) || defined( ANDROID ) std::string GetHomeDirectory( const std::string_view appName ) { @@ -96,7 +89,7 @@ namespace return storagePath; } - return { "." }; + return {}; #endif { const char * homeEnvPath = getenv( "HOME" ); @@ -106,7 +99,7 @@ namespace return System::concatPath( System::concatPath( homeEnvPath, "Library/Preferences" ), appName ); } - return { "." }; + return {}; #endif if ( homeEnvPath != nullptr ) { @@ -124,19 +117,10 @@ namespace } #endif - return { "." }; + return {}; } #endif - std::string_view trimTrailingSeparators( std::string_view path ) - { - while ( path.size() > 1 && path.back() == dirSep ) { - path.remove_suffix( 1 ); - } - - return path; - } - bool globMatch( const std::string_view string, const std::string_view wildcard ) { size_t stringIdx = 0; @@ -325,15 +309,7 @@ bool System::Unlink( const std::string_view path ) std::string System::concatPath( const std::string_view left, const std::string_view right ) { - // Avoid memory allocation while concatenating string. Allocate needed size at once. - std::string temp; - temp.reserve( left.size() + 1 + right.size() ); - - temp += left; - temp += dirSep; - temp += right; - - return temp; + return fsPathToString( std::filesystem::path{ left }.append( right ) ); } void System::appendOSSpecificDirectories( std::vector & directories ) @@ -367,7 +343,7 @@ std::string System::GetConfigDirectory( const std::string_view appName ) return concatPath( concatPath( homeEnv, ".config" ), appName ); } - return { "." }; + return {}; #else return GetHomeDirectory( appName ); #endif @@ -399,13 +375,13 @@ std::string System::GetDataDirectory( const std::string_view appName ) return concatPath( concatPath( homeEnv, ".local/share" ), appName ); } - return { "." }; + return {}; #elif defined( MACOS_APP_BUNDLE ) if ( const char * homeEnv = getenv( "HOME" ); homeEnv != nullptr ) { return concatPath( concatPath( homeEnv, "Library/Application Support" ), appName ); } - return { "." }; + return {}; #else return GetHomeDirectory( appName ); #endif @@ -418,60 +394,19 @@ std::string System::GetDataDirectory( const std::string_view appName ) return result; } -std::string System::GetDirname( std::string_view path ) +std::string System::GetParentDirectory( std::string_view path ) { - if ( path.empty() ) { - return { "." }; - } - - path = trimTrailingSeparators( path ); - - const size_t pos = path.rfind( dirSep ); - - if ( pos == std::string::npos ) { - return { "." }; - } - if ( pos == 0 ) { - return { std::initializer_list{ dirSep } }; - } - - // Trailing separators should already be trimmed - assert( pos != path.size() - 1 ); - - return std::string{ trimTrailingSeparators( path.substr( 0, pos ) ) }; + return fsPathToString( std::filesystem::path{ path }.parent_path() ); } -std::string System::GetBasename( std::string_view path ) +std::string System::GetFileName( std::string_view path ) { - if ( path.empty() ) { - return { "." }; - } - - path = trimTrailingSeparators( path ); - - const size_t pos = path.rfind( dirSep ); - - if ( pos == std::string::npos || ( pos == 0 && path.size() == 1 ) ) { - return std::string{ path }; - } - - // Trailing separators should already be trimmed - assert( pos != path.size() - 1 ); - - return std::string{ path.substr( pos + 1 ) }; + return fsPathToString( std::filesystem::path{ path }.filename() ); } std::string System::GetStem( const std::string_view path ) { - std::string res = GetBasename( path ); - - const size_t pos = res.rfind( '.' ); - - if ( pos != 0 && pos != std::string::npos ) { - res.resize( pos ); - } - - return res; + return fsPathToString( std::filesystem::path{ path }.stem() ); } bool System::IsFile( const std::string_view path ) @@ -513,15 +448,15 @@ bool System::IsDirectory( const std::string_view path ) bool System::GetCaseInsensitivePath( const std::string_view path, std::string & correctedPath ) { #if !defined( _WIN32 ) && !defined( ANDROID ) - static_assert( dirSep == '/', "The following code assumes the use of POSIX IEEE Std 1003.1-2001 pathnames, check the logic" ); - - // The following code is based on https://github.com/OneSadCookie/fcaseopen + // The following code is based on https://github.com/OneSadCookie/fcaseopen and assumes the use of POSIX IEEE Std 1003.1-2001 pathnames correctedPath.clear(); if ( path.empty() ) { return false; } + constexpr char dirSep{ '/' }; + std::unique_ptr dir( path.front() == dirSep ? opendir( "/" ) : opendir( "." ), closedir ); for ( const std::filesystem::path & pathItem : std::filesystem::path{ path } ) { diff --git a/src/engine/system.h b/src/engine/system.h index d178234b65c..c7daa1d0850 100644 --- a/src/engine/system.h +++ b/src/engine/system.h @@ -46,11 +46,17 @@ namespace System std::string concatPath( const std::string_view left, const std::string_view right ); void appendOSSpecificDirectories( std::vector & directories ); + + // Returns the path to the app settings directory (usually some user-specific directory). An empty string can be returned, + // which should be considered as the current directory. std::string GetConfigDirectory( const std::string_view appName ); + + // Returns the path to the app data directory (usually some user-specific directory). An empty string can be returned, + // which should be considered as the current directory. std::string GetDataDirectory( const std::string_view appName ); - std::string GetDirname( std::string_view path ); - std::string GetBasename( std::string_view path ); + std::string GetParentDirectory( std::string_view path ); + std::string GetFileName( std::string_view path ); std::string GetStem( const std::string_view path ); bool IsFile( const std::string_view path ); diff --git a/src/fheroes2/campaign/campaign_scenariodata.cpp b/src/fheroes2/campaign/campaign_scenariodata.cpp index fdca2611e3b..98272a5bffb 100644 --- a/src/fheroes2/campaign/campaign_scenariodata.cpp +++ b/src/fheroes2/campaign/campaign_scenariodata.cpp @@ -622,7 +622,7 @@ namespace const ListFiles files = Settings::FindFiles( "maps", "", false ); for ( const std::string & file : files ) { - result.try_emplace( StringLower( System::GetBasename( file ) ), file ); + result.try_emplace( StringLower( System::GetFileName( file ) ), file ); } return result; diff --git a/src/fheroes2/dialog/dialog_selectfile.cpp b/src/fheroes2/dialog/dialog_selectfile.cpp index 8badc05b085..641d45478fe 100644 --- a/src/fheroes2/dialog/dialog_selectfile.cpp +++ b/src/fheroes2/dialog/dialog_selectfile.cpp @@ -421,7 +421,7 @@ namespace std::string msg( _( "Are you sure you want to delete file:" ) ); msg.append( "\n\n" ); - msg.append( System::GetBasename( listbox.GetCurrent().filename ) ); + msg.append( System::GetFileName( listbox.GetCurrent().filename ) ); if ( Dialog::YES == fheroes2::showStandardTextMessage( _( "Warning" ), msg, Dialog::YES | Dialog::NO ) ) { System::Unlink( listbox.GetCurrent().filename ); diff --git a/src/fheroes2/dialog/dialog_selectscenario.cpp b/src/fheroes2/dialog/dialog_selectscenario.cpp index 97b0b9b3172..1604eb32140 100644 --- a/src/fheroes2/dialog/dialog_selectscenario.cpp +++ b/src/fheroes2/dialog/dialog_selectscenario.cpp @@ -226,7 +226,7 @@ namespace void renderFileName( const Maps::FileInfo & info, bool selected, const int32_t posX, const int32_t posY, fheroes2::Display & display ) { - fheroes2::Text text( System::GetBasename( info.filename ), selected ? fheroes2::FontType::normalYellow() : fheroes2::FontType::normalWhite() ); + fheroes2::Text text( System::GetFileName( info.filename ), selected ? fheroes2::FontType::normalYellow() : fheroes2::FontType::normalWhite() ); text.fitToOneRow( SCENARIO_LIST_MAP_NAME_WIDTH ); const int32_t xCoordinate = posX + SCENARIO_LIST_MAP_NAME_OFFSET_X; diff --git a/src/fheroes2/editor/editor_save_map_window.cpp b/src/fheroes2/editor/editor_save_map_window.cpp index e4200eed7c6..e404084c9f7 100644 --- a/src/fheroes2/editor/editor_save_map_window.cpp +++ b/src/fheroes2/editor/editor_save_map_window.cpp @@ -147,7 +147,7 @@ namespace void FileInfoListBox::RedrawItem( const Maps::FileInfo & info, int32_t posX, int32_t posY, bool current ) { - std::string mapFileName( System::GetBasename( info.filename ) ); + std::string mapFileName( System::GetFileName( info.filename ) ); assert( !mapFileName.empty() ); const fheroes2::FontType font = current ? fheroes2::FontType::normalYellow() : fheroes2::FontType::normalWhite(); @@ -402,7 +402,7 @@ namespace Editor std::string msg( _( "Are you sure you want to delete file:" ) ); msg.append( "\n\n" ); - msg.append( System::GetBasename( listbox.GetCurrent().filename ) ); + msg.append( System::GetFileName( listbox.GetCurrent().filename ) ); if ( Dialog::YES == fheroes2::showStandardTextMessage( _( "Warning" ), msg, Dialog::YES | Dialog::NO ) ) { System::Unlink( listbox.GetCurrent().filename ); diff --git a/src/fheroes2/game/game_io.cpp b/src/fheroes2/game/game_io.cpp index 298c7f73b27..b4e9c0d7bb0 100644 --- a/src/fheroes2/game/game_io.cpp +++ b/src/fheroes2/game/game_io.cpp @@ -267,7 +267,7 @@ fheroes2::GameMode Game::Load( const std::string & filePath ) } // Settings should contain the full path to the current map file, if this map is available - conf.getCurrentMapInfo().filename = Settings::GetLastFile( "maps", System::GetBasename( conf.getCurrentMapInfo().filename ) ); + conf.getCurrentMapInfo().filename = Settings::GetLastFile( "maps", System::GetFileName( conf.getCurrentMapInfo().filename ) ); if ( !conf.loadedFileLanguage().empty() && conf.loadedFileLanguage() != "en" && conf.loadedFileLanguage() != conf.getGameLanguage() ) { std::string warningMessage( _( "This saved game is localized to '" ) ); diff --git a/src/fheroes2/maps/maps_fileinfo.cpp b/src/fheroes2/maps/maps_fileinfo.cpp index c882d3de853..9ed4b74c2e9 100644 --- a/src/fheroes2/maps/maps_fileinfo.cpp +++ b/src/fheroes2/maps/maps_fileinfo.cpp @@ -125,7 +125,7 @@ namespace } } - uniqueMaps.try_emplace( System::GetBasename( mapFile ), std::move( fi ) ); + uniqueMaps.try_emplace( System::GetFileName( mapFile ), std::move( fi ) ); } MapsFileInfoList result; @@ -635,8 +635,8 @@ bool Maps::FileInfo::WinsCompAlsoWins() const OStreamBase & Maps::operator<<( OStreamBase & stream, const FileInfo & fi ) { - // Only the basename of map filename (fi.file) is saved - stream << System::GetBasename( fi.filename ) << fi.name << fi.description << fi.width << fi.height << fi.difficulty << static_cast( maxNumOfPlayers ); + // Only the filename part of the path to the map file is saved + stream << System::GetFileName( fi.filename ) << fi.name << fi.description << fi.width << fi.height << fi.difficulty << static_cast( maxNumOfPlayers ); static_assert( std::is_same_v> ); static_assert( std::is_same_v> ); @@ -655,7 +655,7 @@ IStreamBase & Maps::operator>>( IStreamBase & stream, FileInfo & fi ) { uint8_t kingdommax = 0; - // Only the basename of map filename (fi.file) is loaded + // Only the filename part of the path to the map file is loaded stream >> fi.filename >> fi.name >> fi.description >> fi.width >> fi.height >> fi.difficulty >> kingdommax; static_assert( std::is_same_v> ); diff --git a/src/fheroes2/system/settings.cpp b/src/fheroes2/system/settings.cpp index c2315b0981a..20d5e9636ed 100644 --- a/src/fheroes2/system/settings.cpp +++ b/src/fheroes2/system/settings.cpp @@ -553,70 +553,75 @@ void Settings::setEditorPassability( const bool enable ) } } -void Settings::SetProgramPath( const char * argv0 ) +void Settings::SetProgramPath( const char * path ) { - if ( argv0 ) - path_program = argv0; + if ( path == nullptr ) { + return; + } + + _programPath = path; } const std::vector & Settings::GetRootDirs() { - static std::vector dirs; - if ( !dirs.empty() ) { - return dirs; - } + static const std::vector rootDirs = []() { + std::vector result; #ifdef FHEROES2_DATA - // Macro-defined path. - dirs.emplace_back( EXPANDDEF( FHEROES2_DATA ) ); + // Macro-defined path. + result.emplace_back( EXPANDDEF( FHEROES2_DATA ) ); #endif - // Environment variable. - const char * dataEnvPath = getenv( "FHEROES2_DATA" ); - if ( dataEnvPath != nullptr && std::find( dirs.begin(), dirs.end(), dataEnvPath ) == dirs.end() ) { - dirs.emplace_back( dataEnvPath ); - } + // Environment variable. + const char * dataEnvPath = getenv( "FHEROES2_DATA" ); + if ( dataEnvPath != nullptr && std::find( result.begin(), result.end(), dataEnvPath ) == result.end() ) { + result.emplace_back( dataEnvPath ); + } - // The location of the application. - std::string appPath = System::GetDirname( Settings::Get().path_program ); - if ( !appPath.empty() && std::find( dirs.begin(), dirs.end(), appPath ) == dirs.end() ) { - dirs.emplace_back( std::move( appPath ) ); - } + // The location of the application. + std::string appPath = System::GetParentDirectory( Settings::Get()._programPath ); + if ( std::find( result.begin(), result.end(), appPath ) == result.end() ) { + result.emplace_back( std::move( appPath ) ); + } - // OS specific directories. - System::appendOSSpecificDirectories( dirs ); + // OS specific directories. + System::appendOSSpecificDirectories( result ); #if defined( MACOS_APP_BUNDLE ) - // macOS app bundle Resources directory - char resourcePath[PATH_MAX]; + // macOS app bundle Resources directory + char resourcePath[PATH_MAX]; - CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL( CFBundleGetMainBundle() ); - if ( CFURLGetFileSystemRepresentation( resourcesURL, TRUE, reinterpret_cast( resourcePath ), PATH_MAX ) - && std::find( dirs.begin(), dirs.end(), resourcePath ) == dirs.end() ) { - dirs.emplace_back( resourcePath ); - } - else { - ERROR_LOG( "Unable to get app bundle path" ) - } - CFRelease( resourcesURL ); + CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL( CFBundleGetMainBundle() ); + if ( CFURLGetFileSystemRepresentation( resourcesURL, TRUE, reinterpret_cast( resourcePath ), PATH_MAX ) + && std::find( result.begin(), result.end(), resourcePath ) == result.end() ) { + result.emplace_back( resourcePath ); + } + else { + ERROR_LOG( "Unable to get app bundle path" ) + } + CFRelease( resourcesURL ); #endif - // User config directory. - std::string configPath = System::GetConfigDirectory( "fheroes2" ); - if ( !configPath.empty() && std::find( dirs.begin(), dirs.end(), configPath ) == dirs.end() ) { - dirs.emplace_back( std::move( configPath ) ); - } + // User config directory. + std::string configPath = System::GetConfigDirectory( "fheroes2" ); + if ( std::find( result.begin(), result.end(), configPath ) == result.end() ) { + result.emplace_back( std::move( configPath ) ); + } - // User data directory. - std::string dataPath = System::GetDataDirectory( "fheroes2" ); - if ( !dataPath.empty() && std::find( dirs.begin(), dirs.end(), dataPath ) == dirs.end() ) { - dirs.emplace_back( std::move( dataPath ) ); - } + // User data directory. + std::string dataPath = System::GetDataDirectory( "fheroes2" ); + if ( std::find( result.begin(), result.end(), dataPath ) == result.end() ) { + result.emplace_back( std::move( dataPath ) ); + } + + // Remove all paths that are not directories. Empty path should not be removed because it in fact means the current directory. + result.erase( std::remove_if( result.begin(), result.end(), []( const std::string & path ) { return !path.empty() && !System::IsDirectory( path ); } ), + result.end() ); - // Remove all paths that are not directories. - dirs.erase( std::remove_if( dirs.begin(), dirs.end(), []( const std::string & path ) { return !System::IsDirectory( path ); } ), dirs.end() ); + return result; + }(); - return dirs; + return rootDirs; } ListFiles Settings::FindFiles( const std::string & prefixDir, const std::string & fileNameFilter, const bool exactMatch ) diff --git a/src/fheroes2/system/settings.h b/src/fheroes2/system/settings.h index e185c3386b2..14f5edd8d36 100644 --- a/src/fheroes2/system/settings.h +++ b/src/fheroes2/system/settings.h @@ -337,7 +337,7 @@ class Settings _viewWorldZoomLevel = zoomLevel; } - void SetProgramPath( const char * ); + void SetProgramPath( const char * path ); static std::string GetVersion(); @@ -364,7 +364,7 @@ class Settings fheroes2::ResolutionInfo _resolutionInfo; int _gameDifficulty; - std::string path_program; + std::string _programPath; std::string _gameLanguage; // Not saved in the config file or savefile diff --git a/src/tools/82m2wav.cpp b/src/tools/82m2wav.cpp index 454b190f284..10eae0cf6fe 100644 --- a/src/tools/82m2wav.cpp +++ b/src/tools/82m2wav.cpp @@ -47,10 +47,10 @@ namespace int main( int argc, char ** argv ) { if ( argc < 3 ) { - const std::string baseName = System::GetBasename( argv[0] ); + const std::string toolName = System::GetFileName( argv[0] ); - std::cerr << baseName << " converts the specified 82M file(s) to WAV format." << std::endl - << "Syntax: " << baseName << " dst_dir input_file.82m ..." << std::endl; + std::cerr << toolName << " converts the specified 82M file(s) to WAV format." << std::endl + << "Syntax: " << toolName << " dst_dir input_file.82m ..." << std::endl; return EXIT_FAILURE; } diff --git a/src/tools/bin2txt.cpp b/src/tools/bin2txt.cpp index 4e04a3ba049..c65459dea9c 100644 --- a/src/tools/bin2txt.cpp +++ b/src/tools/bin2txt.cpp @@ -41,10 +41,10 @@ namespace int main( int argc, char ** argv ) { if ( argc < 3 ) { - const std::string baseName = System::GetBasename( argv[0] ); + const std::string toolName = System::GetFileName( argv[0] ); - std::cerr << baseName << " extracts various data from monster animation files." << std::endl - << "Syntax: " << baseName << " dst_dir input_file.bin ..." << std::endl; + std::cerr << toolName << " extracts various data from monster animation files." << std::endl + << "Syntax: " << toolName << " dst_dir input_file.bin ..." << std::endl; return EXIT_FAILURE; } diff --git a/src/tools/extractor.cpp b/src/tools/extractor.cpp index 94c5d54d418..a3e262eae00 100644 --- a/src/tools/extractor.cpp +++ b/src/tools/extractor.cpp @@ -57,10 +57,10 @@ namespace int main( int argc, char ** argv ) { if ( argc < 3 ) { - const std::string baseName = System::GetBasename( argv[0] ); + const std::string toolName = System::GetFileName( argv[0] ); - std::cerr << baseName << " extracts the contents of the specified AGG file(s)." << std::endl - << "Syntax: " << baseName << " dst_dir input_file.agg ..." << std::endl; + std::cerr << toolName << " extracts the contents of the specified AGG file(s)." << std::endl + << "Syntax: " << toolName << " dst_dir input_file.agg ..." << std::endl; return EXIT_FAILURE; } diff --git a/src/tools/h2dmgr.cpp b/src/tools/h2dmgr.cpp index 52b55b68f98..c98310232f8 100644 --- a/src/tools/h2dmgr.cpp +++ b/src/tools/h2dmgr.cpp @@ -62,11 +62,11 @@ namespace void printUsage( char ** argv ) { - const std::string baseName = System::GetBasename( argv[0] ); + const std::string toolName = System::GetFileName( argv[0] ); - std::cerr << baseName << " manages the contents of the specified H2D file(s)." << std::endl - << "Syntax: " << baseName << " extract dst_dir palette_file.pal input_file.h2d ..." << std::endl - << " " << baseName << " combine target_file.h2d palette_file.pal input_file ..." << std::endl; + std::cerr << toolName << " manages the contents of the specified H2D file(s)." << std::endl + << "Syntax: " << toolName << " extract dst_dir palette_file.pal input_file.h2d ..." << std::endl + << " " << toolName << " combine target_file.h2d palette_file.pal input_file ..." << std::endl; } bool loadPalette( const char * paletteFileName ) diff --git a/src/tools/icn2img.cpp b/src/tools/icn2img.cpp index 396c48748bb..1217a48f250 100644 --- a/src/tools/icn2img.cpp +++ b/src/tools/icn2img.cpp @@ -49,11 +49,11 @@ namespace int main( int argc, char ** argv ) { if ( argc < 4 ) { - const std::string baseName = System::GetBasename( argv[0] ); + const std::string toolName = System::GetFileName( argv[0] ); - std::cerr << baseName << " extracts sprites in BMP or PNG format (if supported) and their offsets from the specified ICN file(s) using the specified palette." + std::cerr << toolName << " extracts sprites in BMP or PNG format (if supported) and their offsets from the specified ICN file(s) using the specified palette." << std::endl - << "Syntax: " << baseName << " dst_dir palette_file.pal input_file.icn ..." << std::endl; + << "Syntax: " << toolName << " dst_dir palette_file.pal input_file.icn ..." << std::endl; return EXIT_FAILURE; } diff --git a/src/tools/pal2img.cpp b/src/tools/pal2img.cpp index 08fead5d810..7812a0f3089 100644 --- a/src/tools/pal2img.cpp +++ b/src/tools/pal2img.cpp @@ -39,10 +39,10 @@ namespace int main( int argc, char ** argv ) { if ( argc != 3 ) { - const std::string baseName = System::GetBasename( argv[0] ); + const std::string toolName = System::GetFileName( argv[0] ); - std::cerr << baseName << " generates an image with colors based on a provided palette file." << std::endl - << "Syntax: " << baseName << " palette_file.pal output.bmp|output.png" << std::endl; + std::cerr << toolName << " generates an image with colors based on a provided palette file." << std::endl + << "Syntax: " << toolName << " palette_file.pal output.bmp|output.png" << std::endl; return EXIT_FAILURE; } diff --git a/src/tools/til2img.cpp b/src/tools/til2img.cpp index af4c9db913e..3e7653e8881 100644 --- a/src/tools/til2img.cpp +++ b/src/tools/til2img.cpp @@ -47,10 +47,10 @@ namespace int main( int argc, char ** argv ) { if ( argc < 4 ) { - const std::string baseName = System::GetBasename( argv[0] ); + const std::string toolName = System::GetFileName( argv[0] ); - std::cerr << baseName << " extracts sprites in BMP or PNG format (if supported) from the specified TIL file(s) using the specified palette." << std::endl - << "Syntax: " << baseName << " dst_dir palette_file.pal input_file.til ..." << std::endl; + std::cerr << toolName << " extracts sprites in BMP or PNG format (if supported) from the specified TIL file(s) using the specified palette." << std::endl + << "Syntax: " << toolName << " dst_dir palette_file.pal input_file.til ..." << std::endl; return EXIT_FAILURE; } diff --git a/src/tools/xmi2midi.cpp b/src/tools/xmi2midi.cpp index 598cac3f05e..683061cc9a1 100644 --- a/src/tools/xmi2midi.cpp +++ b/src/tools/xmi2midi.cpp @@ -40,10 +40,10 @@ int main( int argc, char ** argv ) { if ( argc < 3 ) { - const std::string baseName = System::GetBasename( argv[0] ); + const std::string toolName = System::GetFileName( argv[0] ); - std::cerr << baseName << " converts the specified XMI file(s) to MIDI format." << std::endl - << "Syntax: " << baseName << " dst_dir input_file.xmi ..." << std::endl; + std::cerr << toolName << " converts the specified XMI file(s) to MIDI format." << std::endl + << "Syntax: " << toolName << " dst_dir input_file.xmi ..." << std::endl; return EXIT_FAILURE; } From 2b4f5e3ae3aeaaa2dcb9fd482ee15e76c50c5ead Mon Sep 17 00:00:00 2001 From: "Sergei Ivanov (Districh)" <113276641+Districh-ru@users.noreply.github.com> Date: Sun, 1 Dec 2024 13:51:57 +0300 Subject: [PATCH 08/30] Update PS Vita touchpad ID after it has changed in SDL2 v2.30.7 (#9307) --- src/engine/localevent.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/engine/localevent.cpp b/src/engine/localevent.cpp index 1cf1b2b3049..e61d4bd310d 100644 --- a/src/engine/localevent.cpp +++ b/src/engine/localevent.cpp @@ -1034,9 +1034,23 @@ namespace EventProcessing static void onTouchEvent( LocalEvent & eventHandler, const SDL_TouchFingerEvent & event ) { #if defined( TARGET_PS_VITA ) - if ( event.touchId != 0 ) { - // Ignore rear touchpad on PS Vita - return; + { + // PS Vita has two touchpads: front and rear. The ID of the front touchpad must match the value of + // 'SDL_TouchID' used in the 'SDL_AddTouch()' call in the 'VITA_InitTouch()' function in this SDL2 + // source file: video/vita/SDL_vitatouch.c. + constexpr SDL_TouchID frontTouchpadDeviceID + { +#if SDL_VERSION_ATLEAST( 2, 30, 7 ) + 1 +#else + 0 +#endif + }; + + // Use only front touchpad on PS Vita. + if ( event.touchId != frontTouchpadDeviceID ) { + return; + } } #endif From 25d8607c71ab2824bbcb1146190ed41488af1e19 Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Tue, 3 Dec 2024 03:09:09 +0300 Subject: [PATCH 09/30] Speed up getting a list of files on PS Vita (#9310) --- Makefile.vita | 5 ++- src/engine/dir.cpp | 87 ++++++++++++++++++++++++++++++++++++++++--- src/engine/dir.h | 8 ++-- src/engine/system.cpp | 4 +- 4 files changed, 90 insertions(+), 14 deletions(-) diff --git a/Makefile.vita b/Makefile.vita index 24a4a9dd1bf..6b2be96c063 100644 --- a/Makefile.vita +++ b/Makefile.vita @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2021 - 2023 # +# Copyright (C) 2021 - 2024 # # # # 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 # @@ -29,6 +29,7 @@ MAKE := make PROJECT_TITLE := fheroes2 PROJECT_TITLEID := FHOMM0002 +PROJECT_VERSION := $(file < version.txt) .PHONY: all clean $(TARGET).vpk param.sfo $(TARGET).elf translations @@ -56,7 +57,7 @@ eboot.bin: $(TARGET).velf vita-make-fself $(TARGET).velf eboot.bin param.sfo: - vita-mksfoex -s TITLE_ID="$(PROJECT_TITLEID)" "$(PROJECT_TITLE)" param.sfo + vita-mksfoex -s TITLE_ID="$(PROJECT_TITLEID)" -s APP_VER="$(PROJECT_VERSION)" "$(PROJECT_TITLE)" param.sfo $(TARGET).velf: $(TARGET).elf arm-vita-eabi-strip -g $< diff --git a/src/engine/dir.cpp b/src/engine/dir.cpp index ba7ed8ab4a7..27337dc02b3 100644 --- a/src/engine/dir.cpp +++ b/src/engine/dir.cpp @@ -23,9 +23,14 @@ #include "dir.h" +#include + +#if defined( TARGET_PS_VITA ) +#include +#else #include #include -#include +#endif #if defined( _WIN32 ) #include @@ -57,7 +62,7 @@ namespace return ( strCmp( filenamePtr, filter.c_str() ) == 0 ); } - void getFilesFromDirectory( const std::string_view path, const std::string & filter, const bool needExactMatch, ListFiles & files ) + void getFilesFromDirectory( const std::string & path, const std::string & filter, const bool needExactMatch, ListFiles & files ) { std::string correctedPath; if ( !System::GetCaseInsensitivePath( path, correctedPath ) ) { @@ -70,6 +75,62 @@ namespace auto * const strCmp = strcasecmp; #endif +#if defined( TARGET_PS_VITA ) + // On PS Vita, getting a list of files using std::filesystem for some reason works much slower than using the native file system API + class SceUIDWrapper + { + public: + SceUIDWrapper( const std::string & path ) + : uid( sceIoDopen( path.c_str() ) ) + {} + + SceUIDWrapper( const SceUIDWrapper & ) = delete; + + ~SceUIDWrapper() + { + if ( !isValid() ) { + return; + } + + sceIoDclose( uid ); + } + + SceUIDWrapper & operator=( const SceUIDWrapper & ) = delete; + + bool isValid() const + { + return uid >= 0; + } + + SceUID get() const + { + return uid; + } + + private: + const SceUID uid; + }; + + const SceUIDWrapper uid( path ); + if ( !uid.isValid() ) { + return; + } + + SceIoDirent entry; + + while ( sceIoDread( uid.get(), &entry ) > 0 ) { + // Ensure that this directory entry is a regular file + if ( !SCE_S_ISREG( entry.d_stat.st_mode ) ) { + continue; + } + + if ( !nameFilter( entry.d_name, needExactMatch, filter, strCmp ) ) { + continue; + } + + files.emplace_back( System::concatPath( path, entry.d_name ) ); + } +#else std::error_code ec; // Using the non-throwing overload @@ -87,6 +148,7 @@ namespace files.emplace_back( System::fsPathToString( entryPath ) ); } +#endif } } @@ -97,17 +159,30 @@ void ListFiles::Append( ListFiles && files ) } } -void ListFiles::ReadDir( const std::string_view path, const std::string & filter ) +void ListFiles::ReadDir( const std::string & path, const std::string & filter ) { getFilesFromDirectory( path, filter, false, *this ); } -void ListFiles::FindFileInDir( const std::string_view path, const std::string & fileName ) +void ListFiles::FindFileInDir( const std::string_view path, const std::string_view fileName ) { - getFilesFromDirectory( path, fileName, true, *this ); + std::string correctedFilePath; + // If the file system is case-sensitive, then here we will get the actual file path using the case-insensitive + // search (if such a file exists). If the file system is case-insensitive, we will just get the passed path to + // the file back intact. + if ( !System::GetCaseInsensitivePath( System::concatPath( path, fileName ), correctedFilePath ) ) { + return; + } + + // In case the file system is case-insensitive, we additionally check the file for existence. + if ( !System::IsFile( correctedFilePath ) ) { + return; + } + + emplace_back( std::move( correctedFilePath ) ); } -bool ListFiles::IsEmpty( const std::string_view path, const std::string & filter ) +bool ListFiles::IsEmpty( const std::string & path, const std::string & filter ) { ListFiles list; list.ReadDir( path, filter ); diff --git a/src/engine/dir.h b/src/engine/dir.h index 4ec343dec18..bc268f3a737 100644 --- a/src/engine/dir.h +++ b/src/engine/dir.h @@ -32,13 +32,13 @@ struct ListFiles : public std::list void Append( ListFiles && files ); // Adds files from the 'path' directory ending in 'filter' to the list, case-insensitive. - void ReadDir( const std::string_view path, const std::string & filter ); + void ReadDir( const std::string & path, const std::string & filter ); - // Adds files from the 'path' directory with names matching 'fileName' to the list, case-insensitive. - void FindFileInDir( const std::string_view path, const std::string & fileName ); + // Adds the first found file from the 'path' directory with a name matching 'fileName' to the list, case-insensitive. + void FindFileInDir( const std::string_view path, const std::string_view fileName ); // Returns true if there are no files in the 'path' directory with names ending in 'filter', case-insensitive, otherwise returns false. - static bool IsEmpty( const std::string_view path, const std::string & filter ); + static bool IsEmpty( const std::string & path, const std::string & filter ); }; #endif diff --git a/src/engine/system.cpp b/src/engine/system.cpp index 9ad035a6e0e..5cc24979b00 100644 --- a/src/engine/system.cpp +++ b/src/engine/system.cpp @@ -42,7 +42,7 @@ #include #endif -#if !defined( _WIN32 ) && !defined( ANDROID ) +#if !defined( _WIN32 ) && !defined( ANDROID ) && !defined( TARGET_PS_VITA ) #include #include #endif @@ -447,7 +447,7 @@ bool System::IsDirectory( const std::string_view path ) bool System::GetCaseInsensitivePath( const std::string_view path, std::string & correctedPath ) { -#if !defined( _WIN32 ) && !defined( ANDROID ) +#if !defined( _WIN32 ) && !defined( ANDROID ) && !defined( TARGET_PS_VITA ) // The following code is based on https://github.com/OneSadCookie/fcaseopen and assumes the use of POSIX IEEE Std 1003.1-2001 pathnames correctedPath.clear(); From 16237fec4e5f3ada74c3de8ff86b0fc79fc5dd17 Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Tue, 3 Dec 2024 19:59:53 +0800 Subject: [PATCH 10/30] Synchronize maximum allowed number of characters for input and Virtual Keyboard (#9281) --- src/fheroes2/dialog/dialog.h | 3 +++ src/fheroes2/dialog/dialog_selectcount.cpp | 4 +-- src/fheroes2/dialog/dialog_selectfile.cpp | 25 ++++++++++--------- .../editor/editor_save_map_window.cpp | 2 +- src/fheroes2/gui/ui_keyboard.cpp | 20 ++++++++++----- src/fheroes2/gui/ui_keyboard.h | 5 +++- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/fheroes2/dialog/dialog.h b/src/fheroes2/dialog/dialog.h index 23d38abf8c4..a6ba0a1aaf4 100644 --- a/src/fheroes2/dialog/dialog.h +++ b/src/fheroes2/dialog/dialog.h @@ -117,8 +117,11 @@ namespace Dialog int SelectSkillFromArena(); bool SelectCount( std::string header, const int32_t min, const int32_t max, int32_t & selectedValue, const int32_t step = 1, const fheroes2::DialogElement * uiElement = nullptr ); + + // If character limit is set to 0, then no limitation for the resulting string will be applied. bool inputString( const fheroes2::TextBase & title, const fheroes2::TextBase & body, std::string & result, const size_t charLimit, const bool isMultiLine, const std::optional & textLanguage ); + Troop RecruitMonster( const Monster & monster0, const uint32_t available, const bool allowDowngradedMonster, const int32_t windowOffsetY ); void DwellingInfo( const Monster &, const uint32_t available ); int ArmyInfo( const Troop & troop, int flags, bool isReflected = false, const int32_t windowOffsetY = 0 ); diff --git a/src/fheroes2/dialog/dialog_selectcount.cpp b/src/fheroes2/dialog/dialog_selectcount.cpp index 4df8188282a..6592d2f5dc0 100644 --- a/src/fheroes2/dialog/dialog_selectcount.cpp +++ b/src/fheroes2/dialog/dialog_selectcount.cpp @@ -277,10 +277,10 @@ bool Dialog::inputString( const fheroes2::TextBase & title, const fheroes2::Text if ( le.MouseClickLeft( buttonVirtualKB.area() ) || ( isInGameKeyboardRequired && le.MouseClickLeft( textInputArea ) ) ) { if ( textLanguage.has_value() ) { const fheroes2::LanguageSwitcher switcher( textLanguage.value() ); - fheroes2::openVirtualKeyboard( result ); + fheroes2::openVirtualKeyboard( result, charLimit ); } else { - fheroes2::openVirtualKeyboard( result ); + fheroes2::openVirtualKeyboard( result, charLimit ); } if ( charLimit > 0 && result.size() > charLimit ) { diff --git a/src/fheroes2/dialog/dialog_selectfile.cpp b/src/fheroes2/dialog/dialog_selectfile.cpp index 641d45478fe..221e98a807b 100644 --- a/src/fheroes2/dialog/dialog_selectfile.cpp +++ b/src/fheroes2/dialog/dialog_selectfile.cpp @@ -102,20 +102,19 @@ namespace text.draw( dstx + 105 - text.width() / 2, dsty, output ); } - bool redrawTextInputField( const std::string & filename, const fheroes2::Rect & field, const bool isEditing ) + void redrawTextInputField( const std::string & filename, const fheroes2::Rect & field, const bool isEditing ) { if ( filename.empty() ) { - return false; + return; } fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Text currentFilename( filename, isEditing ? fheroes2::FontType::normalWhite() : fheroes2::FontType::normalYellow() ); - const int32_t initialTextWidth = currentFilename.width(); - currentFilename.fitToOneRow( maxFileNameWidth ); - currentFilename.draw( field.x + 4 + ( maxFileNameWidth - currentFilename.width() ) / 2, field.y + 4, display ); + // Do not ignore spaces at the end. + currentFilename.fitToOneRow( maxFileNameWidth, false ); - return ( initialTextWidth + 10 ) > maxFileNameWidth; + currentFilename.draw( field.x + 4 + ( maxFileNameWidth - currentFilename.width() ) / 2, field.y + 4, display ); } class FileInfoListBox : public Interface::ListBox @@ -199,7 +198,7 @@ namespace dsty += 2; fheroes2::Text text{ std::move( savname ), font }; - text.fitToOneRow( maxFileNameWidth ); + text.fitToOneRow( maxFileNameWidth, false ); text.draw( dstx + 4 + ( maxFileNameWidth - text.width() ) / 2, dsty, display ); redrawDateTime( display, info.timestamp, dstx + maxFileNameWidth + 9, dsty, font ); @@ -387,13 +386,14 @@ namespace display.render( background.totalArea() ); std::string result; - bool isTextLimit = false; std::string lastSelectedSaveFileName; const bool isInGameKeyboardRequired = System::isVirtualKeyboardSupported(); bool isCursorVisible = true; + const size_t lengthLimit{ 255 }; + LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( !isEditing || Game::isDelayNeeded( { Game::DelayType::CURSOR_BLINK_DELAY } ) ) && result.empty() ) { @@ -455,7 +455,7 @@ namespace } else if ( isEditing ) { if ( le.MouseClickLeft( buttonVirtualKB->area() ) || ( isInGameKeyboardRequired && le.MouseClickLeft( textInputRoi ) ) ) { - fheroes2::openVirtualKeyboard( filename ); + fheroes2::openVirtualKeyboard( filename, lengthLimit ); charInsertPos = filename.size(); listbox.Unselect(); @@ -469,7 +469,7 @@ namespace } else if ( !filename.empty() && le.MouseClickLeft( textInputRoi ) ) { const fheroes2::Text text( filename, fheroes2::FontType::normalWhite() ); - const int32_t textStartOffsetX = isTextLimit ? 8 : ( textInputRoi.width - text.width() ) / 2; + const int32_t textStartOffsetX = std::max( 0, ( textInputRoi.width - text.width() ) / 2 ); charInsertPos = fheroes2::getTextInputCursorPosition( filename, fheroes2::FontType::normalWhite(), charInsertPos, le.getMouseCursorPos().x, textInputRoi.x + textStartOffsetX ); @@ -478,7 +478,8 @@ namespace needRedraw = true; } else if ( !listboxEvent && le.isAnyKeyPressed() - && ( !isTextLimit || fheroes2::Key::KEY_BACKSPACE == le.getPressedKeyValue() || fheroes2::Key::KEY_DELETE == le.getPressedKeyValue() ) + && ( filename.size() < lengthLimit || fheroes2::Key::KEY_BACKSPACE == le.getPressedKeyValue() + || fheroes2::Key::KEY_DELETE == le.getPressedKeyValue() ) && le.getPressedKeyValue() != fheroes2::Key::KEY_UP && le.getPressedKeyValue() != fheroes2::Key::KEY_DOWN ) { charInsertPos = InsertKeySym( filename, charInsertPos, le.getPressedKeyValue(), LocalEvent::getCurrentKeyModifiers() ); @@ -533,7 +534,7 @@ namespace textInputAndDateBackground.restore(); if ( isEditing ) { - isTextLimit = redrawTextInputField( insertCharToString( filename, charInsertPos, isCursorVisible ? '_' : '\x7F' ), textInputRoi, true ); + redrawTextInputField( insertCharToString( filename, charInsertPos, isCursorVisible ? '_' : '\x7F' ), textInputRoi, true ); redrawDateTime( display, std::time( nullptr ), dateTimeoffsetX, textInputRoi.y + 4, fheroes2::FontType::normalWhite() ); } else if ( isListboxSelected ) { diff --git a/src/fheroes2/editor/editor_save_map_window.cpp b/src/fheroes2/editor/editor_save_map_window.cpp index e404084c9f7..84a29028bfc 100644 --- a/src/fheroes2/editor/editor_save_map_window.cpp +++ b/src/fheroes2/editor/editor_save_map_window.cpp @@ -353,7 +353,7 @@ namespace Editor { // TODO: allow to use other languages once we add support of filesystem language support. const fheroes2::LanguageSwitcher switcher( fheroes2::SupportedLanguage::English ); - fheroes2::openVirtualKeyboard( fileName ); + fheroes2::openVirtualKeyboard( fileName, 255 ); } charInsertPos = fileName.size(); diff --git a/src/fheroes2/gui/ui_keyboard.cpp b/src/fheroes2/gui/ui_keyboard.cpp index 5ad728c09ea..e12b137fa13 100644 --- a/src/fheroes2/gui/ui_keyboard.cpp +++ b/src/fheroes2/gui/ui_keyboard.cpp @@ -22,9 +22,9 @@ #include #include -#include #include #include +#include #include #include #include @@ -112,9 +112,10 @@ namespace class KeyboardRenderer { public: - KeyboardRenderer( fheroes2::Display & output, std::string & info, const bool evilInterface ) + KeyboardRenderer( fheroes2::Display & output, std::string & info, const size_t lengthLimit, const bool evilInterface ) : _output( output ) , _info( info ) + , _lengthLimit( lengthLimit ) , _isEvilInterface( evilInterface ) , _cursorPosition( info.size() ) { @@ -171,7 +172,7 @@ namespace void insertCharacter( const char character ) { - if ( _info.size() >= 255 ) { + if ( _info.size() >= _lengthLimit ) { // Do not add more characters as the string is already long enough. return; } @@ -242,6 +243,7 @@ namespace private: fheroes2::Display & _output; std::string & _info; + size_t _lengthLimit{ 255 }; std::unique_ptr _window; const bool _isEvilInterface{ false }; bool _isCursorVisible{ true }; @@ -785,8 +787,13 @@ namespace namespace fheroes2 { - void openVirtualKeyboard( std::string & output ) + void openVirtualKeyboard( std::string & output, size_t lengthLimit ) { + if ( lengthLimit == 0 ) { + // A string longer than 64KB is extremely impossible. + lengthLimit = std::numeric_limits::max(); + } + SupportedLanguage language = SupportedLanguage::English; DialogAction action = DialogAction::DoNothing; LayoutType layoutType = LayoutType::LowerCase; @@ -796,7 +803,7 @@ namespace fheroes2 language = lastSelectedLanguage; } - KeyboardRenderer renderer( Display::instance(), output, Settings::Get().isEvilInterfaceEnabled() ); + KeyboardRenderer renderer( Display::instance(), output, lengthLimit, Settings::Get().isEvilInterfaceEnabled() ); while ( action != DialogAction::Close ) { action = processVirtualKeyboardEvent( layoutType, language, isSupportedForLanguageSwitching( currentGameLanguage ), renderer ); @@ -845,7 +852,8 @@ namespace fheroes2 std::string strValue = std::to_string( output ); DialogAction action = DialogAction::DoNothing; - KeyboardRenderer renderer( Display::instance(), strValue, Settings::Get().isEvilInterfaceEnabled() ); + // Lets limit to 11 digits: minus and 10 digits for INT32_MIN + KeyboardRenderer renderer( Display::instance(), strValue, 10, Settings::Get().isEvilInterfaceEnabled() ); while ( action != DialogAction::Close ) { action = processVirtualKeyboardEvent( LayoutType::Numeric, SupportedLanguage::English, false, renderer ); diff --git a/src/fheroes2/gui/ui_keyboard.h b/src/fheroes2/gui/ui_keyboard.h index 70ca195a814..bfd19b771cc 100644 --- a/src/fheroes2/gui/ui_keyboard.h +++ b/src/fheroes2/gui/ui_keyboard.h @@ -20,6 +20,7 @@ #pragma once +#include #include #include @@ -28,7 +29,9 @@ namespace fheroes2 // fheroes2 does not support UTF-8 so on mobile devices with virtual keyboard it might be a big problem. // As a solution we should utilize an in-game virtual keyboard which supports all code pages available by the engine. // The default language in the keyboard is English. - void openVirtualKeyboard( std::string & output ); + // + // If length limit is set to 0 then no limit will be applied. + void openVirtualKeyboard( std::string & output, size_t lengthLimit ); void openVirtualNumpad( int32_t & output, const int32_t minValue = INT32_MIN, const int32_t maxValue = INT32_MAX ); } From 635ece6bb85ca9adff514fb83612b9d03aa0e29e Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Tue, 3 Dec 2024 15:14:34 +0300 Subject: [PATCH 11/30] Improve the castle reinforcement logic for AI (implement the long-standing TODO comment) (#9283) --- src/fheroes2/ai/ai_planner.h | 5 +- src/fheroes2/ai/ai_planner_castle.cpp | 219 +++++++++++++++++++++++-- src/fheroes2/ai/ai_planner_hero.cpp | 6 +- src/fheroes2/ai/ai_planner_kingdom.cpp | 90 +--------- src/fheroes2/army/army.cpp | 13 +- src/fheroes2/battle/battle.h | 8 +- src/fheroes2/battle/battle_troop.cpp | 31 ++-- src/fheroes2/battle/battle_troop.h | 2 +- src/fheroes2/castle/castle.cpp | 45 ----- src/fheroes2/castle/castle.h | 2 - src/fheroes2/monster/monster.cpp | 15 +- 11 files changed, 253 insertions(+), 183 deletions(-) diff --git a/src/fheroes2/ai/ai_planner.h b/src/fheroes2/ai/ai_planner.h index 67b2a3d474c..85a32a487d2 100644 --- a/src/fheroes2/ai/ai_planner.h +++ b/src/fheroes2/ai/ai_planner.h @@ -189,11 +189,14 @@ namespace AI void CastleTurn( Castle & castle, const bool defensiveStrategy ); + // Upgrades & hires the maximum possible number of troops in the given castle, and also purposefully reinforces the castle's guest hero + // (if there is one) by giving him the best available troops. + void reinforceCastle( Castle & castle ); + // Returns true if heroes can still do tasks but they have no move points. bool HeroesTurn( VecHeroes & heroes, uint32_t & currentProgressValue, uint32_t endProgressValue ); bool recruitHero( Castle & castle, bool buyArmy ); - void reinforceHeroInCastle( Heroes & hero, Castle & castle, const Funds & budget ); void evaluateRegionSafety(); diff --git a/src/fheroes2/ai/ai_planner_castle.cpp b/src/fheroes2/ai/ai_planner_castle.cpp index 2bdb0c13e1b..d42db9a6769 100644 --- a/src/fheroes2/ai/ai_planner_castle.cpp +++ b/src/fheroes2/ai/ai_planner_castle.cpp @@ -23,16 +23,20 @@ #include #include #include +#include +#include #include #include "ai_common.h" #include "ai_planner.h" // IWYU pragma: associated #include "army.h" +#include "army_troop.h" #include "castle.h" #include "difficulty.h" #include "game.h" #include "heroes.h" #include "kingdom.h" +#include "logging.h" #include "maps_tiles.h" #include "monster.h" #include "payment.h" @@ -307,24 +311,215 @@ void AI::Planner::updateKingdomBudget( const Kingdom & kingdom ) } } -void AI::Planner::CastleTurn( Castle & castle, const bool defensiveStrategy ) +void AI::Planner::reinforceCastle( Castle & castle ) { - if ( defensiveStrategy ) { - const Funds & kingdomFunds = castle.GetKingdom().GetFunds(); + assert( castle.isControlAI() ); - // If the castle is potentially under threat, then it makes sense to try to hire the maximum number of troops so that the enemy cannot hire them even if he - // captures the castle, therefore, it is worth starting with hiring. - castle.recruitBestAvailable( kingdomFunds ); + const auto recruitMonster = [&castle]( const Troop & troop ) { + // This method can hire a unit to both the castle garrison and the guest hero's army (depending on the availability of free slots) + if ( castle.RecruitMonster( troop, false ) ) { + DEBUG_LOG( DBG_AI, DBG_INFO, castle.GetName() << " hires " << troop.GetCount() << " " << troop.GetPluralName( troop.GetCount() ) ) + + return true; + } + + return false; + }; + + // First of all, upgrade the existing units in the garrison and in the guest hero's army (if there is one) and merge the same ones to free + // up as much slots as possible for new units + + Army & garrison = castle.GetArmy(); + + garrison.UpgradeTroops( castle ); + garrison.MergeSameMonsterTroops(); + + Heroes * guestHero = castle.GetHero(); + if ( guestHero ) { + assert( guestHero->isActive() && guestHero->isControlAI() && guestHero->GetColor() == castle.GetColor() ); + + Army & guestHeroArmy = guestHero->GetArmy(); + + guestHeroArmy.UpgradeTroops( castle ); + guestHeroArmy.MergeSameMonsterTroops(); + } + + // It is allowed to hire non-upgraded units even if an upgraded dwelling is built + static const std::array castleDwellings{ DWELLING_UPGRADE7, DWELLING_UPGRADE6, DWELLING_MONSTER6, DWELLING_UPGRADE5, + DWELLING_MONSTER5, DWELLING_UPGRADE4, DWELLING_MONSTER4, DWELLING_UPGRADE3, + DWELLING_MONSTER3, DWELLING_UPGRADE2, DWELLING_MONSTER2, DWELLING_MONSTER1 }; + + for ( const uint32_t dwelling : castleDwellings ) { + if ( !castle.isBuild( dwelling ) ) { + continue; + } + + const Monster monster( castle.GetRace(), dwelling ); + + const uint32_t count = castle.getRecruitLimit( monster, castle.GetKingdom().GetFunds() ); + if ( count == 0 ) { + continue; + } + + const Troop troop( monster, count ); + if ( recruitMonster( troop ) ) { + continue; + } + + Troop * weakestTroop = nullptr; + + if ( guestHero ) { + Army & guestHeroArmy = guestHero->GetArmy(); + + // If there is a guest hero in the castle, and there is no free slot for a new unit, then we can try to free up a slot by transferring some unit to the guest + // hero + if ( [&castle = std::as_const( castle ), &garrison, &guestHero = std::as_const( *guestHero ), &guestHeroArmy]() { + for ( size_t i = 0; i < garrison.Size(); ++i ) { + Troop * garrisonTroop = garrison.GetTroop( i ); + // All garrison slots should be occupied here because we just couldn't find a place for a new unit + assert( garrisonTroop != nullptr && garrisonTroop->isValid() ); + + if ( guestHeroArmy.JoinTroop( *garrisonTroop ) ) { + DEBUG_LOG( DBG_AI, DBG_TRACE, + castle.GetName() << " transfers " << garrisonTroop->GetCount() << " " << garrisonTroop->GetPluralName( garrisonTroop->GetCount() ) + << " to the army of the guest hero " << guestHero.GetName() ) +#ifndef WITH_DEBUG + (void)castle; + (void)guestHero; +#endif + + garrisonTroop->Reset(); + + return true; + } + } + + return false; + }() ) { + if ( !recruitMonster( troop ) ) { + // We have just successfully transferred a unit from the garrison to the guest hero's army, but we still can't hire a new unit. This shouldn't happen. + assert( 0 ); + } + + continue; + } + + weakestTroop = guestHeroArmy.GetWeakestTroop(); + // All slots in the guest hero's army should be occupied here because we just couldn't find a place for a new unit + assert( weakestTroop != nullptr ); + } + + { + Troop * weakestGarrisonTroop = garrison.GetWeakestTroop(); + // All garrison slots should be occupied here because we just couldn't find a place for a new unit + assert( weakestGarrisonTroop != nullptr ); + + // We need to compare a strength of troops themselves here (excluding commanding hero's stats) + weakestTroop = weakestTroop && Troop( *weakestTroop ).GetStrength() < Troop( *weakestGarrisonTroop ).GetStrength() ? weakestTroop : weakestGarrisonTroop; + } - Army & garrison = castle.GetArmy(); + // If we still can't find a free slot, let's try to dismiss the weakest unit of those that are present in the garrison and in the army of the guest hero - + // provided that it is weaker than the unit to hire - // Then we try to upgrade the existing units in the castle garrison... - garrison.UpgradeTroops( castle ); + assert( weakestTroop != nullptr ); - // ... and then we try to hire troops again, because after upgrading the existing troops, there could be a place for new units. - castle.recruitBestAvailable( kingdomFunds ); + // We need to compare a strength of troops themselves here (excluding commanding hero's stats) + if ( Troop( *weakestTroop ).GetStrength() > troop.GetStrength() ) { + DEBUG_LOG( DBG_AI, DBG_TRACE, + castle.GetName() << " skips hiring " << troop.GetCount() << " " << troop.GetPluralName( troop.GetCount() ) + << " because the weakest unit consisting of " << weakestTroop->GetCount() << " " + << weakestTroop->GetPluralName( weakestTroop->GetCount() ) << " is stronger" ) - OptimizeTroopsOrder( garrison ); + continue; + } + + DEBUG_LOG( DBG_AI, DBG_TRACE, castle.GetName() << " dismisses " << weakestTroop->GetCount() << " " << weakestTroop->GetPluralName( weakestTroop->GetCount() ) ) + + weakestTroop->Reset(); + + if ( !recruitMonster( troop ) ) { + // We have just successfully dismissed a unit, but we still can't hire a new unit. This shouldn't happen. + assert( 0 ); + } + } + + if ( guestHero ) { + Army & guestHeroArmy = guestHero->GetArmy(); + + // Transfer the best troops from the garrison to the guest hero + guestHeroArmy.JoinStrongestFromArmy( garrison ); + + // Check if we should leave some troops in the garrison + // TODO: amount of troops left could depend on region's safetyFactor + if ( const uint32_t regionID = world.getTile( castle.GetIndex() ).GetRegion(); + castle.isCastle() && _regions.at( regionID ).safetyFactor <= 100 && !garrison.isValid() ) { + auto [troopForTransferToGarrison, transferHalf] + = [guestHeroRole = guestHero->getAIRole(), &guestHeroArmy = std::as_const( guestHeroArmy )]() -> std::pair { + const bool isFighterRole = ( guestHeroRole == Heroes::Role::FIGHTER || guestHeroRole == Heroes::Role::CHAMPION ); + + // We need to compare a strength of troops themselves here (excluding commanding hero's stats) + const double troopsStrength = Troops( guestHeroArmy.getTroops() ).GetStrength(); + const double significanceRatio = isFighterRole ? 20.0 : 10.0; + + { + Troop * candidateTroop = guestHeroArmy.GetSlowestTroop(); + assert( candidateTroop != nullptr ); + + // We need to compare a strength of troops themselves here (excluding commanding hero's stats) + if ( Troop( *candidateTroop ).GetStrength() <= troopsStrength / significanceRatio ) { + return { candidateTroop, false }; + } + } + + // if this is an important hero, then all his troops are significant + if ( isFighterRole ) { + return {}; + } + + { + Troop * candidateTroop = guestHeroArmy.GetWeakestTroop(); + assert( candidateTroop != nullptr ); + + // We need to compare a strength of troops themselves here (excluding commanding hero's stats) + if ( Troop( *candidateTroop ).GetStrength() <= troopsStrength / significanceRatio ) { + return { candidateTroop, true }; + } + } + + return {}; + }(); + + if ( troopForTransferToGarrison ) { + assert( guestHeroArmy.GetOccupiedSlotCount() > 1 ); + + const uint32_t initialCount = troopForTransferToGarrison->GetCount(); + const uint32_t countToTransfer = transferHalf ? initialCount / 2 : initialCount; + + if ( garrison.JoinTroop( troopForTransferToGarrison->GetMonster(), countToTransfer, true ) ) { + if ( countToTransfer == initialCount ) { + troopForTransferToGarrison->Reset(); + } + else { + troopForTransferToGarrison->SetCount( initialCount - countToTransfer ); + } + } + } + } + } + + OptimizeTroopsOrder( garrison ); + + if ( guestHero ) { + OptimizeTroopsOrder( guestHero->GetArmy() ); + } +} + +void AI::Planner::CastleTurn( Castle & castle, const bool defensiveStrategy ) +{ + if ( defensiveStrategy ) { + // If the castle is potentially under threat, then it makes sense to try to hire the maximum number of troops so that the enemy cannot hire them even if he + // captures the castle, therefore, it is worth starting with hiring. + reinforceCastle( castle ); // Avoid building monster dwellings when defensive as they might fall into enemy's hands. Instead, try to build defensive structures if there is at least some // kind of garrison in the castle. diff --git a/src/fheroes2/ai/ai_planner_hero.cpp b/src/fheroes2/ai/ai_planner_hero.cpp index 5b524e9f357..8652244885a 100644 --- a/src/fheroes2/ai/ai_planner_hero.cpp +++ b/src/fheroes2/ai/ai_planner_hero.cpp @@ -2644,10 +2644,8 @@ void AI::Planner::HeroesActionComplete( Heroes & hero, const int32_t tileIndex, // This method is called upon action completion and the hero could no longer be available. // So it is to check if the hero is still present. if ( hero.isActive() ) { - Castle * castle = hero.inCastleMutable(); - - if ( castle ) { - reinforceHeroInCastle( hero, *castle, castle->GetKingdom().GetFunds() ); + if ( Castle * castle = hero.inCastleMutable(); castle ) { + reinforceCastle( *castle ); } else { OptimizeTroopsOrder( hero.GetArmy() ); diff --git a/src/fheroes2/ai/ai_planner_kingdom.cpp b/src/fheroes2/ai/ai_planner_kingdom.cpp index d871d7eeac2..045a3d4a21a 100644 --- a/src/fheroes2/ai/ai_planner_kingdom.cpp +++ b/src/fheroes2/ai/ai_planner_kingdom.cpp @@ -36,7 +36,6 @@ #include "ai_planner.h" // IWYU pragma: associated #include "ai_planner_internals.h" #include "army.h" -#include "army_troop.h" #include "audio.h" #include "audio_manager.h" #include "castle.h" @@ -308,7 +307,7 @@ bool AI::Planner::recruitHero( Castle & castle, bool buyArmy ) } if ( buyArmy ) { - reinforceHeroInCastle( *recruit, castle, kingdom.GetFunds() ); + reinforceCastle( castle ); } else { OptimizeTroopsOrder( recruit->GetArmy() ); @@ -317,93 +316,6 @@ bool AI::Planner::recruitHero( Castle & castle, bool buyArmy ) return true; } -void AI::Planner::reinforceHeroInCastle( Heroes & hero, Castle & castle, const Funds & budget ) -{ - // It is impossible to reinforce dead heroes. - assert( hero.isActive() ); - - const Heroes::AIHeroMeetingUpdater heroMeetingUpdater( hero ); - - if ( !hero.HaveSpellBook() && castle.GetLevelMageGuild() > 0 && !hero.IsFullBagArtifacts() ) { - // this call will check if AI kingdom have enough resources to buy book - hero.BuySpellBook( &castle ); - } - - Army & heroArmy = hero.GetArmy(); - Army & garrison = castle.GetArmy(); - - // Merge all troops in the castle to have the best army. - heroArmy.JoinStrongestFromArmy( garrison ); - - // Upgrade troops and try to merge them again. - heroArmy.UpgradeTroops( castle ); - garrison.UpgradeTroops( castle ); - heroArmy.JoinStrongestFromArmy( garrison ); - - // Recruit more troops and also merge them. - castle.recruitBestAvailable( budget ); - heroArmy.JoinStrongestFromArmy( garrison ); - - const uint32_t regionID = world.getTile( castle.GetIndex() ).GetRegion(); - - // Check if we should leave some troops in the garrison - // TODO: amount of troops left could depend on region's safetyFactor - if ( castle.isCastle() && _regions[regionID].safetyFactor <= 100 && !garrison.isValid() ) { - auto [troopForTransferToGarrison, transferHalf] = [&hero, &heroArmy]() -> std::pair { - const Heroes::Role heroRole = hero.getAIRole(); - const bool isFighterRole = ( heroRole == Heroes::Role::FIGHTER || heroRole == Heroes::Role::CHAMPION ); - - // We need to compare a strength of troops excluding hero's stats. - const double troopsStrength = Troops( heroArmy.getTroops() ).GetStrength(); - const double significanceRatio = isFighterRole ? 20.0 : 10.0; - - { - Troop * candidateTroop = heroArmy.GetSlowestTroop(); - assert( candidateTroop != nullptr ); - - if ( candidateTroop->GetStrength() <= troopsStrength / significanceRatio ) { - return { candidateTroop, false }; - } - } - - // if this is an important hero, then all his troops are significant - if ( isFighterRole ) { - return {}; - } - - { - Troop * candidateTroop = heroArmy.GetWeakestTroop(); - assert( candidateTroop != nullptr ); - - if ( candidateTroop->GetStrength() <= troopsStrength / significanceRatio ) { - return { candidateTroop, true }; - } - } - - return {}; - }(); - - if ( troopForTransferToGarrison ) { - assert( heroArmy.GetOccupiedSlotCount() > 1 ); - - const uint32_t initialCount = troopForTransferToGarrison->GetCount(); - const uint32_t countToTransfer = transferHalf ? initialCount / 2 : initialCount; - - if ( garrison.JoinTroop( troopForTransferToGarrison->GetMonster(), countToTransfer, true ) ) { - if ( countToTransfer == initialCount ) { - troopForTransferToGarrison->Reset(); - } - else { - troopForTransferToGarrison->SetCount( initialCount - countToTransfer ); - } - } - } - } - - OptimizeTroopsOrder( heroArmy ); - OptimizeTroopsOrder( garrison ); -} - void AI::Planner::evaluateRegionSafety() { std::vector> regionsToCheck; diff --git a/src/fheroes2/army/army.cpp b/src/fheroes2/army/army.cpp index 89d20513998..aaca58f727c 100644 --- a/src/fheroes2/army/army.cpp +++ b/src/fheroes2/army/army.cpp @@ -504,6 +504,7 @@ void Troops::UpgradeTroops( const Castle & castle ) const { for ( Troop * troop : *this ) { assert( troop != nullptr ); + if ( !troop->isValid() ) { continue; } @@ -512,7 +513,6 @@ void Troops::UpgradeTroops( const Castle & castle ) const continue; } - Kingdom & kingdom = castle.GetKingdom(); if ( castle.GetRace() != troop->GetRace() ) { continue; } @@ -521,11 +521,16 @@ void Troops::UpgradeTroops( const Castle & castle ) const continue; } + Kingdom & kingdom = castle.GetKingdom(); + const Funds payment = troop->GetTotalUpgradeCost(); - if ( kingdom.AllowPayment( payment ) ) { - kingdom.OddFundsResource( payment ); - troop->Upgrade(); + if ( !kingdom.AllowPayment( payment ) ) { + continue; } + + kingdom.OddFundsResource( payment ); + + troop->Upgrade(); } } diff --git a/src/fheroes2/battle/battle.h b/src/fheroes2/battle/battle.h index 5f19af7086a..4322a69746b 100644 --- a/src/fheroes2/battle/battle.h +++ b/src/fheroes2/battle/battle.h @@ -68,17 +68,15 @@ namespace Battle TargetInfo() = default; - explicit TargetInfo( Unit * defender_ ) - : defender( defender_ ) + explicit TargetInfo( Unit * def ) + : defender( def ) {} static bool isFinishAnimFrame( const TargetInfo & info ); }; struct TargetsInfo : public std::vector - { - TargetsInfo() = default; - }; + {}; enum MonsterState : uint32_t { diff --git a/src/fheroes2/battle/battle_troop.cpp b/src/fheroes2/battle/battle_troop.cpp index 555212fd250..d256b1705ba 100644 --- a/src/fheroes2/battle/battle_troop.cpp +++ b/src/fheroes2/battle/battle_troop.cpp @@ -163,7 +163,7 @@ Battle::Unit::Unit( const Troop & troop, const Position & pos, const bool ref, c , _initialCount( troop.GetCount() ) , dead( 0 ) , shots( troop.GetShots() ) - , disruptingray( 0 ) + , _disruptingRaysNum( 0 ) , reflect( ref ) , mirror( nullptr ) , idleTimer( animation.getIdleDelay() ) @@ -1043,8 +1043,9 @@ uint32_t Battle::Unit::GetAttack() const { uint32_t res = ArmyTroop::GetAttack(); - if ( Modes( SP_BLOODLUST ) ) + if ( Modes( SP_BLOODLUST ) ) { res += Spell( Spell::BLOODLUST ).ExtraValue(); + } return res; } @@ -1053,31 +1054,35 @@ uint32_t Battle::Unit::GetDefense() const { uint32_t res = ArmyTroop::GetDefense(); - if ( Modes( SP_STONESKIN ) ) + if ( Modes( SP_STONESKIN ) ) { res += Spell( Spell::STONESKIN ).ExtraValue(); - else if ( Modes( SP_STEELSKIN ) ) + } + else if ( Modes( SP_STEELSKIN ) ) { res += Spell( Spell::STEELSKIN ).ExtraValue(); + } - // disrupting ray accumulate effect - if ( disruptingray ) { - const uint32_t step = disruptingray * Spell( Spell::DISRUPTINGRAY ).ExtraValue(); + if ( _disruptingRaysNum ) { + const uint32_t step = _disruptingRaysNum * Spell( Spell::DISRUPTINGRAY ).ExtraValue(); - if ( step >= res ) + if ( step >= res ) { res = 1; - else + } + else { res -= step; + } } - // check moat const Castle * castle = Arena::GetCastle(); if ( castle && castle->isBuild( BUILD_MOAT ) && ( Board::isMoatIndex( GetHeadIndex(), *this ) || Board::isMoatIndex( GetTailIndex(), *this ) ) ) { const uint32_t step = GameStatic::GetBattleMoatReduceDefense(); - if ( step >= res ) + if ( step >= res ) { res = 1; - else + } + else { res -= step; + } } return res; @@ -1352,7 +1357,7 @@ void Battle::Unit::SpellModesAction( const Spell & spell, uint32_t duration, con break; case Spell::DISRUPTINGRAY: - ++disruptingray; + ++_disruptingRaysNum; break; default: diff --git a/src/fheroes2/battle/battle_troop.h b/src/fheroes2/battle/battle_troop.h index 66f01c3b732..7ac30bc7110 100644 --- a/src/fheroes2/battle/battle_troop.h +++ b/src/fheroes2/battle/battle_troop.h @@ -320,7 +320,7 @@ namespace Battle uint32_t _initialCount; uint32_t dead; uint32_t shots; - uint32_t disruptingray; + uint32_t _disruptingRaysNum; bool reflect; Position position; diff --git a/src/fheroes2/castle/castle.cpp b/src/fheroes2/castle/castle.cpp index 022999d4ea3..8106fb8baa0 100644 --- a/src/fheroes2/castle/castle.cpp +++ b/src/fheroes2/castle/castle.cpp @@ -1095,51 +1095,6 @@ bool Castle::RecruitMonster( const Troop & troop, bool showDialog ) return true; } -bool Castle::_recruitMonsterFromDwelling( const uint32_t buildingType, const uint32_t count, const bool force /* = false */ ) -{ - const Monster monster( _race, GetActualDwelling( buildingType ) ); - assert( count <= getRecruitLimit( monster, GetKingdom().GetFunds() ) ); - - const Troop troop( monster, std::min( count, getRecruitLimit( monster, GetKingdom().GetFunds() ) ) ); - - if ( RecruitMonster( troop, false ) ) { - return true; - } - - // TODO: before removing an existing stack of monsters try to upgrade them and also merge some stacks. - - if ( force ) { - Troop * weak = GetArmy().GetWeakestTroop(); - if ( weak && weak->GetStrength() < troop.GetStrength() ) { - DEBUG_LOG( DBG_GAME, DBG_INFO, - _name << ": " << troop.GetCount() << " " << troop.GetMultiName() << " replace " << weak->GetCount() << " " << weak->GetMultiName() ) - weak->Set( troop ); - return true; - } - } - - return false; -} - -void Castle::recruitBestAvailable( Funds budget ) -{ - for ( uint32_t dw = DWELLING_MONSTER6; dw >= DWELLING_MONSTER1; dw >>= 1 ) { - if ( !isBuild( dw ) ) { - continue; - } - - const Monster monster( _race, GetActualDwelling( dw ) ); - const uint32_t willRecruit = getRecruitLimit( monster, budget ); - if ( willRecruit == 0 ) { - continue; - } - - if ( _recruitMonsterFromDwelling( dw, willRecruit, true ) ) { - budget -= ( monster.GetCost() * willRecruit ); - } - } -} - uint32_t Castle::getRecruitLimit( const Monster & monster, const Funds & budget ) const { // validate that monster is from the current castle diff --git a/src/fheroes2/castle/castle.h b/src/fheroes2/castle/castle.h index ab1bf4567b7..4113d62eb8b 100644 --- a/src/fheroes2/castle/castle.h +++ b/src/fheroes2/castle/castle.h @@ -264,7 +264,6 @@ class Castle final : public MapPosition, public BitModes, public ColorBase, publ // Returns true in case of successful recruitment. bool RecruitMonster( const Troop & troop, bool showDialog = true ); - void recruitBestAvailable( Funds budget ); uint32_t getRecruitLimit( const Monster & monster, const Funds & budget ) const; int getBuildingValue() const; @@ -408,7 +407,6 @@ class Castle final : public MapPosition, public BitModes, public ColorBase, publ // Recruit maximum monsters from the castle. Returns 'true' if the recruit was made. bool _recruitCastleMax( const Troops & currentCastleArmy ); - bool _recruitMonsterFromDwelling( const uint32_t buildingType, const uint32_t count, const bool force = false ); friend OStreamBase & operator<<( OStreamBase & stream, const Castle & castle ); friend IStreamBase & operator>>( IStreamBase & stream, Castle & castle ); diff --git a/src/fheroes2/monster/monster.cpp b/src/fheroes2/monster/monster.cpp index 2ae794afbbc..4f3412032e4 100644 --- a/src/fheroes2/monster/monster.cpp +++ b/src/fheroes2/monster/monster.cpp @@ -143,21 +143,22 @@ uint32_t Monster::GetShots() const return fheroes2::getMonsterData( id ).battleStats.shots; } -// Get universal heuristic of Monster type regardless of context; both combat and strategic value -// Doesn't account for situational special bonuses such as spell immunity -double Monster::GetMonsterStrength( int attack, int defense ) const +double Monster::GetMonsterStrength( int attack /* = -1 */, int defense /* = -1 */ ) const { // TODO: do not use virtual functions when calculating strength for troops without hero's skills. - // If no modified values were provided then re-calculate - // GetAttack and GetDefense will call overloaded versions accounting for Hero bonuses - if ( attack == -1 ) + if ( attack < 0 ) { + // This is a virtual function that can be overridden in subclasses and take into account various bonuses (for example, hero bonuses) attack = GetAttack(); + } - if ( defense == -1 ) + if ( defense < 0 ) { + // This is a virtual function that can be overridden in subclasses and take into account various bonuses (for example, hero bonuses) defense = GetDefense(); + } const double attackDefense = 1.0 + attack * 0.1 + defense * 0.05; + return attackDefense * fheroes2::getMonsterData( id ).battleStats.monsterBaseStrength; } From a2bf16bbb84edd47bf3bba91391d8d3bae300c9e Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Tue, 3 Dec 2024 17:02:29 +0300 Subject: [PATCH 12/30] AI::Planner::reinforceCastle() : paraphrase a few comments (#9312) --- src/fheroes2/ai/ai_planner_castle.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/fheroes2/ai/ai_planner_castle.cpp b/src/fheroes2/ai/ai_planner_castle.cpp index d42db9a6769..ce0e65f0f17 100644 --- a/src/fheroes2/ai/ai_planner_castle.cpp +++ b/src/fheroes2/ai/ai_planner_castle.cpp @@ -316,7 +316,7 @@ void AI::Planner::reinforceCastle( Castle & castle ) assert( castle.isControlAI() ); const auto recruitMonster = [&castle]( const Troop & troop ) { - // This method can hire a unit to both the castle garrison and the guest hero's army (depending on the availability of free slots) + // This method can hire a unit to both the castle garrison and the guest hero's army (depending on the availability of suitable slots) if ( castle.RecruitMonster( troop, false ) ) { DEBUG_LOG( DBG_AI, DBG_INFO, castle.GetName() << " hires " << troop.GetCount() << " " << troop.GetPluralName( troop.GetCount() ) ) @@ -371,8 +371,7 @@ void AI::Planner::reinforceCastle( Castle & castle ) if ( guestHero ) { Army & guestHeroArmy = guestHero->GetArmy(); - // If there is a guest hero in the castle, and there is no free slot for a new unit, then we can try to free up a slot by transferring some unit to the guest - // hero + // If there is a guest hero in the castle, and there is no slot for a new unit, then we can try to free up a slot by transferring some unit to the guest hero if ( [&castle = std::as_const( castle ), &garrison, &guestHero = std::as_const( *guestHero ), &guestHeroArmy]() { for ( size_t i = 0; i < garrison.Size(); ++i ) { Troop * garrisonTroop = garrison.GetTroop( i ); @@ -418,8 +417,8 @@ void AI::Planner::reinforceCastle( Castle & castle ) weakestTroop = weakestTroop && Troop( *weakestTroop ).GetStrength() < Troop( *weakestGarrisonTroop ).GetStrength() ? weakestTroop : weakestGarrisonTroop; } - // If we still can't find a free slot, let's try to dismiss the weakest unit of those that are present in the garrison and in the army of the guest hero - - // provided that it is weaker than the unit to hire + // If we still can't find a slot, let's try to dismiss the weakest unit of those that are present in the garrison and in the army of the guest hero - provided + // that it is weaker than the unit to hire assert( weakestTroop != nullptr ); From 1f9c244e27c57e332783b86e152cf0ea09bd3348 Mon Sep 17 00:00:00 2001 From: WeirdYunus <166413432+WeirdYunus@users.noreply.github.com> Date: Wed, 4 Dec 2024 06:26:51 +0300 Subject: [PATCH 13/30] Update the Turkish translation (#9308) --- files/lang/tr.po | 706 ++++++++++++++++++++++++++++------------------- 1 file changed, 427 insertions(+), 279 deletions(-) diff --git a/files/lang/tr.po b/files/lang/tr.po index b98594b1c6b..37d33e2644a 100644 --- a/files/lang/tr.po +++ b/files/lang/tr.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-11-28 15:55+0000\n" -"PO-Revision-Date: 2022-10-01 20:20+0300\n" +"PO-Revision-Date: 2024-12-01 16:37+0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: Turkish \n" "Language: tr\n" @@ -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.1.1\n" +"X-Generator: Poedit 3.5\n" msgid "" "BATTLE\n" @@ -29,182 +29,207 @@ msgid "" "NEW\n" "GAME" msgstr "" +"YENİ\n" +"OYUN" msgid "" "SAVE\n" "GAME" msgstr "" +"OYUNU\n" +"KAYDET" msgid "" "LOAD\n" "GAME" msgstr "" +"OYUNU\n" +"YÜKLE" msgid "INFO" -msgstr "" +msgstr "BİLGİ" msgid "QUIT" -msgstr "" +msgstr "ÇIKIŞ" msgid "" "NEW\n" "MAP" msgstr "" +"YENİ\n" +"HARİTA" msgid "" "SAVE\n" "MAP" msgstr "" +"HARİTAYI\n" +"KAYDET" msgid "" "LOAD\n" "MAP" msgstr "" +"HARİTAYI\n" +"YÜKLE" msgid "CANCEL" -msgstr "" +msgstr "İPTAL" msgid "OKAY" -msgstr "" +msgstr "TAMAM" msgid "smallerButton|OKAY" -msgstr "" +msgstr "TAMAM" msgid "ACCEPT" -msgstr "" +msgstr "KABUL" msgid "DECLINE" -msgstr "" +msgstr "REDDET" msgid "LEARN" -msgstr "" +msgstr "ÖĞREN" msgid "TRADE" -msgstr "" +msgstr "TAKAS" msgid "YES" -msgstr "" +msgstr "EVET" msgid "NO" -msgstr "" +msgstr "HAYIR" msgid "EXIT" -msgstr "" +msgstr "ÇIKIŞ" msgid "smallerButton|EXIT" -msgstr "" +msgstr "ÇIKIŞ" msgid "DISMISS" -msgstr "" +msgstr "BIRAK" msgid "UPGRADE" -msgstr "" +msgstr "GELİŞTİR" msgid "RESTART" -msgstr "" +msgstr "TEKRARLA" msgid "HEROES" -msgstr "" +msgstr "KAHRAMANLAR" msgid "" "TOWNS/\n" "CASTLES" msgstr "" +"KASABALAR/\n" +"KALELER" msgid "S" -msgstr "" +msgstr "S" msgid "M" -msgstr "" +msgstr "M" msgid "L" -msgstr "" +msgstr "L" msgid "X-L" -msgstr "" +msgstr "X-L" msgid "ALL" -msgstr "" +msgstr "HEPSİ" msgid "SELECT" -msgstr "" +msgstr "SEÇ" msgid "" "STANDARD\n" "GAME" msgstr "" +"STANDART\n" +"OYUN" msgid "" "CAMPAIGN\n" "GAME" msgstr "" +"HİKAYE\n" +"OYUNU" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" +"ÇOK\n" +"OYUNCULU\n" +"OYUN" msgid "CONFIG" -msgstr "" +msgstr "AYARLAR" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" +"ORİJİNAL\n" +"HİKAYE" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" +"EKLENTİ\n" +"HİKAYESİ" msgid "HOT SEAT" -msgstr "" +msgstr "SIRAYLA OYNAMA" msgid "2 PLAYERS" -msgstr "" +msgstr "2 OYUNCU" msgid "3 PLAYERS" -msgstr "" +msgstr "3 OYUNCU" msgid "4 PLAYERS" -msgstr "" +msgstr "4 OYUNCU" msgid "5 PLAYERS" -msgstr "" +msgstr "5 OYUNCU" msgid "6 PLAYERS" -msgstr "" +msgstr "6 OYUNCU" msgid "GIFT" msgstr "HEDİYE" msgid "MAX" -msgstr "" +msgstr "MAKS" msgid "DIFFICULTY" msgstr "ZORLUK" msgid "VIEW INTRO" -msgstr "" +msgstr "İNTROYU İZLE" msgid "MIN" msgstr "MİN" msgid "RESET" -msgstr "" +msgstr "SIFIRLA" msgid "START" -msgstr "" +msgstr "BAŞLA" msgid "CASTLE" -msgstr "" +msgstr "KALE" msgid "TOWN" -msgstr "" +msgstr "KASABA" msgid "RESTRICT" -msgstr "" +msgstr "KISITLA" msgid "" "D\n" @@ -215,6 +240,11 @@ msgid "" "S\n" "S" msgstr "" +"B\n" +"I\n" +"R\n" +"A\n" +"K" msgid "" "E\n" @@ -222,6 +252,11 @@ msgid "" "I\n" "T" msgstr "" +"Ç\n" +"I\n" +"K\n" +"I\n" +"Ş" msgid "" "P\n" @@ -231,6 +266,13 @@ msgid "" "O\n" "L" msgstr "" +"D\n" +"E\n" +"V\n" +"R\n" +"İ\n" +"Y\n" +"E" msgid "" "C\n" @@ -242,6 +284,12 @@ msgid "" "G\n" "N" msgstr "" +"H\n" +"İ\n" +"K\n" +"A\n" +"Y\n" +"E" msgid "" "S\n" @@ -253,15 +301,23 @@ msgid "" "R\n" "D" msgstr "" +"S\n" +"T\n" +"A\n" +"N\n" +"D\n" +"A\n" +"R\n" +"T" msgid "RUMORS" -msgstr "" +msgstr "SÖYLENTİLER" msgid "EVENTS" -msgstr "" +msgstr "ETKİNLİKLER" msgid "LANGUAGE" -msgstr "" +msgstr "DİL" msgid "Warrior" msgstr "Savaşçı" @@ -369,19 +425,20 @@ msgid "All %{race} troops +1" msgstr "Bütün %{race} askerler +1" msgid "NeutralRaceTroops|Neutral" -msgstr "" +msgstr "Tarafsız" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Birlikleri %{count} hizala -%{penalty}" -#, fuzzy msgid "Some undead in army -1" -msgstr "Gruptaki bazı ölümsüzler -1" +msgstr "Gruptaki bazı undeadler -1" msgid "" "Default\n" "troop" msgstr "" +"Varsayılan\n" +"askerler" msgid "View %{name}" msgstr "Görünüm %{name}" @@ -407,9 +464,8 @@ msgstr "Son birlik hareket ettirilemiyor" msgid "Move the %{name}" msgstr "%{name} 'i hareket ettir" -#, fuzzy msgid "Set %{monster} Count" -msgstr "%{monster} vur" +msgstr "%{monster} sayısını ayarla" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" @@ -433,31 +489,28 @@ msgstr "" "bırakıyor." msgid "You cannot cast spells without a commanding hero." -msgstr "" +msgstr "Yöneten bir kahraman olmadan büyü kullanamazsın." msgid "You have already cast a spell this round." msgstr "Bu turda zaten bir büyü yaptın." -#, fuzzy msgid "That spell will have no effect!" -msgstr "Bu büyü hiç kimseyi etkilemeyecek!" +msgstr "Bu büyünün etkisi olmayacak!" msgid "You may only summon one type of elemental per combat." msgstr "Savaş başına yalnızca bir tür elemental çağırabilirsiniz." -#, fuzzy msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "Kahramanınızın yanında bir Elemental çağırmak için açık bir alan yok." -#, fuzzy msgid "" "The Moat reduces the defense skill of troops trapped in it by %{count} and " "restricts their movement range." msgstr "" "Hendek savunma becerisi herhangi bir birimin savunma becerisi ve hareket " -"hızını -%{count} düşürüyor." +"hızını %{count} düşürüyor." msgid "Speed: %{speed}" msgstr "Hız: %{speed}" @@ -471,15 +524,14 @@ msgstr "Kapalı" msgid "On" msgstr "Açık" -#, fuzzy msgid "Turn Order" -msgstr "Ordu Düzeni" +msgstr "Sıra sırası" msgid "Auto Spell Casting" msgstr "Otomatik Büyü Yapma" msgid "Grid" -msgstr "Kafes" +msgstr "Izgara" msgid "Shadow Movement" msgstr "Gölge Hareketi" @@ -488,26 +540,25 @@ msgid "Shadow Cursor" msgstr "Gölge İmleci" msgid "Audio" -msgstr "" +msgstr "Ses" msgid "Settings" -msgstr "" +msgstr "Ayarlar" msgid "Configure" -msgstr "" +msgstr "Ayarla" msgid "Hot Keys" -msgstr "" +msgstr "Kısayollar" msgid "Damage Info" -msgstr "" +msgstr "Hasar Bilgisi" msgid "Set the speed of combat actions and animations." msgstr "Savaş eylemlerinin ve animasyonların hızını ayarlayın." -#, fuzzy msgid "Toggle to display the turn order during the battle." -msgstr "Savaş sırasında ordu düzenini görüntülemek için geçiş yapın." +msgstr "Savaş sırasında sıra sırasını görüntülemek için aktif edin." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " @@ -540,26 +591,25 @@ msgstr "" "kapatın." msgid "Change the audio settings of the game." -msgstr "" +msgstr "Oyunun ses ayarını değiştir." msgid "Check and configure all the hot keys present in the game." -msgstr "" +msgstr "Oyunda bulunan tüm kısayol tuşlarını kontrol edin ve yapılandırın." -#, fuzzy msgid "Toggle to display damage information during the battle." -msgstr "Savaş sırasında ordu düzenini görüntülemek için geçiş yapın." +msgstr "Savaş sırasında hasar bilgisini görüntülemek için akif edin." msgid "Exit this menu." -msgstr "" +msgstr "Bu menüden çık." msgid "Okay" -msgstr "" +msgstr "Tamam" msgid "The enemy has surrendered!" msgstr "Düşman teslim oldu!" msgid "Their cowardice costs them %{gold} gold." -msgstr "" +msgstr "Korkaklıkları onlara %{gold} altına mal oldu." msgid "The enemy has fled!" msgstr "Düşman kaçtı!" @@ -576,13 +626,11 @@ msgstr "Korkak %{name} savaştan kaçtı." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} düşmana teslim oldu, ve utanç içinde ayrıldı." -#, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." -msgstr "Kuvvetleriniz acı bir yenilgiye uğradı, ve %{name} davanızı terk etti." +msgstr "Birliğiniz acı bir yenilgiye uğradı, ve %{name} sizi terk etti." -#, fuzzy msgid "Your forces suffer a bitter defeat." -msgstr "Kuvvetleriniz acı bir yenilgiye uğradı." +msgstr "Birliğiniz acı bir yenilgiye uğradı." msgid "Battlefield Casualties" msgstr "Savaş Alanı Kayıpları" @@ -594,14 +642,13 @@ msgid "Defender" msgstr "Savunan" msgid "Click to leave the battle results." -msgstr "" +msgstr "Savaş sonuçlarından çıkmak için tıklayın." -#, fuzzy msgid "Click to restart the battle in manual mode." -msgstr "Savaşı otomatik modda bitirmek istediğinizden emin misiniz?" +msgstr "Savaşı manuel modda bbaşlatmak için tıklayın." msgid "Restart" -msgstr "" +msgstr "Tekrarla" msgid "You have captured an enemy artifact!" msgstr "Bir düşman eseri ele geçirdiniz!" @@ -619,7 +666,7 @@ msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" -"Karanlık büyücülük sanatlarını uygulayarak, %{canavar} olarak hizmetinize " +"Karanlık büyücülük sanatlarını uygulayarak, %{monster} olarak hizmetinize " "geri dönmek için düşmanın ölülerinin %{count} kadarını çağırabilirsiniz." msgid "%{name} the %{race}" @@ -686,7 +733,6 @@ msgstr "" "almanız için hazır olacak, ancak kahramanın yalnızca acemi bir kahramanın " "güçleri olacak." -#, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " @@ -694,7 +740,8 @@ msgid "" "in the army." msgstr "" "Teslim olmak Altın'a mal olur. Ancak fidyeyi öderseniz, kahraman ve hayatta " -"kalan tüm yaratıkları yeniden askere alınabilir." +"kalan tüm yaratıkları yeniden askere alınabilir. Teslim olmanın maliyeti, " +"orduda kalan geçici olmayan birliklerin toplam maliyetinin yarısı kadardır." msgid "Open Hero Screen to view full information about the hero." msgstr "" @@ -818,14 +865,12 @@ msgstr "Mesaj Çubuğu" msgid "Shows the results of individual monster's actions." msgstr "Bireysel canavar eylemlerinin sonuçlarını gösterir." -#, fuzzy msgid "Are you sure you want to enable auto combat?" -msgstr "Geri çekilmek istediğine emin misin?" +msgstr "Otomatik savaşı etkinleştirmek istediğine emin misin?" msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Savaşı otomatik modda bitirmek istediğinizden emin misiniz?" -#, fuzzy msgid "The %{name} skips their turn." msgid_plural "The %{name} skip their turn." msgstr[0] "%{name} sırasını geçti." @@ -846,11 +891,10 @@ msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} yok oldu." msgstr[1] "%{count} %{defender} yok oldu." -#, fuzzy msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." -msgstr[0] "Ayna görüntüsü oluşturuldu." -msgstr[1] "Ayna görüntüsü oluşturuldu." +msgstr[0] "1 ruh dahil edildi." +msgstr[1] "%{count} ruh dahil edildi." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." @@ -860,7 +904,6 @@ msgstr[1] "" msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "%{monster}: [%{src}] buradan [%{dst}] buraya hareket ettirdi." -#, fuzzy msgid "The %{name} resists the spell!" msgid_plural "The %{name} resist the spell!" msgstr[0] "%{name} büyüye direndi!" @@ -890,37 +933,31 @@ msgstr "%{spell} büyüsü bir yaşayan canavara %{damage} hasar verdi." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "%{spell} büyüsü bütün yaşayan canavarlara %{damage} hasar verdi." -#, fuzzy msgid "The %{attacker}'s attack blinds the %{target}!" msgid_plural "The %{attacker}' attack blinds the %{target}!" -msgstr[0] "%{attacker}' saldırısı %{target}'ı kör etti!" -msgstr[1] "%{attacker}' saldırısı %{target}'ı kör etti!" +msgstr[0] "%{attacker}'ın saldırısı %{target}'ı kör etti!" +msgstr[1] "%{attacker}'ların saldırısı %{target}'ı kör etti!" -#, fuzzy msgid "The %{attacker}'s gaze turns the %{target} to stone!" msgid_plural "The %{attacker}' gaze turns the %{target} to stone!" msgstr[0] "%{attacker}'ın bakışları %{target}'ı taşa çevirdi!" -msgstr[1] "%{attacker}'ın bakışları %{target}'ı taşa çevirdi!" +msgstr[1] "%{attacker}'ların bakışları %{target}'ı taşa çevirdi!" -#, fuzzy msgid "The %{attacker}'s curse falls upon the %{target}!" msgid_plural "The %{attacker}' curse falls upon the %{target}!" msgstr[0] "%{attacker}'ın laneti %{target}'in üzerine geldi!" -msgstr[1] "%{attacker}'ın laneti %{target}'in üzerine geldi!" +msgstr[1] "%{attacker}'ların laneti %{target}'in üzerine geldi!" -#, fuzzy msgid "The %{target} is paralyzed by the %{attacker}!" msgid_plural "The %{target} are paralyzed by the %{attacker}!" -msgstr[0] "%{attacker}' saldırısı %{target}'ı sakatladı!" -msgstr[1] "%{attacker}' saldırısı %{target}'ı sakatladı!" +msgstr[0] "%{attacker}'ın saldırısı %{target}'ı sakatladı!" +msgstr[1] "%{attacker}'ın saldırısı %{target}'ları sakatladı!" -#, fuzzy msgid "The %{attacker} dispels all good spells on your %{target}!" msgid_plural "The %{attacker} dispel all good spells on your %{target}!" msgstr[0] "%{attacker} %{target} üzerindeki tüm iyi büyüleri yok etti!" msgstr[1] "%{attacker} %{target} üzerindeki tüm iyi büyüleri yok etti!" -#, fuzzy msgid "The %{attacker} casts %{spell} on the %{target}!" msgid_plural "The %{attacker} cast %{spell} on the %{target}!" msgstr[0] "%{attacker} %{target}'a %{spell} uyguladı!" @@ -947,7 +984,6 @@ msgstr "Ayna görüntüsü oluşturuldu." msgid "The mirror image is destroyed!" msgstr "Ayna görüntüsü yok edildi!" -#, fuzzy msgid "Are you sure you want to interrupt the auto combat?" msgstr "Otomatik savaşı kesmek istediğine emin misin?" @@ -991,27 +1027,25 @@ msgid "AI" msgstr "Bilgisayar" msgid "Start" -msgstr "" +msgstr "Başla" -#, fuzzy msgid "Start the battle." -msgstr "Savaşa geri dön." +msgstr "Savaşı başlat." msgid "Exit" -msgstr "" +msgstr "Çıkış" msgid "Reset" -msgstr "" +msgstr "Sıfırla" msgid "Reset to default settings." -msgstr "" +msgstr "Varsayılan ayarlara sıfırla." msgid "Please select another hero." msgstr "Lütfen başka bir kahraman seç." -#, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" -msgstr "%{race1} %{name1}" +msgstr "%{race1} %{name1} vs %{race2} %{name2}" msgid "Monsters" msgstr "Canavarlar" @@ -1031,14 +1065,13 @@ msgstr "%{name} %{count} Okçu gücüyle ateş ediyor" msgid "each with a +%{attack} bonus to their attack skill." msgstr "her biri saldırı becerilerine +%{attack} bonusu ekler." -#, fuzzy msgid "The %{name} is destroyed." -msgstr "Ayna görüntüsü yok edildi!" +msgstr "%{name} yok edildi." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%{count} tane %{name} ölümden uyandı!" +msgstr[1] "%{count} tane %{name} ölümden uyandı!" msgid "Dwarven Alliance" msgstr "Cüce İttifakı" @@ -1053,7 +1086,7 @@ msgid "Ogre Alliance" msgstr "Dev İttifakı" msgid "Dwarfbane" -msgstr "" +msgstr "Cücekaçıran" msgid "Dragon Alliance" msgstr "Ejderha İttifakı" @@ -1062,34 +1095,34 @@ msgid "Elven Alliance" msgstr "Elf İttifakı" msgid "Kraeger defeated" -msgstr "" +msgstr "Kraeger yenildi" msgid "Wayward Son" -msgstr "" +msgstr "Wayward Oğlu" msgid "Uncle Ivan" msgstr "Ivan Amca" msgid "Annexation" -msgstr "" +msgstr "İlhak" msgid "Force of Arms" -msgstr "" +msgstr "Silahların Gücü" msgid "Save the Dwarves" -msgstr "" +msgstr "Cüceleri Kurtar" msgid "Carator Mines" -msgstr "" +msgstr "Carator Madenleri" msgid "Turning Point" msgstr "Dönüm Noktası" msgid "scenarioName|Defender" -msgstr "" +msgstr "Savunucu" msgid "Corlagon's Defense" -msgstr "" +msgstr "Corlagon'un Savunması" msgid "The Crown" msgstr "Taç" @@ -1101,7 +1134,7 @@ msgid "Betrayal" msgstr "İhanet" msgid "Final Justice" -msgstr "" +msgstr "Son Adalet" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " @@ -1109,29 +1142,44 @@ msgid "" "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" +"Roland, kardeşine karşı isyan savaşını başlatmak için kalesinin yakınındaki " +"lordları yenmenize ihtiyaç duyuyor. Birbirleriyle müttefik değiller, bu " +"yüzden zamanlarının çoğunu birbirleriyle savaşarak geçirecekler. Tüm " +"kalelerini ve kahramanlarını yendiğinizde zafer sizindir." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" +"Yerel lordlar Roland'a bağlılık yemini etmeyi reddediyor ve bastırılmaları " +"gerekiyor. Zengin ve güçlüler, bu yüzden zorlu bir mücadeleye hazır olun. " +"Kazanmak için tüm düşman kalelerini ele geçirin." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" +"Göreviniz Cüceleri Archibald'ın güçlerine karşı savunmaktır. Kazanmak için " +"tüm düşman kasabalarını ve kalelerini ele geçirin ve tüm cüce kasabalarını " +"bir kerede kaybetmemeye dikkat edin, aksi takdirde düşman kazanmış olur." msgid "" "You will face four allied enemies in a straightforward fight for resources " "and treasure. Capture all of the enemy castles for victory." msgstr "" +"Kaynaklar ve hazine için dört müttefik düşmanla düz bir mücadelede karşı " +"karşıya geleceksin. Zafer için tüm düşman kalelerini ele geçir." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" +"Düşmanlarınız size karşı müttefik ve yakınınızda başlıyor, bu yüzden " +"savaşmaya hazır olun. Kazanmak için bu küçük vadideki dört kalenin hepsini " +"ele geçirmeniz gerekecek." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " @@ -1139,6 +1187,10 @@ msgid "" "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" +"Noraston'daki Büyücüler Loncası, Archibald'ın müttefiklerinden gelen bir " +"saldırıya karşı Roland'ın yardımını istedi. Kazanmak için tüm düşman " +"kalelerini ele geçirin ve Noraston'u kaybetmeyin, yoksa senaryoyu " +"kaybedersiniz. (İpucu: Okyanustaki bir adada düşman kalesi var.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " @@ -1146,11 +1198,17 @@ msgid "" "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" +"Mümkün olduğunca büyük bir ordu toplayın ve düşman kalesini 8 hafta içinde " +"ele geçirin. Karşınızda yalnızca bir düşman var, ancak düşman kalesine " +"ulaşmak için uzun bir yol kat etmelisiniz. Bu senaryonun sonunda ordunuzda " +"bulunan herhangi bir birlik, son savaşta sizinle olacak." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" +"Archibald'ın kahramanları bulmadan önce Tacı bulun. Roland, Archibald'a " +"karşı son savaşta Taca ihtiyaç duyacaktır." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " @@ -1158,44 +1216,53 @@ msgid "" "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" +"Üç müttefik düşman önünüzde duruyor ve zafer, Lord Corlagon da dahil. Roland " +"kuzeybatıdaki bir kalede ve düşmana yenilirse kaybedeceksiniz. Lord " +"Corlagon'u yakalamanın, final senaryosunda size karşı savaşmamasını " +"sağlayacağını unutmayın." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" +"Bu son savaş. Hem siz hem de düşmanınız tepeden tırnağa silahlanmış " +"durumdasınız ve hepsi size karşı müttefik. Savaşı bitirmek için Archibald'ı " +"yakalayın!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" +"Taraf değiştirmek, düşmanın kalesine karşı üç kale bırakır. Bu savaş, " +"savaşın geri kalanında karşılaşacağınız en kolay savaş olacak...hain." msgid "Barbarian Wars" -msgstr "" +msgstr "Barbar savaşları" msgid "First Blood" -msgstr "" +msgstr "İlk Kan" msgid "Necromancers" -msgstr "" +msgstr "Nekromansörler" msgid "Slay the Dwarves" -msgstr "" +msgstr "Cüceleri öldür" msgid "Country Lords" -msgstr "" +msgstr "Ülke Lordları" msgid "Dragon Master" -msgstr "" +msgstr "Ejderha Ustası" msgid "Rebellion" -msgstr "" +msgstr "İsyan" msgid "Apocalypse" -msgstr "" +msgstr "Kıyamet" msgid "Greater Glory" -msgstr "" +msgstr "Büyük Zafer" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " @@ -1203,6 +1270,10 @@ msgid "" "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" +"Kral Archibald, bu bölgedeki üç düşmanı yenmenizi istiyor. Birbirleriyle " +"müttefik değiller, bu yüzden enerjilerinin çoğunu kendi aralarında savaşarak " +"geçirecekler. Tüm düşman kalelerine sahip olduğunuzda ve savaşacak kahraman " +"kalmadığında kazanacaksınız." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " @@ -1210,6 +1281,10 @@ msgid "" "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" +"Kuzeydeki barbar kabileleri fethederek birleştirmelisiniz. Önceki görevde " +"olduğu gibi, düşman size karşı müttefik değil, ancak emrinde daha fazla " +"kaynak var. Tüm düşman kalelerine sahip olduğunuzda ve savaşacak kahraman " +"kalmadığında kazanacaksınız." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " @@ -1217,6 +1292,10 @@ msgid "" "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" +"İyiliksever büyücüler Necromancer'ların kalesini ele geçirdi. Zafere ulaşmak " +"için onu geri almalısınız. Güçlü bir orduyla başlasanız da, bir kalenizin " +"olmadığını ve 7 gün içinde bir tane almanız gerektiğini, aksi takdirde bu " +"savaşı kaybedeceğinizi unutmayın. (İpucu: En yakın kale güneydoğudadır.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " @@ -1224,18 +1303,29 @@ msgid "" "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" +"Cücelerin Kral Archibald'ın planlarına müdahale edebilmeleri için " +"fethedilmeleri gerekiyor. Roland'ın güçlerinin birden fazla kahramanı ve " +"başlamak için birçok kasabası var, bu yüzden birden fazla yönden saldırıya " +"hazır olun. Zaferi elde etmek için tüm düşman kasabalarını ve kalelerini ele " +"geçirmelisiniz." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" +"Roland'ın güçlerinin önderlik ettiği bir köylü isyanını bastırmalısınız. " +"Hepsi size karşı müttefik, ancak size yardım edecek deneyimli bir kahraman " +"olan Lord Corlagon var. Kazanmak için tüm düşman kalelerini ele geçirin." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" +"Bu görevde size karşı müttefik olan iki düşman var. İkisi de iyi silahlanmış " +"ve sizi adalarından çıkarmaya çalışıyorlar. Onlardan kaçının ve kazanmak " +"için Dragon City'yi ele geçirin." msgid "" "Your orders are to conquer the country lords that have sworn to serve " @@ -1243,56 +1333,71 @@ msgid "" "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" +"Emriniz, Roland'a hizmet etmeye yemin etmiş olan ülke lordlarını " +"fethetmektir. Tüm düşman kaleleri size karşı birleşmiştir. Kaleniz olmadan " +"başladığınız için, haftanın sonundan önce bir kale ele geçirmek için acele " +"etmelisiniz. Zafer için tüm düşman kalelerini ele geçirin." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" +"Roland'ın kahramanları bulmadan önce Tacı bulun. Archibald, Roland'a karşı " +"son savaşta Taç'a ihtiyaç duyacaktır." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" +"Bu son savaş. Hem siz hem de düşmanınız tepeden tırnağa silahlanmış " +"durumdasınız ve hepsi size karşı müttefik. Savaşı kazanmak için Roland'ı " +"yakalayın ve Archibald'ı savaşta kaybetmemeye dikkat edin!" msgid "Arrow's Flight" -msgstr "" +msgstr "Ok'un Uçuşu" msgid "Island of Chaos" -msgstr "" +msgstr "Kaos Adası" msgid "The Abyss" -msgstr "" +msgstr "Boşluk" msgid "Uprising" -msgstr "" +msgstr "Yükseliş" msgid "Aurora Borealis" -msgstr "" +msgstr "Kuzey Işıkları" msgid "Betrayal's End" -msgstr "" +msgstr "İhanet'in Sonu" msgid "Corruption's Heart" -msgstr "" +msgstr "Yolsuzluğun Kalbi" msgid "The Giant's Pass" -msgstr "" +msgstr "Dev'in Geçişi" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" +"İmparatorluğa bu bölgede faaliyet gösterebilmesi için gerekli olanakları " +"sağlamak amacıyla, asi yerel beyleri kontrol altına alın." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" +"Bu alandaki tüm muhalefeti ortadan kaldırın. Daha sonra eserin ilk parçası " +"sizin olacak." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" +"Kuzeydoğudaki büyücüler isyan ediyor! İmparatorluğun iyiliği için dağlara " +"giderken onların zayıf ayaklanmasını bastırmalısın." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " @@ -1300,42 +1405,55 @@ msgid "" "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" +"Kraeger, sizin gelişinize hazırlık yaptıktan sonra, görevinizi engellemek " +"için bir nekromansör gücü ayarladı. Üçüncü haftanın ilk gününden önce " +"Scabsdale kalesini ele geçirmelisiniz, aksi takdirde Nekromansörler sizin " +"için çok güçlü olacak." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" +"Bu bölgedeki barbar despot henüz sizin varlığınızdan habersiz. Keşfedilmeden " +"ve saldırıya uğramadan önce hemen güçlerinizi toplayın! Tüm düşman güçlerini " +"bastırarak bölgeyi güvence altına alın." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" +"İmparatorluk bu bölgede zayıf. Bu bölgedeki tüm güçleri tamamen alt " +"edemeyeceksiniz, bu yüzden misilleme saldırıları başlamadan önce elinizden " +"geleni alın. Unutmayın, gerçek amacınız Anduran Miğferi'ni ele geçirmek." msgid "For the good of the Empire, eliminate Kraeger." -msgstr "" +msgstr "İmparatorluğun iyiliği için Kraeger'ı ortadan kaldırın." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" +"Sonunda, İmparatorluğu nekromanserin kötülüğünden kurtarma fırsatına ve " +"olanaklarına sahipsin. Onları tamamen yok et ve sonsuza dek bir kahraman " +"olarak anılacaksın." msgid "Border Towns" -msgstr "" +msgstr "Sınır Kasabaları" msgid "Conquer and Unify" -msgstr "" +msgstr "Fethet ve Birleştir" msgid "Crazy Uncle Ivan" -msgstr "" +msgstr "Çılgın Amca Ivan" msgid "The Wayward Son" -msgstr "" +msgstr "Wayward oğlu" msgid "Ivory Gates" -msgstr "" +msgstr "Fildişi Kapılar" msgid "The Elven Lands" msgstr "Elf Diyarları" @@ -1344,66 +1462,88 @@ msgid "The Epic Battle" msgstr "Epik Savaş" msgid "The Southern War" -msgstr "" +msgstr "Güney Savaşı" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" +"Tüm düşman kabileleri fethedin ve birleştirin. Tüm torunların atası olan " +"kahraman Jarkonas'ı kaybetmeyin." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" +"Rakibiniz Harondale Krallığı, sınırınızdaki zayıf kasabalara saldırıyor! İlk " +"saldırılarından kurtulun ve onları tamamen ezin!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" +"İnatçı oğlunuz Joseph'i bulun, onun ıssız topraklarda yaşadığı söyleniyor. " +"Üçüncü ayın ilk gününden önce yapın, aksi takdirde ailenize hiçbir faydası " +"olmayacaktır." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be of no help to your kingdom." msgstr "" +"Çılgın amcan Ivan'ı kurtar. Dördüncü ayın ilk gününden önce onu bul yoksa " +"krallığına hiçbir faydası olmayacak." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" +"Krallığınızın güney sınırına saldıran barbarları yok edin! Düşmüş " +"kasabalarınızı kurtarın ve ardından orman krallığını istila edin. Hiçbir " +"düşmanı ayakta bırakmayın." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." -msgstr "" +msgstr "İhanet sonucu düşen Fildişi Kapılar kalesini geri al." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so we will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" +"Elflerin gözüne gir. Ağaçların kesilmesine izin vermeyecekler, bu yüzden " +"sana her 2 haftada bir odun göndereceğiz. Görevini yedinci ayın ilk gününden " +"önce tamamlamalısın, yoksa krallık kesinlikle düşecek.Elflerin gözüne gir. " +"Ağaçların kesilmesine izin vermeyecekler, bu yüzden sana her 2 haftada bir " +"odun göndereceğiz. Görevini yedinci ayın ilk gününden önce tamamlamalısın, " +"yoksa krallık kesinlikle düşecek." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" +"Bu, rakip krallığınız Harondale'e karşı son savaşınız. Herkesi ortadan " +"kaldırın ve kahraman Jarkonas VI'yı kaybetmeyin." msgid "Fount of Wizardry" -msgstr "" +msgstr "Büyücülük Pınarı" msgid "Power's End" -msgstr "" +msgstr "Güç'ün Sonu" msgid "The Eternal Scrolls" -msgstr "" +msgstr "Ebedi Parşömenler" msgid "The Shrouded Isles" -msgstr "" +msgstr "Örtülü Adalar" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" +"Göreviniz büyülü Shrouded Isles'daki savaşan büyücüleri yenmektir. Bu görevi " +"tamamlamak size rakiplerinize karşı bir mücadele şansı verecektir." msgid "" "The location of the great library has been discovered! You must make your " @@ -1589,7 +1729,7 @@ msgid "campaignBonus|Foremost Scroll" msgstr "" msgid "campaignBonus|Gauntlets" -msgstr "" +msgstr "Eldivenler" msgid "campaignBonus|Hideous Mask" msgstr "" @@ -1625,7 +1765,7 @@ msgid "campaignBonus|Tax Lien" msgstr "" msgid "campaignBonus|Thunder Mace" -msgstr "" +msgstr "Yıldırım Topuzu" msgid "campaignBonus|Traveler's Boots" msgstr "" @@ -1767,7 +1907,7 @@ msgstr "" msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." -msgstr "" +msgstr "Krallık senaryonun başında ekstradan %{amount} %{resource} alacaktır." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " @@ -1832,17 +1972,14 @@ msgstr "" msgid "This building has been disabled." msgstr "" -#, fuzzy msgid "Cannot afford the %{name}." -msgstr "%{name} 'i hareket ettir" +msgstr "%{name} karşılanamıyor." -#, fuzzy msgid "The %{name} is already built." -msgstr "Ayna görüntüsü yok edildi!" +msgstr "%{name} zaten inşa edildi." -#, fuzzy msgid "Cannot build the %{name}." -msgstr "%{name} 'i hareket ettir" +msgstr "%{name} inşa edilemiyor." msgid "Build %{name}." msgstr "" @@ -2069,9 +2206,8 @@ msgstr "" msgid "Cannot recruit - you have too many Heroes." msgstr "" -#, fuzzy msgid "Cannot afford a Hero." -msgstr "Son birlik hareket ettirilemiyor" +msgstr "Kahraman karşılanamıyor." msgid "There is no room in the garrison for this army." msgstr "" @@ -2206,55 +2342,55 @@ msgid "warlock|Red Tower" msgstr "" msgid "Black Tower" -msgstr "" +msgstr "Kara Kule" msgid "Library" msgstr "Kütüphane" msgid "Orchard" -msgstr "" +msgstr "Meyve bahçesi" msgid "Habitat" -msgstr "" +msgstr "Habitat" msgid "Pen" -msgstr "" +msgstr "Ağıl" msgid "Foundry" -msgstr "" +msgstr "Dökümhane" msgid "Upg. Foundry" -msgstr "" +msgstr "Dökümhane Geliştir" msgid "Cliff Nest" -msgstr "" +msgstr "Uçurum Yuvası" msgid "Ivory Tower" -msgstr "" +msgstr "Fildişi kule" msgid "Upg. Ivory Tower" -msgstr "" +msgstr "Fildişi kule Geliştir" msgid "Cloud Castle" -msgstr "" +msgstr "Bulut Kalesi" msgid "Upg. Cloud Castle" -msgstr "" +msgstr "Bulut Kalesi Geliştir" msgid "Storm" -msgstr "" +msgstr "Yıldırım" msgid "Skull Pile" -msgstr "" +msgstr "Kafatası Yığını" msgid "Excavation" -msgstr "" +msgstr "Kazı" msgid "Graveyard" -msgstr "" +msgstr "Mezarlık" msgid "Upg. Graveyard" -msgstr "" +msgstr "Mezarlığı Geliştir" msgid "Upg. Pyramid" msgstr "" @@ -2378,49 +2514,49 @@ msgid "" msgstr "" msgid "Thieves' Guild" -msgstr "" +msgstr "Hırsızlar Loncası" msgid "Tavern" -msgstr "" +msgstr "Meyhane" msgid "Shipyard" -msgstr "" +msgstr "Liman" msgid "Well" -msgstr "" +msgstr "Su Kuyusu" msgid "Statue" -msgstr "" +msgstr "Heykel" msgid "Marketplace" -msgstr "" +msgstr "Market" msgid "Moat" -msgstr "" +msgstr "Hendek" msgid "Castle" -msgstr "" +msgstr "Kale" msgid "Tent" -msgstr "" +msgstr "Çadır" msgid "Captain's Quarters" -msgstr "" +msgstr "Kaptanın Karargahı" msgid "Mage Guild, Level 1" -msgstr "" +msgstr "Büyücü Loncası, Level 1" msgid "Mage Guild, Level 2" -msgstr "" +msgstr "Büyücü Loncası, Level 2" msgid "Mage Guild, Level 3" -msgstr "" +msgstr "Büyücü Loncası, Level 3" msgid "Mage Guild, Level 4" -msgstr "" +msgstr "Büyücü Loncası, Level 4" msgid "Mage Guild, Level 5" -msgstr "" +msgstr "Büyücü Loncası, Level 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " @@ -2779,234 +2915,245 @@ msgid "Need: " msgstr "" msgid "New Game" -msgstr "" +msgstr "Yeni Oyun" msgid "Start a single or multi-player game." -msgstr "" +msgstr "Tek yada Çoklu bir oyun başlat." msgid "Load Game" -msgstr "" +msgstr "Oyunu Yükle" msgid "Load a previously saved game." -msgstr "" +msgstr "Önceden kaydedilmiş bir oyunu yükle." msgid "Save Game" -msgstr "" +msgstr "Oyunu Kaydet" msgid "Save the current game." -msgstr "" +msgstr "Şu anki oyunu kaydet." msgid "Quit" -msgstr "" +msgstr "Çıkış" msgid "Quit out of Heroes of Might and Magic II." -msgstr "" +msgstr "Heroes of Might and Magic II'den çık." msgid "Language" -msgstr "" +msgstr "Dil" msgid "Graphics" -msgstr "" +msgstr "Grafikler" msgid "Black & White" -msgstr "" +msgstr "Siyah & Beyaz" msgid "Mouse Cursor" -msgstr "" +msgstr "Fare İmleci" msgid "Color" -msgstr "" +msgstr "Renkli" msgid "Text Support" -msgstr "" +msgstr "Yazı Desteği" msgid "Change the language of the game." -msgstr "" +msgstr "Oyunun dilini değiştir." msgid "Select Game Language" -msgstr "" +msgstr "Oyunun dilini seç" msgid "Change the graphics settings of the game." -msgstr "" +msgstr "Oyunun grafik ayarlarını değiş." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." -msgstr "" +msgstr "Renkli imleci aç ya da kapat. Bu sadece estetik bir seçimdir." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" +"Oyun içindeki pencereler ve olaylar hakkında ek bilgi çıkışı sağlamak için " +"metin desteği modunu açın/kapatın." msgid "" "Map\n" "Difficulty" msgstr "" +"Harita\n" +"Zorluğu" msgid "" "Game\n" "Difficulty" msgstr "" +"Oyun\n" +"Zorluğu" msgid "Rating" -msgstr "" +msgstr "Derecelendirme" msgid "Map Size" -msgstr "" +msgstr "Harita Boyu" msgid "Opponents" -msgstr "" +msgstr "Rakipler" msgid "Class" -msgstr "" +msgstr "Sınıf" msgid "" "Victory\n" "Conditions" msgstr "" +"Zafer\n" +"Şartları" msgid "" "Loss\n" "Conditions" msgstr "" +"Yenilgi\n" +"Şartları" msgid "First select recipients!" -msgstr "" +msgstr "Önce alıcıları seçin!" msgid "You cannot select %{resource}!" -msgstr "" +msgstr "%{resource} seçemezsin!" msgid "Select count %{resource}:" -msgstr "" +msgstr "%{resource} sayısını seç:" msgid "Select Recipients" -msgstr "" +msgstr "Alıcıları Seçin" msgid "Your Funds" -msgstr "" +msgstr "Paranız" msgid "Planned Gift" -msgstr "" +msgstr "Planlanmış Hediye" msgid "Gift from %{name}" -msgstr "" +msgstr "%{name}'dan hediye" msgid "Resolution" -msgstr "" +msgstr "Çözünürlük" msgid "Fullscreen" -msgstr "" +msgstr "Tam Ekran" msgid "window|Mode" -msgstr "" +msgstr "Modu" msgid "Windowed" -msgstr "" +msgstr "Pencere" msgid "V-Sync" -msgstr "" +msgstr "Dikey Eşitleme" msgid "FPS" -msgstr "" +msgstr "FPS" -#, fuzzy msgid "System Info" -msgstr "Sistem Seçenekleri" +msgstr "Sistem Bilgisi" msgid "Change the resolution of the game." -msgstr "" +msgstr "Oyunun çözünürlüğünü değiş." msgid "Select Game Resolution" -msgstr "" +msgstr "Oyunun çözünürlüğünü seç" msgid "Toggle between fullscreen and windowed modes." -msgstr "" +msgstr "Tam ve pencereli ekran arasında geçiş yap." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." -msgstr "" +msgstr "Dikey eşitleme ayarı bazı monitörlerde yırtılma sorununu çözer." msgid "Show extra information such as FPS and current time." -msgstr "" +msgstr "FPS ve zaman hakkında ekstra bilgi göster." msgid "Hotkey: " -msgstr "" +msgstr "Kısayol: " msgid "Category: " -msgstr "" +msgstr "Kategori: " msgid "Event: " -msgstr "" +msgstr "Etkinlik: " msgid "Hot Keys:" -msgstr "" +msgstr "Kısayollar:" msgid "Evil" -msgstr "" +msgstr "Şeytani" msgid "Good" -msgstr "" +msgstr "İyi" msgid "Interface Type" -msgstr "" +msgstr "Arayüz Tipi" msgid "Hide" -msgstr "" +msgstr "Gizle" msgid "Show" -msgstr "" +msgstr "Göster" msgid "Interface" -msgstr "" +msgstr "Arayüz" msgid "Slow" -msgstr "" +msgstr "Yavaş" msgid "Normal" -msgstr "" +msgstr "Normal" msgid "Fast" -msgstr "" +msgstr "Hızlı" msgid "Very Fast" -msgstr "" +msgstr "Çok Hızlı" msgid "Scroll Speed" -msgstr "" +msgstr "Kaydırma Hızı" msgid "Toggle the type of interface you want to use." -msgstr "" +msgstr "Kullanmak istediğiniz arayüz türünü değiştirin." msgid "Toggle interface visibility." -msgstr "" +msgstr "Arayüz görünürlüğünü değiştirin." msgid "Sets the speed at which you scroll the window." -msgstr "" +msgstr "Pencereyi kaydırma hızını ayarlar." msgid "Select Game Language:" -msgstr "" +msgstr "Oyunun dilini seç:" -#, fuzzy msgid "Select Language:" -msgstr "Büyü hedefini seç" +msgstr "Dil Seç:" msgid "Click to choose the selected language." -msgstr "" +msgstr "Seçili dili seçmek için tıklayın." msgid "%{name} has gained a level." -msgstr "" +msgstr "%{name} level kazandı." msgid "%{skill} +1" -msgstr "" +msgstr "%{skill} +1" msgid "You have learned %{skill}." -msgstr "" +msgstr "%{skill} öğrendin." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" +"%{name} level kazandı.\n" +"\n" +"%{skill} +1" msgid "" "You may learn either:\n" @@ -3014,14 +3161,19 @@ msgid "" "or\n" "%{skill2}" msgstr "" +"Ya %{skill1}\n" +"ya da\n" +"%{skill2} öğrenebilirsin." msgid "n/a" -msgstr "" +msgstr "n/a" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" +"Lütfen kaliteli ürünlerimizi inceleyin. Bir takas teklif etmek isterseniz, " +"takas etmek istediğiniz ve takas etmek istediğiniz ürünlere tıklayın." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " @@ -3064,9 +3216,8 @@ msgstr "" msgid "already learned" msgstr "" -#, fuzzy msgid "treeOfKnowledge|free" -msgstr "Bilgi" +msgstr "ücretsiz" msgid "already claimed" msgstr "" @@ -3255,9 +3406,8 @@ msgstr "" msgid "Click to start placing the selected hero." msgstr "" -#, fuzzy msgid "%{color} %{race}" -msgstr "%{name} %{race}" +msgstr "%{color} %{race}" msgid "You will place" msgstr "" @@ -3268,13 +3418,11 @@ msgstr "" msgid "Click to select this color." msgstr "" -#, fuzzy msgid "Select Treasure:" -msgstr "Büyü hedefini seç" +msgstr "Defineyi seç:" -#, fuzzy msgid "Select Ocean Object:" -msgstr "Büyü hedefini seç" +msgstr "Okyanus Hedefini Seç:" msgid "Castle/town placing" msgstr "" @@ -3793,10 +3941,10 @@ msgid "Create a new map from scratch." msgstr "" msgid "New Map" -msgstr "" +msgstr "Yeni Harita" msgid "Load Map" -msgstr "" +msgstr "Harita Yükle" msgid "Load an existing map." msgstr "" @@ -3897,7 +4045,7 @@ msgid "Rocks" msgstr "" msgid "Trees" -msgstr "" +msgstr "Ağaçlar" msgid "Water Objects" msgstr "" @@ -4511,7 +4659,7 @@ msgid "Please make sure that campaign files are correct and present." msgstr "" msgid "Days spent" -msgstr "" +msgstr "Geçen Günler" msgid "The number of days spent on this campaign." msgstr "" @@ -4764,7 +4912,7 @@ msgid "hotkey|hot seat game" msgstr "Sistem seçeneklerini özelleştirin" msgid "hotkey|battle only game" -msgstr "" +msgstr "sadece savaş oyunu" msgid "hotkey|choose the original campaign" msgstr "" @@ -5232,7 +5380,7 @@ msgid "" msgstr "" msgid "Battle Only" -msgstr "" +msgstr "Sadece Savaş" msgid "Setup and play a battle without loading any map." msgstr "" @@ -5412,7 +5560,7 @@ msgid "Major Event!" msgstr "" msgid "Scenario:" -msgstr "" +msgstr "Senaryo:" msgid "Game Difficulty:" msgstr "" @@ -5430,7 +5578,7 @@ msgid "Click here to select which scenario to play." msgstr "" msgid "Scenario" -msgstr "" +msgstr "Senaryo" msgid "Game Difficulty" msgstr "" @@ -5623,7 +5771,7 @@ msgid "" msgstr "" msgid "Are you sure you want to quit?" -msgstr "" +msgstr "Çıkmak istediğine emin misin?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" From 14f081e0fb4a97f39a56ae0dd78d329949b465ac Mon Sep 17 00:00:00 2001 From: George King <98261225+GeorgeK1ng@users.noreply.github.com> Date: Wed, 4 Dec 2024 04:53:36 +0100 Subject: [PATCH 14/30] Updated Czech translation (#9299) --- files/lang/cs.po | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/files/lang/cs.po b/files/lang/cs.po index b01850f53ab..98af8a0b3b8 100644 --- a/files/lang/cs.po +++ b/files/lang/cs.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" -"PO-Revision-Date: 2024-10-21 20:45+0200\n" +"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"PO-Revision-Date: 2024-11-26 19:28+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: Czech \n" "Language: cs\n" @@ -6142,12 +6142,14 @@ msgid "Keyboard|ABC" msgstr "ABC" msgid "The entered value is invalid." -msgstr "" +msgstr "Vložená hodnota není platná." msgid "" "The entered value is out of range.\n" "It should be not less than %{minValue} and not more than %{maxValue}." msgstr "" +"Zadaná hodnota je mimo povolený rozsah.\n" +"Musí být minimálně %{minValue} a maximálně %{maxValue}." msgid "Kingdom Income" msgstr "Příjmy království" @@ -6885,15 +6887,14 @@ msgstr "" "nelítostném oceánu. Vděčně říká: \"Dal bych vám artefakt jako odměnu, ale už " "nemáte místo.\"" -#, fuzzy msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" -"Vytáhli jste ztroskotaného námořníka odsouzeného k nevyhnutelné smrti v " -"nelítostném oceánu. Vděčně vás odměňuje za váš laskavý skutek tím, že vám " -"dává %{art}." +"Zachránil jsi trosečníka z vraku lodi před jistou smrtí v nemilosrdném " +"oceánu. Vděčný za tvůj čin laskavosti tě odměňuje tím, že ti daruje " +"%{art}." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "Leprikón vám nabízí %{art} za %{gold} zlata." @@ -7685,7 +7686,6 @@ msgstr "" "\"Mám pro tebe hádanku,\" říká Sfinga. \"Odpověz správně a budeš odměněn. " "Odpověz špatně a budeš sněden. Přijímáš výzvu?\"" -#, fuzzy msgid "" "The Sphinx asks you the following riddle:\n" "\n" @@ -7693,15 +7693,16 @@ msgid "" msgstr "" "Sfinga vám pokládá následující hádanku:\n" "\n" -"'%{riddle}'\n" -"\n" -"Jaká je vaše odpověď?" +"'" msgid "" "sphinx|'\n" "\n" "Your answer?" msgstr "" +"'\n" +"\n" +"Vaše odpověď?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " @@ -8641,10 +8642,10 @@ msgid "speed|Instant" msgstr "Okamžitá" msgid "Click this button to adjust the level of zoom." -msgstr "" +msgstr "Klikněte na toto tlačítko pro úpravu úrovně přiblížení." msgid "Zoom" -msgstr "" +msgstr "Přiblížení" msgid "week|Squirrel" msgstr "Veverky" From 997dcc3177cd055a9b8fade108ac7deecbe4a5db Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:40:59 +0800 Subject: [PATCH 15/30] Update translation files (#9314) --- docs/json/lang_cs.json | 2 +- docs/json/lang_tr.json | 2 +- files/lang/cs.po | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/json/lang_cs.json b/docs/json/lang_cs.json index 2142b6c32eb..1ada26fdf03 100644 --- a/docs/json/lang_cs.json +++ b/docs/json/lang_cs.json @@ -1 +1 @@ -{"schemaVersion":1,"label":"Czech","message":"99%","color":"green"} +{"schemaVersion":1,"label":"Czech","message":"100%","color":"green"} diff --git a/docs/json/lang_tr.json b/docs/json/lang_tr.json index 3f7ad39c5f7..3bcdb018ee2 100644 --- a/docs/json/lang_tr.json +++ b/docs/json/lang_tr.json @@ -1 +1 @@ -{"schemaVersion":1,"label":"Turkish","message":"6%","color":"red"} +{"schemaVersion":1,"label":"Turkish","message":"16%","color":"red"} diff --git a/files/lang/cs.po b/files/lang/cs.po index 98af8a0b3b8..0cc0ef43aa0 100644 --- a/files/lang/cs.po +++ b/files/lang/cs.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-24 10:21+0000\n" +"POT-Creation-Date: 2024-12-04 04:06+0000\n" "PO-Revision-Date: 2024-11-26 19:28+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: Czech \n" @@ -6893,8 +6893,7 @@ msgid "" "%{art}." msgstr "" "Zachránil jsi trosečníka z vraku lodi před jistou smrtí v nemilosrdném " -"oceánu. Vděčný za tvůj čin laskavosti tě odměňuje tím, že ti daruje " -"%{art}." +"oceánu. Vděčný za tvůj čin laskavosti tě odměňuje tím, že ti daruje %{art}." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "Leprikón vám nabízí %{art} za %{gold} zlata." From f4b1cdfd0bae072713116f9b75b471c8949eecd9 Mon Sep 17 00:00:00 2001 From: Zenseii Date: Wed, 4 Dec 2024 14:38:31 +0100 Subject: [PATCH 16/30] Fix French and German ultimate staff sprite with PoL assets (#9204) --- src/fheroes2/agg/agg.cpp | 6 +++--- src/fheroes2/agg/agg.h | 4 ++-- src/fheroes2/agg/agg_image.cpp | 36 +++++++++++++++++++++++++++----- src/fheroes2/agg/bin_info.cpp | 2 +- src/fheroes2/game/fheroes2.cpp | 2 +- src/fheroes2/gui/ui_language.cpp | 2 +- 6 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/fheroes2/agg/agg.cpp b/src/fheroes2/agg/agg.cpp index 73c0c6cce06..1273db9fd3d 100644 --- a/src/fheroes2/agg/agg.cpp +++ b/src/fheroes2/agg/agg.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2019 - 2022 * + * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * @@ -37,9 +37,9 @@ namespace fheroes2::AGGFile heroes2x_agg; } -std::vector AGG::getDataFromAggFile( const std::string & key ) +std::vector AGG::getDataFromAggFile( const std::string & key, const bool ignoreExpansion ) { - if ( heroes2x_agg.isGood() ) { + if ( !ignoreExpansion && heroes2x_agg.isGood() ) { // Make sure that the below container is not const and not a reference // so returning it from the function will invoke a move constructor instead of copy constructor. std::vector buf = heroes2x_agg.read( key ); diff --git a/src/fheroes2/agg/agg.h b/src/fheroes2/agg/agg.h index 2c9c4f9a90c..4002bfe8b10 100644 --- a/src/fheroes2/agg/agg.h +++ b/src/fheroes2/agg/agg.h @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2019 - 2022 * + * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * @@ -56,7 +56,7 @@ namespace AGG std::string _expansionAGGFilePath; }; - std::vector getDataFromAggFile( const std::string & key ); + std::vector getDataFromAggFile( const std::string & key, const bool ignoreExpansion ); } #endif diff --git a/src/fheroes2/agg/agg_image.cpp b/src/fheroes2/agg/agg_image.cpp index f4f0d132617..04ca18ba258 100644 --- a/src/fheroes2/agg/agg_image.cpp +++ b/src/fheroes2/agg/agg_image.cpp @@ -268,7 +268,7 @@ namespace // BMP files within AGG are not Bitmap images! fheroes2::Sprite loadBMPFile( const std::string & path ) { - const std::vector & data = AGG::getDataFromAggFile( path ); + const std::vector & data = AGG::getDataFromAggFile( path, false ); if ( data.size() < 6 ) { // It is an invalid BMP file. return {}; @@ -582,12 +582,32 @@ namespace void loadICN( const int id ); + void replacePOLAssetWithSW( const int id, const int assetIndex ) + { + const std::vector & body = ::AGG::getDataFromAggFile( ICN::getIcnFileName( id ), true ); + ROStreamBuf imageStream( body ); + + imageStream.seek( headerSize + assetIndex * 13 ); + + fheroes2::ICNHeader header1; + imageStream >> header1; + + fheroes2::ICNHeader header2; + imageStream >> header2; + const uint32_t dataSize = header2.offsetData - header1.offsetData; + + const uint8_t * data = body.data() + headerSize + header1.offsetData; + const uint8_t * dataEnd = data + dataSize; + + _icnVsSprite[id][assetIndex] = fheroes2::decodeICNSprite( data, dataEnd, header1 ); + } + void LoadOriginalICN( const int id ) { // If this assertion blows up then something wrong with your logic and you load resources more than once! assert( _icnVsSprite[id].empty() ); - const std::vector & body = ::AGG::getDataFromAggFile( ICN::getIcnFileName( id ) ); + const std::vector & body = ::AGG::getDataFromAggFile( ICN::getIcnFileName( id ), false ); if ( body.empty() ) { return; @@ -2492,7 +2512,7 @@ namespace throw std::logic_error( "The game resources are corrupted. Please use resources from a licensed version of Heroes of Might and Magic II." ); } - const std::vector & body = ::AGG::getDataFromAggFile( ICN::getIcnFileName( id ) ); + const std::vector & body = ::AGG::getDataFromAggFile( ICN::getIcnFileName( id ), false ); const uint32_t crc32 = fheroes2::calculateCRC32( body.data(), body.size() ); if ( id == ICN::SMALFONT ) { @@ -3405,7 +3425,7 @@ namespace // Since we cannot access game settings from here we are checking an existence // of one of POL resources as an indicator for this version. - if ( !::AGG::getDataFromAggFile( ICN::getIcnFileName( ICN::X_TRACK1 ) ).empty() ) { + if ( !::AGG::getDataFromAggFile( ICN::getIcnFileName( ICN::X_TRACK1 ), false ).empty() ) { fheroes2::Sprite editorIcon; fheroes2::h2d::readImage( "main_menu_editor_icon.image", editorIcon ); @@ -4216,6 +4236,12 @@ namespace fheroes2::Sprite & targetImage = _icnVsSprite[id][83]; targetImage = CreateHolyShoutEffect( _icnVsSprite[id][91], 1, 0 ); ApplyPalette( targetImage, PAL::GetPalette( PAL::PaletteType::PURPLE ) ); + + // The French and German Price of Loyalty assets contain a wrong artifact sprite at index 6. We replace it with the correct sprite from SW assets. + const int assetIndex = 6; + if ( _icnVsSprite[id][assetIndex].width() == 21 ) { + replacePOLAssetWithSW( id, assetIndex ); + } } return true; case ICN::OBJNARTI: @@ -5156,7 +5182,7 @@ namespace if ( tilImages.empty() ) { tilImages.resize( 4 ); // 4 possible sides - const std::vector & data = ::AGG::getDataFromAggFile( tilFileName[id] ); + const std::vector & data = ::AGG::getDataFromAggFile( tilFileName[id], false ); if ( data.size() < headerSize ) { // The important resource is absent! Make sure that you are using the correct version of the game. assert( 0 ); diff --git a/src/fheroes2/agg/bin_info.cpp b/src/fheroes2/agg/bin_info.cpp index 708b4ceec2c..4c27b31c2b4 100644 --- a/src/fheroes2/agg/bin_info.cpp +++ b/src/fheroes2/agg/bin_info.cpp @@ -58,7 +58,7 @@ namespace return mapIterator->second; } - Bin_Info::MonsterAnimInfo info( monsterID, AGG::getDataFromAggFile( GetFilename( monsterID ) ) ); + Bin_Info::MonsterAnimInfo info( monsterID, AGG::getDataFromAggFile( GetFilename( monsterID ), false ) ); if ( info.isValid() ) { _animMap[monsterID] = info; return info; diff --git a/src/fheroes2/game/fheroes2.cpp b/src/fheroes2/game/fheroes2.cpp index 6f821153d0f..10c68533628 100644 --- a/src/fheroes2/game/fheroes2.cpp +++ b/src/fheroes2/game/fheroes2.cpp @@ -327,7 +327,7 @@ int main( int argc, char ** argv ) const AudioManager::AudioInitializer audioInitializer( dataInitializer.getOriginalAGGFilePath(), dataInitializer.getExpansionAGGFilePath(), midiSoundFonts ); // Load palette. - fheroes2::setGamePalette( AGG::getDataFromAggFile( "KB.PAL" ) ); + fheroes2::setGamePalette( AGG::getDataFromAggFile( "KB.PAL", false ) ); fheroes2::Display::instance().changePalette( nullptr, true ); // init game data diff --git a/src/fheroes2/gui/ui_language.cpp b/src/fheroes2/gui/ui_language.cpp index 5617dd521d9..51a2210b40e 100644 --- a/src/fheroes2/gui/ui_language.cpp +++ b/src/fheroes2/gui/ui_language.cpp @@ -85,7 +85,7 @@ namespace fheroes2 SupportedLanguage getResourceLanguage() { - const std::vector & data = ::AGG::getDataFromAggFile( ICN::getIcnFileName( ICN::FONT ) ); + const std::vector & data = ::AGG::getDataFromAggFile( ICN::getIcnFileName( ICN::FONT ), false ); if ( data.empty() ) { // How is it possible to run the game without a font? assert( 0 ); From 3554bdb78b66f895ac746fd32c02b1a31a08a17c Mon Sep 17 00:00:00 2001 From: Zenseii Date: Thu, 5 Dec 2024 14:18:17 +0100 Subject: [PATCH 17/30] Add missing CP1254 button letters (#9315) --- src/fheroes2/gui/ui_font.cpp | 62 ++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/fheroes2/gui/ui_font.cpp b/src/fheroes2/gui/ui_font.cpp index 27a2bbc0842..723fdbd8d97 100644 --- a/src/fheroes2/gui/ui_font.cpp +++ b/src/fheroes2/gui/ui_font.cpp @@ -3396,7 +3396,7 @@ namespace updateSmallFontLetterShadow( font[252 - 32] ); } } - + // Turkish uses CP1254 void generateCP1254Alphabet( std::vector> & icnVsSprite ) { // Resize fonts. @@ -5804,6 +5804,64 @@ namespace fheroes2::SetPixel( released[220 - 32], offset + 4, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[220 - 32], offset + 8, offset + 0, buttonGoodReleasedColor ); } + void generateGoodCP1254ButtonFont( std::vector & released ) + { + // Increase size to fit full CP1254 set of characters. Fill with 1px transparent images. + const fheroes2::Sprite firstSprite{ released[0] }; + released.insert( released.end(), 160, firstSprite ); + + // We need 2 pixels from all sides of a letter to add extra effects. + const int32_t offset = 2; + + // Offset letters with diacritics above them. + for ( const int & charCode : { 214, 220, 221 } ) { + released[charCode - 32].setPosition( buttonFontOffset.x, buttonFontOffset.y - 2 ); + } + + // Offset G with breve. + released[208 - 32].setPosition( buttonFontOffset.x, buttonFontOffset.y - 3 ); + + // C with cedilla. + released[199 - 32].resize( released[67 - 32].width(), released[67 - 32].height() + 3 ); + released[199 - 32].reset(); + fheroes2::Copy( released[67 - 32], 0, 0, released[199 - 32], 0, 0, released[67 - 32].width(), released[67 - 32].height() ); + fheroes2::DrawLine( released[199 - 32], { offset + 5, offset + 10 }, { offset + 6, offset + 11 }, buttonGoodReleasedColor ); + fheroes2::DrawLine( released[199 - 32], { offset + 4, offset + 12 }, { offset + 5, offset + 12 }, buttonGoodReleasedColor ); + + // G with breve. + released[208 - 32].resize( released[71 - 32].width(), released[71 - 32].height() + 3 ); + released[208 - 32].reset(); + fheroes2::Copy( released[71 - 32], 0, 0, released[208 - 32], 0, 3, released[71 - 32].width(), released[71 - 32].height() ); + fheroes2::DrawLine( released[208 - 32], { offset + 3, offset + 0 }, { offset + 4, offset + 1 }, buttonGoodReleasedColor ); + fheroes2::DrawLine( released[208 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); + + // O with diaeresis. + released[214 - 32].resize( released[79 - 32].width(), released[79 - 32].height() + 2 ); + released[214 - 32].reset(); + fheroes2::Copy( released[79 - 32], 0, 0, released[214 - 32], 0, 2, released[79 - 32].width(), released[79 - 32].height() ); + fheroes2::SetPixel( released[214 - 32], offset + 3, offset + 0, buttonGoodReleasedColor ); + fheroes2::SetPixel( released[214 - 32], offset + 6, offset + 0, buttonGoodReleasedColor ); + + // U with diaeresis. + released[220 - 32].resize( released[85 - 32].width(), released[85 - 32].height() + 2 ); + released[220 - 32].reset(); + fheroes2::Copy( released[85 - 32], 0, 0, released[220 - 32], 0, 2, released[85 - 32].width(), released[85 - 32].height() ); + fheroes2::SetPixel( released[220 - 32], offset + 4, offset + 0, buttonGoodReleasedColor ); + fheroes2::SetPixel( released[220 - 32], offset + 8, offset + 0, buttonGoodReleasedColor ); + + // I with dot above. + released[221 - 32].resize( released[73 - 32].width(), released[73 - 32].height() + 2 ); + released[221 - 32].reset(); + fheroes2::Copy( released[73 - 32], 0, 0, released[221 - 32], 0, 2, released[73 - 32].width(), released[73 - 32].height() ); + fheroes2::SetPixel( released[221 - 32], offset + 2, offset + 0, buttonGoodReleasedColor ); + + // S with cedilla. + released[222 - 32].resize( released[83 - 32].width(), released[83 - 32].height() + 3 ); + released[222 - 32].reset(); + fheroes2::Copy( released[83 - 32], 0, 0, released[222 - 32], 0, 0, released[83 - 32].width(), released[83 - 32].height() ); + fheroes2::DrawLine( released[222 - 32], { offset + 4, offset + 10 }, { offset + 5, offset + 11 }, buttonGoodReleasedColor ); + fheroes2::DrawLine( released[222 - 32], { offset + 3, offset + 12 }, { offset + 4, offset + 12 }, buttonGoodReleasedColor ); + } } namespace fheroes2 @@ -5936,7 +5994,7 @@ namespace fheroes2 generateCP1252GoodButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; case SupportedLanguage::Turkish: - // generateGoodCP1254ButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); + generateGoodCP1254ButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; case SupportedLanguage::Vietnamese: // generateGoodCP1258ButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); From 0a28c04e9deaf63c16611f0b5fc458b518c366e1 Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Thu, 5 Dec 2024 16:20:20 +0300 Subject: [PATCH 18/30] Move the Battle::Unit::isUnderSpellEffect() with the AI-only logic to the AI code (#9316) --- src/fheroes2/ai/ai_battle_spell.cpp | 71 ++++++++++++++++++++++++++-- src/fheroes2/battle/battle_troop.cpp | 59 ----------------------- src/fheroes2/battle/battle_troop.h | 1 - 3 files changed, 68 insertions(+), 63 deletions(-) diff --git a/src/fheroes2/ai/ai_battle_spell.cpp b/src/fheroes2/ai/ai_battle_spell.cpp index 93911816820..c870f00b4cc 100644 --- a/src/fheroes2/ai/ai_battle_spell.cpp +++ b/src/fheroes2/ai/ai_battle_spell.cpp @@ -55,6 +55,72 @@ namespace return result; } + + bool isSpellcastUselessForUnit( const Battle::Unit & unit, const Spell & spell ) + { + const int spellID = spell.GetID(); + + if ( unit.isImmovable() && spellID != Spell::ANTIMAGIC ) { + return true; + } + + switch ( spellID ) { + case Spell::BLESS: + case Spell::MASSBLESS: + return unit.Modes( Battle::SP_BLESS ); + + case Spell::BLOODLUST: + return unit.Modes( Battle::SP_BLOODLUST ); + + case Spell::CURSE: + case Spell::MASSCURSE: + return unit.Modes( Battle::SP_CURSE ); + + case Spell::HASTE: + case Spell::MASSHASTE: + return unit.Modes( Battle::SP_HASTE ); + + case Spell::SHIELD: + case Spell::MASSSHIELD: + return unit.Modes( Battle::SP_SHIELD ); + + case Spell::SLOW: + case Spell::MASSSLOW: + return unit.Modes( Battle::SP_SLOW ); + + case Spell::STONESKIN: + case Spell::STEELSKIN: + return unit.Modes( Battle::SP_STONESKIN | Battle::SP_STEELSKIN ); + + case Spell::BLIND: + case Spell::PARALYZE: + case Spell::PETRIFY: + return unit.Modes( Battle::SP_BLIND | Battle::SP_PARALYZE | Battle::SP_STONE ); + + case Spell::DRAGONSLAYER: + return unit.Modes( Battle::SP_DRAGONSLAYER ); + + case Spell::ANTIMAGIC: + return unit.Modes( Battle::SP_ANTIMAGIC ); + + case Spell::BERSERKER: + return unit.Modes( Battle::SP_BERSERKER ); + + case Spell::HYPNOTIZE: + return unit.Modes( Battle::SP_HYPNOTIZE ); + + case Spell::MIRRORIMAGE: + return unit.Modes( Battle::CAP_MIRROROWNER ); + + case Spell::DISRUPTINGRAY: + return unit.GetDefense() < spell.ExtraValue(); + + default: + break; + } + + return false; + } } AI::SpellSelection AI::BattlePlanner::selectBestSpell( Battle::Arena & arena, const Battle::Unit & currentUnit, bool retreating ) const @@ -328,9 +394,8 @@ double AI::BattlePlanner::spellEffectValue( const Spell & spell, const Battle::U { const int spellID = spell.GetID(); - // Make sure this spell can be applied to the current unit (skip check for dispel estimation) - if ( !forDispel - && ( ( target.isImmovable() && spellID != Spell::ANTIMAGIC ) || target.isUnderSpellEffect( spell ) || !target.AllowApplySpell( spell, _commander ) ) ) { + // Make sure that this spell makes sense to apply (skip this check to evaluate the effect of dispelling) + if ( !forDispel && ( isSpellcastUselessForUnit( target, spell ) || !target.AllowApplySpell( spell, _commander ) ) ) { return 0.0; } diff --git a/src/fheroes2/battle/battle_troop.cpp b/src/fheroes2/battle/battle_troop.cpp index d256b1705ba..3d5d6305e50 100644 --- a/src/fheroes2/battle/battle_troop.cpp +++ b/src/fheroes2/battle/battle_troop.cpp @@ -831,65 +831,6 @@ bool Battle::Unit::AllowApplySpell( const Spell & spell, const HeroBase * applyi return ( GetMagicResist( spell, applyingHero ) < 100 ); } -bool Battle::Unit::isUnderSpellEffect( const Spell & spell ) const -{ - switch ( spell.GetID() ) { - case Spell::BLESS: - case Spell::MASSBLESS: - return Modes( SP_BLESS ); - - case Spell::BLOODLUST: - return Modes( SP_BLOODLUST ); - - case Spell::CURSE: - case Spell::MASSCURSE: - return Modes( SP_CURSE ); - - case Spell::HASTE: - case Spell::MASSHASTE: - return Modes( SP_HASTE ); - - case Spell::SHIELD: - case Spell::MASSSHIELD: - return Modes( SP_SHIELD ); - - case Spell::SLOW: - case Spell::MASSSLOW: - return Modes( SP_SLOW ); - - case Spell::STONESKIN: - case Spell::STEELSKIN: - return Modes( SP_STONESKIN | SP_STEELSKIN ); - - case Spell::BLIND: - case Spell::PARALYZE: - case Spell::PETRIFY: - return Modes( SP_BLIND | SP_PARALYZE | SP_STONE ); - - case Spell::DRAGONSLAYER: - return Modes( SP_DRAGONSLAYER ); - - case Spell::ANTIMAGIC: - return Modes( SP_ANTIMAGIC ); - - case Spell::BERSERKER: - return Modes( SP_BERSERKER ); - - case Spell::HYPNOTIZE: - return Modes( SP_HYPNOTIZE ); - - case Spell::MIRRORIMAGE: - return Modes( CAP_MIRROROWNER ); - - case Spell::DISRUPTINGRAY: - return GetDefense() < spell.ExtraValue(); - - default: - break; - } - return false; -} - bool Battle::Unit::ApplySpell( const Spell & spell, const HeroBase * applyingHero, TargetInfo & target ) { // HACK!!! Chain lightning is the only spell which can't be cast on allies but could be applied on them diff --git a/src/fheroes2/battle/battle_troop.h b/src/fheroes2/battle/battle_troop.h index 7ac30bc7110..e3f1ebc6da1 100644 --- a/src/fheroes2/battle/battle_troop.h +++ b/src/fheroes2/battle/battle_troop.h @@ -204,7 +204,6 @@ namespace Battle bool ApplySpell( const Spell & spell, const HeroBase * applyingHero, TargetInfo & target ); bool AllowApplySpell( const Spell & spell, const HeroBase * applyingHero, const bool forceApplyToAlly = false ) const; - bool isUnderSpellEffect( const Spell & spell ) const; std::vector getCurrentSpellEffects() const; void PostAttackAction( const Unit & enemy ); From db8136f9ca2faddbf9c56de4a5feb6c89f5723ba Mon Sep 17 00:00:00 2001 From: "Sergei Ivanov (Districh)" <113276641+Districh-ru@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:19:40 +0300 Subject: [PATCH 19/30] Use drawOnState() for buttons precessing and cache buttons' position for event handler loop (#9223) --- src/engine/image.cpp | 5 + src/engine/image.h | 1 + src/fheroes2/battle/battle_dialogs.cpp | 104 +++++++---- src/fheroes2/battle/battle_interface.cpp | 52 +++--- src/fheroes2/battle/battle_interface.h | 6 +- src/fheroes2/battle/battle_only.cpp | 12 +- src/fheroes2/castle/buildinginfo.cpp | 68 ++++---- src/fheroes2/castle/castle_dialog.cpp | 6 +- src/fheroes2/castle/castle_mageguild.cpp | 6 +- src/fheroes2/castle/castle_town.cpp | 35 ++-- src/fheroes2/castle/castle_well.cpp | 4 +- src/fheroes2/dialog/dialog_adventure.cpp | 10 +- src/fheroes2/dialog/dialog_arena.cpp | 2 +- src/fheroes2/dialog/dialog_armyinfo.cpp | 96 +++++----- src/fheroes2/dialog/dialog_audio.cpp | 3 +- src/fheroes2/dialog/dialog_buyboat.cpp | 14 +- src/fheroes2/dialog/dialog_chest.cpp | 16 +- src/fheroes2/dialog/dialog_file.cpp | 16 +- src/fheroes2/dialog/dialog_game_settings.cpp | 16 +- src/fheroes2/dialog/dialog_gameinfo.cpp | 6 +- .../dialog/dialog_graphics_settings.cpp | 16 +- src/fheroes2/dialog/dialog_hotkeys.cpp | 2 +- .../dialog/dialog_interface_settings.cpp | 20 +-- .../dialog/dialog_language_selection.cpp | 7 +- src/fheroes2/dialog/dialog_levelup.cpp | 26 +-- src/fheroes2/dialog/dialog_marketplace.cpp | 24 +-- src/fheroes2/dialog/dialog_recruit.cpp | 14 +- src/fheroes2/dialog/dialog_resolution.cpp | 7 +- src/fheroes2/dialog/dialog_selectcount.cpp | 15 +- src/fheroes2/dialog/dialog_selectitems.cpp | 19 +- src/fheroes2/dialog/dialog_system_options.cpp | 20 +-- src/fheroes2/dialog/dialog_thievesguild.cpp | 2 +- .../editor/editor_castle_details_window.cpp | 18 +- src/fheroes2/editor/editor_interface.cpp | 16 +- src/fheroes2/editor/editor_mainmenu.cpp | 77 ++++---- .../editor/editor_map_specs_window.cpp | 41 ++--- src/fheroes2/editor/editor_options.cpp | 16 +- src/fheroes2/game/game_campaign.cpp | 12 +- src/fheroes2/game/game_highscores.cpp | 4 +- src/fheroes2/game/game_interface.cpp | 2 +- src/fheroes2/game/game_loadgame.cpp | 27 +-- src/fheroes2/game/game_mainmenu.cpp | 55 +++--- src/fheroes2/game/game_newgame.cpp | 164 +++++++++++------- src/fheroes2/game/game_scenarioinfo.cpp | 12 +- src/fheroes2/gui/interface_list.h | 32 ++-- src/fheroes2/gui/ui_button.cpp | 14 +- src/fheroes2/gui/ui_button.h | 41 ++++- src/fheroes2/gui/ui_keyboard.cpp | 5 +- src/fheroes2/heroes/heroes_dialog.cpp | 78 ++++----- src/fheroes2/heroes/heroes_meeting.cpp | 2 +- src/fheroes2/kingdom/kingdom_overview.cpp | 2 +- src/fheroes2/kingdom/puzzle.cpp | 7 +- src/fheroes2/kingdom/view_world.cpp | 6 +- 53 files changed, 703 insertions(+), 578 deletions(-) diff --git a/src/engine/image.cpp b/src/engine/image.cpp index 8ff20ff42d8..e71664256b0 100644 --- a/src/engine/image.cpp +++ b/src/engine/image.cpp @@ -1144,6 +1144,11 @@ namespace fheroes2 Blit( in, 0, 0, out, 0, 0, in.width(), in.height(), flip ); } + void Blit( const Image & in, Image & out, const Rect & outRoi, const bool flip /* = false */ ) + { + Blit( in, 0, 0, out, outRoi.x, outRoi.y, outRoi.width, outRoi.height, flip ); + } + void Blit( const Image & in, Image & out, int32_t outX, int32_t outY, const bool flip /* = false */ ) { Blit( in, 0, 0, out, outX, outY, in.width(), in.height(), flip ); diff --git a/src/engine/image.h b/src/engine/image.h index 618363a6ed5..bfa9603ac07 100644 --- a/src/engine/image.h +++ b/src/engine/image.h @@ -233,6 +233,7 @@ namespace fheroes2 // draw one image onto another void Blit( const Image & in, Image & out, const bool flip = false ); + void Blit( const Image & in, Image & out, const Rect & outRoi, const bool flip = false ); void Blit( const Image & in, Image & out, int32_t outX, int32_t outY, const bool flip = false ); void Blit( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const bool flip = false ); diff --git a/src/fheroes2/battle/battle_dialogs.cpp b/src/fheroes2/battle/battle_dialogs.cpp index 9153180ce09..27c7ff0f27e 100644 --- a/src/fheroes2/battle/battle_dialogs.cpp +++ b/src/fheroes2/battle/battle_dialogs.cpp @@ -287,6 +287,7 @@ namespace const fheroes2::Point buttonOffset( 112 + pos_rt.x, 362 + pos_rt.y ); fheroes2::Button buttonOkay( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); + buttonOkay.draw(); RedrawBattleSettings( optionAreas ); @@ -294,7 +295,7 @@ namespace display.render(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOkay.area() ) ? buttonOkay.drawOnPress() : buttonOkay.drawOnRelease(); + buttonOkay.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOkay.area() ) ); bool redrawScreen = false; @@ -336,15 +337,16 @@ namespace else if ( le.MouseClickLeft( optionAreas[6] ) ) { return DialogAction::AudioSettings; } + if ( le.MouseClickLeft( optionAreas[7] ) ) { return DialogAction::HotKeys; } + if ( le.MouseClickLeft( optionAreas[8] ) ) { conf.setBattleDamageInfo( !conf.isBattleShowDamageInfoEnabled() ); redrawScreen = true; } - - if ( le.isMouseRightButtonPressedInArea( optionAreas[0] ) ) { + else if ( le.isMouseRightButtonPressedInArea( optionAreas[0] ) ) { fheroes2::showStandardTextMessage( _( "Speed" ), _( "Set the speed of combat actions and animations." ), 0 ); } else if ( le.isMouseRightButtonPressedInArea( optionAreas[1] ) ) { @@ -661,14 +663,16 @@ bool Battle::Arena::DialogBattleSummary( const Result & res, const std::vectorarea() ) ? buttonRestart->drawOnPress() : buttonRestart->drawOnRelease(); + buttonRestart->drawOnState( le.isMouseLeftButtonPressedInArea( buttonRestart->area() ) ); } if ( Game::HotKeyCloseWindow() || le.MouseClickLeft( buttonOk.area() ) ) { break; } + if ( le.isMouseRightButtonPressedInArea( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Click to leave the battle results." ), Dialog::ZERO ); } @@ -697,6 +701,11 @@ bool Battle::Arena::DialogBattleSummary( const Result & res, const std::vectorGetCommander() : ( res.army2 & RESULT_WINS ? _army2->GetCommander() : nullptr ) ); const HeroBase * loser = ( res.army1 & RESULT_LOSS ? _army1->GetCommander() : ( res.army2 & RESULT_LOSS ? _army2->GetCommander() : nullptr ) ); @@ -790,7 +799,7 @@ bool Battle::Arena::DialogBattleSummary( const Result & res, const std::vector( &hero ) : nullptr; + if ( !buttons ) { + // This is a case when this dialog was called by the right mouse button press. - std::string statusMessage = _( "Hero's Options" ); + btnClose.disable(); + btnClose.draw(); - LocalEvent & le = LocalEvent::Get(); - while ( le.HandleEvents() && !result ) { - btnCast.isEnabled() && le.isMouseLeftButtonPressedInArea( btnCast.area() ) ? btnCast.drawOnPress() : btnCast.drawOnRelease(); - btnRetreat.isEnabled() && le.isMouseLeftButtonPressedInArea( btnRetreat.area() ) ? btnRetreat.drawOnPress() : btnRetreat.drawOnRelease(); - btnSurrender.isEnabled() && le.isMouseLeftButtonPressedInArea( btnSurrender.area() ) ? btnSurrender.drawOnPress() : btnSurrender.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( btnClose.area() ) ? btnClose.drawOnPress() : btnClose.drawOnRelease(); + display.render( pos_rt ); - if ( !buttons ) { + while ( le.HandleEvents() ) { if ( !le.isMouseRightButtonPressed() ) { break; } + } + + return result; + } + + btnClose.draw(); + + display.render( pos_rt ); + + // The Hero Screen is available for a Hero only (not Captain) and only when the corresponding player has a turn. + Heroes * heroForHeroScreen = ( currentColor == hero.GetColor() ) ? dynamic_cast( &hero ) : nullptr; - continue; + std::string statusMessage = _( "Hero's Options" ); + + while ( le.HandleEvents() && !result ) { + if ( btnCast.isEnabled() ) { + btnCast.drawOnState( le.isMouseLeftButtonPressedInArea( btnCast.area() ) ); } + if ( btnRetreat.isEnabled() ) { + btnRetreat.drawOnState( le.isMouseLeftButtonPressedInArea( btnRetreat.area() ) ); + } + if ( btnSurrender.isEnabled() ) { + btnSurrender.drawOnState( le.isMouseLeftButtonPressedInArea( btnSurrender.area() ) ); + } + + btnClose.drawOnState( le.isMouseLeftButtonPressedInArea( btnClose.area() ) ); // The Cast Spell is available for a hero and a captain. if ( le.isMouseCursorPosInArea( btnCast.area() ) && currentColor == hero.GetColor() ) { @@ -1131,8 +1157,12 @@ int Battle::Arena::DialogBattleHero( HeroBase & hero, const bool buttons, Status bool Battle::DialogBattleSurrender( const HeroBase & hero, uint32_t cost, Kingdom & kingdom ) { - if ( kingdom.GetColor() == hero.GetColor() ) // this is weird. You're surrending to yourself! + if ( kingdom.GetColor() == hero.GetColor() ) { + // This is weird. You're surrendering to yourself! + assert( 0 ); + return false; + } fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); @@ -1181,16 +1211,15 @@ bool Battle::DialogBattleSurrender( const HeroBase & hero, uint32_t cost, Kingdo btnAccept.draw(); btnDecline.draw(); - const auto drawGoldMsg = [cost, &kingdom, &btnAccept]() { + const auto drawGoldMsg = [cost, &kingdom, &display]( const fheroes2::Rect & btnAcceptArea ) { std::string str = _( "Not enough gold (%{gold})" ); StringReplace( str, "%{gold}", cost - kingdom.GetFunds().gold ); const fheroes2::Text text( str, fheroes2::FontType::smallWhite() ); - const fheroes2::Rect rect = btnAccept.area(); // Since button area includes 3D effect on the left side we need to shift the text by X axis to center it in relation to the button. - text.draw( rect.x + ( rect.width - text.width() ) / 2 + 2, rect.y - 13, fheroes2::Display::instance() ); + text.draw( btnAcceptArea.x + ( btnAcceptArea.width - text.width() ) / 2 + 2, btnAcceptArea.y - 13, display ); }; const int icn = isEvilInterface ? ICN::SURRENDE : ICN::SURRENDR; @@ -1213,7 +1242,7 @@ bool Battle::DialogBattleSurrender( const HeroBase & hero, uint32_t cost, Kingdo fheroes2::ImageRestorer back( display, pos_rt.x, pos_rt.y, pos_rt.width, pos_rt.height ); if ( !kingdom.AllowPayment( Funds( Resource::GOLD, cost ) ) ) { - drawGoldMsg(); + drawGoldMsg( btnAccept.area() ); } display.render(); @@ -1221,12 +1250,15 @@ bool Battle::DialogBattleSurrender( const HeroBase & hero, uint32_t cost, Kingdo bool result = false; while ( le.HandleEvents() && !result ) { - if ( btnAccept.isEnabled() ) - le.isMouseLeftButtonPressedInArea( btnAccept.area() ) ? btnAccept.drawOnPress() : btnAccept.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( btnDecline.area() ) ? btnDecline.drawOnPress() : btnDecline.drawOnRelease(); + if ( btnAccept.isEnabled() ) { + btnAccept.drawOnState( le.isMouseLeftButtonPressedInArea( btnAccept.area() ) ); + } + + btnDecline.drawOnState( le.isMouseLeftButtonPressedInArea( btnDecline.area() ) ); - if ( btnMarket.isEnabled() ) - le.isMouseLeftButtonPressedInArea( btnMarket.area() ) ? btnMarket.drawOnPress() : btnMarket.drawOnRelease(); + if ( btnMarket.isEnabled() ) { + btnMarket.drawOnState( le.isMouseLeftButtonPressedInArea( btnMarket.area() ) ); + } if ( btnAccept.isEnabled() && ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( btnAccept.area() ) ) ) { result = true; @@ -1243,7 +1275,7 @@ bool Battle::DialogBattleSurrender( const HeroBase & hero, uint32_t cost, Kingdo else { btnAccept.disable(); - drawGoldMsg(); + drawGoldMsg( btnAccept.area() ); } btnAccept.draw(); diff --git a/src/fheroes2/battle/battle_interface.cpp b/src/fheroes2/battle/battle_interface.cpp index c300fb78ab1..07cf28a4758 100644 --- a/src/fheroes2/battle/battle_interface.cpp +++ b/src/fheroes2/battle/battle_interface.cpp @@ -682,9 +682,9 @@ namespace Battle SetScrollButtonUp( ICN::DROPLISL, 6, 7, fheroes2::Point( ax, area.y - 10 ) ); SetScrollButtonDn( ICN::DROPLISL, 8, 9, fheroes2::Point( ax, area.y + area.height - 11 ) ); - const fheroes2::Rect & buttonPgUpArea = buttonPgUp.area(); + const fheroes2::Rect & buttonPgUpArea = _buttonPgUp.area(); - _scrollbarSliderAreaLength = buttonPgDn.area().y - ( buttonPgUpArea.y + buttonPgUpArea.height ) - 7; + _scrollbarSliderAreaLength = _buttonPgDn.area().y - ( buttonPgUpArea.y + buttonPgUpArea.height ) - 7; setScrollBarArea( { ax + 5, buttonPgUpArea.y + buttonPgUpArea.height + 3, 12, _scrollbarSliderAreaLength } ); @@ -774,9 +774,9 @@ namespace Battle { fheroes2::Display & display = fheroes2::Display::instance(); - const fheroes2::Rect & buttonPgUpArea = buttonPgUp.area(); + const fheroes2::Rect & buttonPgUpArea = _buttonPgUp.area(); const int32_t buttonPgUpBottom = buttonPgUpArea.y + buttonPgUpArea.height; - const int32_t buttonPgDnAreaY = buttonPgDn.area().y; + const int32_t buttonPgDnAreaY = _buttonPgDn.area().y; const int32_t ax = buttonPgUpArea.x; const int32_t ah = buttonPgDnAreaY - buttonPgUpBottom; @@ -1277,8 +1277,8 @@ Battle::Interface::Interface( Arena & battleArena, const int32_t tileIndex ) // Shadow that fits the hexagon grid. _hexagonGridShadow = DrawHexagonShadow( 4, 1 ); - btn_auto.setICNInfo( ICN::TEXTBAR, 4, 5 ); - btn_settings.setICNInfo( ICN::TEXTBAR, 6, 7 ); + _buttonAuto.setICNInfo( ICN::TEXTBAR, 4, 5 ); + _buttonSettings.setICNInfo( ICN::TEXTBAR, 6, 7 ); // opponents if ( HeroBase * opponent = arena.GetCommander1(); opponent != nullptr ) { @@ -1294,13 +1294,13 @@ Battle::Interface::Interface( Arena & battleArena, const int32_t tileIndex ) const fheroes2::Rect & area = border.GetArea(); - const fheroes2::Rect settingsRect = btn_settings.area(); - const int32_t satusOffsetY = area.y + area.height - settingsRect.height - btn_auto.area().height; - btn_auto.setPosition( area.x, satusOffsetY ); - btn_settings.setPosition( area.x, area.y + area.height - settingsRect.height ); + const fheroes2::Rect & settingsRect = _buttonSettings.area(); + const int32_t satusOffsetY = area.y + area.height - settingsRect.height - _buttonAuto.area().height; + _buttonAuto.setPosition( area.x, satusOffsetY ); + _buttonSettings.setPosition( area.x, area.y + area.height - settingsRect.height ); - btn_skip.setICNInfo( ICN::TEXTBAR, 0, 1 ); - btn_skip.setPosition( area.x + area.width - btn_skip.area().width, area.y + area.height - btn_skip.area().height ); + _buttonSkip.setICNInfo( ICN::TEXTBAR, 0, 1 ); + _buttonSkip.setPosition( area.x + area.width - _buttonSkip.area().width, area.y + area.height - _buttonSkip.area().height ); status.setPosition( area.x + settingsRect.width, satusOffsetY ); @@ -1472,9 +1472,9 @@ void Battle::Interface::RedrawInterface() { status.redraw( fheroes2::Display::instance() ); - btn_auto.draw(); - btn_settings.draw(); - btn_skip.draw(); + _buttonAuto.draw(); + _buttonSettings.draw(); + _buttonSkip.draw(); popup.redraw(); @@ -2864,7 +2864,7 @@ void Battle::Interface::HumanBattleTurn( const Unit & unit, Actions & actions, s cursor.SetThemes( Cursor::POINTER ); _turnOrder.queueEventProcessing( msg, _interfacePosition.getPosition() ); } - else if ( le.isMouseCursorPosInArea( btn_auto.area() ) ) { + else if ( le.isMouseCursorPosInArea( _buttonAuto.area() ) ) { cursor.SetThemes( Cursor::WAR_POINTER ); msg = _( "Enable auto combat" ); ButtonAutoAction( unit, actions ); @@ -2873,7 +2873,7 @@ void Battle::Interface::HumanBattleTurn( const Unit & unit, Actions & actions, s fheroes2::showStandardTextMessage( _( "Auto Combat" ), _( "Allows the computer to fight out the battle for you." ), Dialog::ZERO ); } } - else if ( le.isMouseCursorPosInArea( btn_settings.area() ) ) { + else if ( le.isMouseCursorPosInArea( _buttonSettings.area() ) ) { cursor.SetThemes( Cursor::WAR_POINTER ); msg = _( "Customize system options" ); ButtonSettingsAction(); @@ -2882,7 +2882,7 @@ void Battle::Interface::HumanBattleTurn( const Unit & unit, Actions & actions, s fheroes2::showStandardTextMessage( _( "System Options" ), _( "Allows you to customize the combat screen." ), Dialog::ZERO ); } } - else if ( le.isMouseCursorPosInArea( btn_skip.area() ) ) { + else if ( le.isMouseCursorPosInArea( _buttonSkip.area() ) ) { cursor.SetThemes( Cursor::WAR_POINTER ); msg = _( "Skip this unit" ); ButtonSkipAction( actions ); @@ -3167,9 +3167,9 @@ void Battle::Interface::_openBattleSettingsDialog() void Battle::Interface::EventShowOptions() { - btn_settings.drawOnPress(); + _buttonSettings.drawOnPress(); _openBattleSettingsDialog(); - btn_settings.drawOnRelease(); + _buttonSettings.drawOnRelease(); humanturn_redraw = true; } @@ -3206,9 +3206,9 @@ void Battle::Interface::ButtonAutoAction( const Unit & unit, Actions & actions ) { LocalEvent & le = LocalEvent::Get(); - le.isMouseLeftButtonPressedInArea( btn_auto.area() ) ? btn_auto.drawOnPress() : btn_auto.drawOnRelease(); + _buttonAuto.drawOnState( le.isMouseLeftButtonPressedInArea( _buttonAuto.area() ) ); - if ( le.MouseClickLeft( btn_auto.area() ) ) { + if ( le.MouseClickLeft( _buttonAuto.area() ) ) { EventStartAutoBattle( unit, actions ); } } @@ -3217,9 +3217,9 @@ void Battle::Interface::ButtonSettingsAction() { LocalEvent & le = LocalEvent::Get(); - le.isMouseLeftButtonPressedInArea( btn_settings.area() ) ? btn_settings.drawOnPress() : btn_settings.drawOnRelease(); + _buttonSettings.drawOnState( le.isMouseLeftButtonPressedInArea( _buttonSettings.area() ) ); - if ( le.MouseClickLeft( btn_settings.area() ) ) { + if ( le.MouseClickLeft( _buttonSettings.area() ) ) { _openBattleSettingsDialog(); humanturn_redraw = true; @@ -3230,9 +3230,9 @@ void Battle::Interface::ButtonSkipAction( Actions & actions ) { LocalEvent & le = LocalEvent::Get(); - le.isMouseLeftButtonPressedInArea( btn_skip.area() ) ? btn_skip.drawOnPress() : btn_skip.drawOnRelease(); + _buttonSkip.drawOnState( le.isMouseLeftButtonPressedInArea( _buttonSkip.area() ) ); - if ( le.MouseClickLeft( btn_skip.area() ) && _currentUnit ) { + if ( le.MouseClickLeft( _buttonSkip.area() ) && _currentUnit ) { actions.emplace_back( Command::SKIP, _currentUnit->GetUID() ); humanturn_exit = true; } diff --git a/src/fheroes2/battle/battle_interface.h b/src/fheroes2/battle/battle_interface.h index 72435993e4e..0445c4a8ec0 100644 --- a/src/fheroes2/battle/battle_interface.h +++ b/src/fheroes2/battle/battle_interface.h @@ -448,9 +448,9 @@ namespace Battle int _battleGroundIcn{ ICN::UNKNOWN }; int _borderObjectsIcn{ ICN::UNKNOWN }; - fheroes2::Button btn_auto; - fheroes2::Button btn_settings; - fheroes2::Button btn_skip; + fheroes2::Button _buttonAuto; + fheroes2::Button _buttonSettings; + fheroes2::Button _buttonSkip; Status status; std::unique_ptr _opponent1; diff --git a/src/fheroes2/battle/battle_only.cpp b/src/fheroes2/battle/battle_only.cpp index 33466a052fc..9c23d2a352f 100644 --- a/src/fheroes2/battle/battle_only.cpp +++ b/src/fheroes2/battle/battle_only.cpp @@ -215,9 +215,15 @@ bool Battle::Only::setup( const bool allowBackup, bool & reset ) bool needRedrawOpponentsStats = false; bool needRedrawControlInfo = false; - buttonStart.isEnabled() && le.isMouseLeftButtonPressedInArea( buttonStart.area() ) ? buttonStart.drawOnPress() : buttonStart.drawOnRelease(); - buttonExit.isEnabled() && le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); - buttonReset.isEnabled() && le.isMouseLeftButtonPressedInArea( buttonReset.area() ) ? buttonReset.drawOnPress() : buttonReset.drawOnRelease(); + if ( buttonStart.isEnabled() ) { + buttonStart.drawOnState( le.isMouseLeftButtonPressedInArea( buttonStart.area() ) ); + } + if ( buttonExit.isEnabled() ) { + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); + } + if ( buttonReset.isEnabled() ) { + buttonReset.drawOnState( le.isMouseLeftButtonPressedInArea( buttonReset.area() ) ); + } if ( ( buttonStart.isEnabled() && le.MouseClickLeft( buttonStart.area() ) ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) { result = true; diff --git a/src/fheroes2/castle/buildinginfo.cpp b/src/fheroes2/castle/buildinginfo.cpp index ce7252f828d..c17e3f22c87 100644 --- a/src/fheroes2/castle/buildinginfo.cpp +++ b/src/fheroes2/castle/buildinginfo.cpp @@ -492,20 +492,7 @@ bool BuildingInfo::DialogBuyBuilding( bool buttons ) const const Dialog::FrameBox dialogFrame( totalDialogHeight, buttons ); const fheroes2::Rect & dialogRoi = dialogFrame.GetArea(); - const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); - const int buttonOkayIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_OKAY_BUTTON : ICN::UNIFORM_GOOD_OKAY_BUTTON; - - fheroes2::Point pos{ dialogRoi.x, dialogRoi.y + dialogRoi.height - fheroes2::AGG::GetICN( buttonOkayIcnID, 0 ).height() }; - fheroes2::Button buttonOkay( pos.x, pos.y, buttonOkayIcnID, 0, 1 ); - - const int buttonCancelIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_CANCEL_BUTTON : ICN::UNIFORM_GOOD_CANCEL_BUTTON; - - pos.x = dialogRoi.x + dialogRoi.width - fheroes2::AGG::GetICN( buttonCancelIcnID, 0 ).width(); - pos.y = dialogRoi.y + dialogRoi.height - fheroes2::AGG::GetICN( buttonCancelIcnID, 0 ).height(); - fheroes2::Button buttonCancel( pos.x, pos.y, buttonCancelIcnID, 0, 1 ); - - pos.x = dialogRoi.x + ( dialogRoi.width - buildingFrame.width() ) / 2; - pos.y = dialogRoi.y + elementOffset; + fheroes2::Point pos{ dialogRoi.x + ( dialogRoi.width - buildingFrame.width() ) / 2, pos.y = dialogRoi.y + elementOffset }; fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( buildingFrame, display, pos.x, pos.y ); @@ -540,32 +527,51 @@ bool BuildingInfo::DialogBuyBuilding( bool buttons ) const rbs.SetPos( pos.x, pos.y ); rbs.Redraw(); - if ( buttons ) { - if ( BuildingStatus::ALLOW_BUILD != castle.CheckBuyBuilding( _buildingType ) ) { - buttonOkay.disable(); + LocalEvent & le = LocalEvent::Get(); + + if ( !buttons ) { + // This is a case when this dialog was called by the right mouse button press. + + display.render(); + + while ( le.HandleEvents() ) { + if ( !le.isMouseRightButtonPressed() ) { + break; + } } - buttonOkay.draw(); - buttonCancel.draw(); + return false; } - else { - buttonOkay.disable(); - buttonOkay.hide(); - buttonCancel.disable(); - buttonCancel.hide(); + const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); + const int buttonOkayIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_OKAY_BUTTON : ICN::UNIFORM_GOOD_OKAY_BUTTON; + + pos.x = dialogRoi.x; + pos.y = dialogRoi.y + dialogRoi.height - fheroes2::AGG::GetICN( buttonOkayIcnID, 0 ).height(); + + fheroes2::Button buttonOkay( pos.x, pos.y, buttonOkayIcnID, 0, 1 ); + + const int buttonCancelIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_CANCEL_BUTTON : ICN::UNIFORM_GOOD_CANCEL_BUTTON; + + pos.x = dialogRoi.x + dialogRoi.width - fheroes2::AGG::GetICN( buttonCancelIcnID, 0 ).width(); + pos.y = dialogRoi.y + dialogRoi.height - fheroes2::AGG::GetICN( buttonCancelIcnID, 0 ).height(); + fheroes2::Button buttonCancel( pos.x, pos.y, buttonCancelIcnID, 0, 1 ); + + if ( BuildingStatus::ALLOW_BUILD != castle.CheckBuyBuilding( _buildingType ) ) { + buttonOkay.disable(); } + buttonOkay.draw(); + buttonCancel.draw(); + display.render(); - LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - if ( !buttons && !le.isMouseRightButtonPressed() ) { - break; + if ( buttonOkay.isEnabled() ) { + buttonOkay.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOkay.area() ) ); } - le.isMouseLeftButtonPressedInArea( buttonOkay.area() ) ? buttonOkay.drawOnPress() : buttonOkay.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( buttonOkay.isEnabled() && ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOkay.area() ) ) ) { return true; @@ -575,10 +581,10 @@ bool BuildingInfo::DialogBuyBuilding( bool buttons ) const break; } - if ( buttonOkay.isVisible() && le.isMouseRightButtonPressedInArea( buttonOkay.area() ) ) { + if ( le.isMouseRightButtonPressedInArea( buttonOkay.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), GetConditionDescription(), Dialog::ZERO ); } - else if ( buttonCancel.isVisible() && le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } } diff --git a/src/fheroes2/castle/castle_dialog.cpp b/src/fheroes2/castle/castle_dialog.cpp index 1c3e6094f1f..d3d95a0666e 100644 --- a/src/fheroes2/castle/castle_dialog.cpp +++ b/src/fheroes2/castle/castle_dialog.cpp @@ -404,13 +404,13 @@ Castle::CastleDialogReturnValue Castle::OpenDialog( const bool openConstructionW // During hero purchase or building construction skip any interaction with the dialog. if ( alphaHero >= 255 && fadeBuilding.isFadeDone() ) { if ( buttonPrevCastle.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonPrevCastle.area() ) ? buttonPrevCastle.drawOnPress() : buttonPrevCastle.drawOnRelease(); + buttonPrevCastle.drawOnState( le.isMouseLeftButtonPressedInArea( buttonPrevCastle.area() ) ); } if ( buttonNextCastle.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonNextCastle.area() ) ? buttonNextCastle.drawOnPress() : buttonNextCastle.drawOnRelease(); + buttonNextCastle.drawOnState( le.isMouseLeftButtonPressedInArea( buttonNextCastle.area() ) ); } - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); // Check buttons for closing this castle's window. if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { diff --git a/src/fheroes2/castle/castle_mageguild.cpp b/src/fheroes2/castle/castle_mageguild.cpp index 1c31ce44b20..5692377609b 100644 --- a/src/fheroes2/castle/castle_mageguild.cpp +++ b/src/fheroes2/castle/castle_mageguild.cpp @@ -241,6 +241,7 @@ void Castle::_openMageGuild( const Heroes * hero ) const spells5.Redraw( display ); fheroes2::Button buttonExit( cur_pt.x + fheroes2::Display::DEFAULT_WIDTH - exitWidth, cur_pt.y + bottomBarOffsetY, ICN::BUTTON_GUILDWELL_EXIT, 0, 1 ); + buttonExit.draw(); display.render(); @@ -249,10 +250,11 @@ void Castle::_openMageGuild( const Heroes * hero ) const // message loop while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); - if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) + if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; + } spells1.QueueEventProcessing() || spells2.QueueEventProcessing() || spells3.QueueEventProcessing() || spells4.QueueEventProcessing() || spells5.QueueEventProcessing(); diff --git a/src/fheroes2/castle/castle_town.cpp b/src/fheroes2/castle/castle_town.cpp index 320783a67fa..80cbda65d83 100644 --- a/src/fheroes2/castle/castle_town.cpp +++ b/src/fheroes2/castle/castle_town.cpp @@ -149,8 +149,8 @@ int Castle::DialogBuyHero( const Heroes * hero ) const LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOkay.area() ) ? buttonOkay.drawOnPress() : buttonOkay.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + buttonOkay.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOkay.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( buttonOkay.isEnabled() && ( le.MouseClickLeft( buttonOkay.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) ) { return Dialog::OK; @@ -470,12 +470,11 @@ Castle::ConstructionDialogResult Castle::_openConstructionDialog( uint32_t & dwe fheroes2::Button buttonPrevCastle( cur_pt.x, dst_pt.y, ICN::SMALLBAR, 1, 2 ); fheroes2::TimedEventValidator timedButtonPrevCastle( [&buttonPrevCastle]() { return buttonPrevCastle.isPressed(); } ); buttonPrevCastle.subscribe( &timedButtonPrevCastle ); - const fheroes2::Rect buttonPrevCastleArea( buttonPrevCastle.area() ); // bottom small bar const fheroes2::Sprite & bar = fheroes2::AGG::GetICN( ICN::SMALLBAR, 0 ); const int32_t statusBarWidth = bar.width(); - dst_pt.x = cur_pt.x + buttonPrevCastleArea.width; + dst_pt.x = cur_pt.x + buttonPrevCastle.area().width; fheroes2::Copy( bar, 0, 0, display, dst_pt.x, dst_pt.y, statusBarWidth, bar.height() ); StatusBar statusBar; @@ -486,13 +485,11 @@ Castle::ConstructionDialogResult Castle::_openConstructionDialog( uint32_t & dwe fheroes2::Button buttonNextCastle( dst_pt.x + statusBarWidth, dst_pt.y, ICN::SMALLBAR, 3, 4 ); fheroes2::TimedEventValidator timedButtonNextCastle( [&buttonNextCastle]() { return buttonNextCastle.isPressed(); } ); buttonNextCastle.subscribe( &timedButtonNextCastle ); - const fheroes2::Rect buttonNextCastleArea( buttonNextCastle.area() ); // button exit dst_pt.x = cur_pt.x + 553; dst_pt.y = cur_pt.y + 428; fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, ICN::BUTTON_EXIT_TOWN, 0, 1 ); - const fheroes2::Rect buttonExitArea( buttonExit.area() ); if ( GetKingdom().GetCastles().size() < 2 ) { buttonPrevCastle.disable(); @@ -505,7 +502,7 @@ Castle::ConstructionDialogResult Castle::_openConstructionDialog( uint32_t & dwe // redraw resource panel const fheroes2::Rect & rectResource = fheroes2::drawResourcePanel( GetKingdom().GetFunds(), display, cur_pt ); - const fheroes2::Rect resActiveArea( rectResource.x, rectResource.y, rectResource.width, buttonExitArea.y - rectResource.y - 3 ); + const fheroes2::Rect resActiveArea( rectResource.x, rectResource.y, rectResource.width, buttonExit.area().y - rectResource.y - 3 ); auto recruitHeroDialog = [this, &buttonExit]( Heroes * hero ) { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); @@ -521,23 +518,23 @@ Castle::ConstructionDialogResult Castle::_openConstructionDialog( uint32_t & dwe LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonExitArea ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); - if ( le.MouseClickLeft( buttonExitArea ) || Game::HotKeyCloseWindow() ) { + if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( buttonPrevCastle.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonPrevCastleArea ) ? buttonPrevCastle.drawOnPress() : buttonPrevCastle.drawOnRelease(); + buttonPrevCastle.drawOnState( le.isMouseLeftButtonPressedInArea( buttonPrevCastle.area() ) ); - if ( le.MouseClickLeft( buttonPrevCastleArea ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) || timedButtonPrevCastle.isDelayPassed() ) { + if ( le.MouseClickLeft( buttonPrevCastle.area() ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) || timedButtonPrevCastle.isDelayPassed() ) { return ConstructionDialogResult::PrevConstructionWindow; } } if ( buttonNextCastle.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonNextCastleArea ) ? buttonNextCastle.drawOnPress() : buttonNextCastle.drawOnRelease(); + buttonNextCastle.drawOnState( le.isMouseLeftButtonPressedInArea( buttonNextCastle.area() ) ); - if ( le.MouseClickLeft( buttonNextCastleArea ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) || timedButtonNextCastle.isDelayPassed() ) { + if ( le.MouseClickLeft( buttonNextCastle.area() ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) || timedButtonNextCastle.isDelayPassed() ) { return ConstructionDialogResult::NextConstructionWindow; } } @@ -549,7 +546,7 @@ Castle::ConstructionDialogResult Castle::_openConstructionDialog( uint32_t & dwe else if ( le.isMouseRightButtonPressedInArea( resActiveArea ) ) { fheroes2::showKingdomIncome( world.GetKingdom( GetColor() ), 0 ); } - else if ( le.isMouseRightButtonPressedInArea( buttonExitArea ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonExit.area() ) ) { fheroes2::showStandardTextMessage( _( "Exit" ), _( "Exit this menu." ), Dialog::ZERO ); } else if ( le.isMouseCursorPosInArea( dwelling1.GetArea() ) && dwelling1.QueueEventProcessing( buttonExit ) ) { @@ -671,10 +668,10 @@ Castle::ConstructionDialogResult Castle::_openConstructionDialog( uint32_t & dwe // Use half fade if game resolution is not 640x480. fheroes2::fadeInDisplay( restorer.rect(), !display.isDefaultSize() ); } - else if ( le.isMouseRightButtonPressedInArea( buttonNextCastleArea ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonNextCastle.area() ) ) { fheroes2::showStandardTextMessage( _( "Show next town" ), _( "Click to show the next town." ), Dialog::ZERO ); } - else if ( le.isMouseRightButtonPressedInArea( buttonPrevCastleArea ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonPrevCastle.area() ) ) { fheroes2::showStandardTextMessage( _( "Show previous town" ), _( "Click to show the previous town." ), Dialog::ZERO ); } @@ -741,14 +738,14 @@ Castle::ConstructionDialogResult Castle::_openConstructionDialog( uint32_t & dwe statusBar.ShowMessage( _( "Set garrison combat formation to 'Spread'" ) ); else if ( isCaptainBuilt && le.isMouseCursorPosInArea( rectGroupedArmyFormat ) ) statusBar.ShowMessage( _( "Set garrison combat formation to 'Grouped'" ) ); - else if ( le.isMouseCursorPosInArea( buttonExitArea ) ) + else if ( le.isMouseCursorPosInArea( buttonExit.area() ) ) statusBar.ShowMessage( _( "Exit Castle Options" ) ); else if ( le.isMouseCursorPosInArea( resActiveArea ) ) statusBar.ShowMessage( _( "Show Income" ) ); - else if ( buttonPrevCastle.isEnabled() && le.isMouseCursorPosInArea( buttonPrevCastleArea ) ) { + else if ( buttonPrevCastle.isEnabled() && le.isMouseCursorPosInArea( buttonPrevCastle.area() ) ) { statusBar.ShowMessage( _( "Show previous town" ) ); } - else if ( buttonNextCastle.isEnabled() && le.isMouseCursorPosInArea( buttonNextCastleArea ) ) { + else if ( buttonNextCastle.isEnabled() && le.isMouseCursorPosInArea( buttonNextCastle.area() ) ) { statusBar.ShowMessage( _( "Show next town" ) ); } else { diff --git a/src/fheroes2/castle/castle_well.cpp b/src/fheroes2/castle/castle_well.cpp index bed240a99fd..e41ce338830 100644 --- a/src/fheroes2/castle/castle_well.cpp +++ b/src/fheroes2/castle/castle_well.cpp @@ -245,9 +245,9 @@ void Castle::_openWell() Game::passAnimationDelay( Game::CASTLE_UNIT_DELAY ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::CASTLE_UNIT_DELAY } ) ) ) { - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); - le.isMouseLeftButtonPressedInArea( buttonMax.area() ) ? buttonMax.drawOnPress() : buttonMax.drawOnRelease(); + buttonMax.drawOnState( le.isMouseLeftButtonPressedInArea( buttonMax.area() ) ); const BuildingType pressedHotkeyBuildingID = getPressedBuildingHotkey(); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { diff --git a/src/fheroes2/dialog/dialog_adventure.cpp b/src/fheroes2/dialog/dialog_adventure.cpp index 4578260374b..2095d16b201 100644 --- a/src/fheroes2/dialog/dialog_adventure.cpp +++ b/src/fheroes2/dialog/dialog_adventure.cpp @@ -79,11 +79,11 @@ namespace // dialog menu loop while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonWorld.area() ) ? buttonWorld.drawOnPress() : buttonWorld.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonPuzzle.area() ) ? buttonPuzzle.drawOnPress() : buttonPuzzle.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonInfo.area() ) ? buttonInfo.drawOnPress() : buttonInfo.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonDig.area() ) ? buttonDig.drawOnPress() : buttonDig.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + buttonWorld.drawOnState( le.isMouseLeftButtonPressedInArea( buttonWorld.area() ) ); + buttonPuzzle.drawOnState( le.isMouseLeftButtonPressedInArea( buttonPuzzle.area() ) ); + buttonInfo.drawOnState( le.isMouseLeftButtonPressedInArea( buttonInfo.area() ) ); + buttonDig.drawOnState( le.isMouseLeftButtonPressedInArea( buttonDig.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( le.MouseClickLeft( buttonWorld.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::WORLD_VIEW_WORLD ) ) { result = Dialog::WORLD; diff --git a/src/fheroes2/dialog/dialog_arena.cpp b/src/fheroes2/dialog/dialog_arena.cpp index b5890147a5e..cc9eda2175a 100644 --- a/src/fheroes2/dialog/dialog_arena.cpp +++ b/src/fheroes2/dialog/dialog_arena.cpp @@ -169,7 +169,7 @@ int Dialog::SelectSkillFromArena() while ( le.HandleEvents() ) { bool redraw = false; - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) && Skill::Primary::UNKNOWN != InfoSkillPrev( res ) ) { res = InfoSkillPrev( res ); diff --git a/src/fheroes2/dialog/dialog_armyinfo.cpp b/src/fheroes2/dialog/dialog_armyinfo.cpp index 741e0eb86b0..25fe10232b4 100644 --- a/src/fheroes2/dialog/dialog_armyinfo.cpp +++ b/src/fheroes2/dialog/dialog_armyinfo.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -519,69 +520,62 @@ int Dialog::ArmyInfo( const Troop & troop, int flags, bool isReflected, const in const fheroes2::Rect dialogRoi( pos_rt.x, pos_rt.y + fheroes2::shadowWidthPx, sprite_dialog.width(), sprite_dialog.height() - 2 * fheroes2::shadowWidthPx ); DrawMonster( monsterAnimation, troop, monsterOffset, isReflected, isAnimated, dialogRoi ); - const int upgradeButtonIcnID = isEvilInterface ? ICN::BUTTON_SMALL_UPGRADE_EVIL : ICN::BUTTON_SMALL_UPGRADE_GOOD; - fheroes2::Point dst_pt( pos_rt.x + 400, pos_rt.y + 40 ); - dst_pt.x = pos_rt.x + 280; - dst_pt.y = pos_rt.y + 192; - fheroes2::Button buttonUpgrade( dst_pt.x, dst_pt.y, upgradeButtonIcnID, 0, 1 ); - - const int dismissButtonIcnID = isEvilInterface ? ICN::BUTTON_SMALL_DISMISS_EVIL : ICN::BUTTON_SMALL_DISMISS_GOOD; - dst_pt.x = pos_rt.x + 280; - dst_pt.y = pos_rt.y + 221; - fheroes2::Button buttonDismiss( dst_pt.x, dst_pt.y, dismissButtonIcnID, 0, 1 ); - const int exitButtonIcnID = isEvilInterface ? ICN::BUTTON_SMALL_EXIT_EVIL : ICN::BUTTON_SMALL_EXIT_GOOD; const int32_t exitWidth = fheroes2::AGG::GetICN( exitButtonIcnID, 0 ).width(); const int32_t interfaceAdjustment = isEvilInterface ? 0 : 18; - dst_pt.x = pos_rt.x + sprite_dialog.width() - 58 - exitWidth + interfaceAdjustment; - dst_pt.y = pos_rt.y + 221; - fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, exitButtonIcnID, 0, 1 ); + fheroes2::Button buttonExit( pos_rt.x + sprite_dialog.width() - 58 - exitWidth + interfaceAdjustment, pos_rt.y + 221, exitButtonIcnID, 0, 1 ); - if ( ( flags & ( BUTTONS | UPGRADE ) ) == ( BUTTONS | UPGRADE ) ) { - buttonUpgrade.enable(); - buttonUpgrade.draw(); - } - else { - buttonUpgrade.disable(); - } + LocalEvent & le = LocalEvent::Get(); + + if ( !( flags & BUTTONS ) ) { + // This is a case when this dialog was called by the right mouse button press. + + display.render( restorer.rect() ); + + while ( le.HandleEvents( true ) ) { + if ( !le.isMouseRightButtonPressed() ) { + break; + } + } - if ( ( flags & ( BUTTONS | DISMISS ) ) == ( BUTTONS | DISMISS ) ) { - buttonDismiss.enable(); - buttonDismiss.draw(); + return Dialog::ZERO; } - else { - buttonDismiss.disable(); + + std::unique_ptr buttonUpgrade; + std::unique_ptr buttonDismiss; + + if ( flags & UPGRADE ) { + const int upgradeButtonIcnID = isEvilInterface ? ICN::BUTTON_SMALL_UPGRADE_EVIL : ICN::BUTTON_SMALL_UPGRADE_GOOD; + buttonUpgrade = std::make_unique( pos_rt.x + 280, pos_rt.y + 192, upgradeButtonIcnID, 0, 1 ); + + buttonUpgrade->draw(); } - if ( flags & BUTTONS ) { - buttonExit.draw(); + if ( flags & DISMISS ) { + const int dismissButtonIcnID = isEvilInterface ? ICN::BUTTON_SMALL_DISMISS_EVIL : ICN::BUTTON_SMALL_DISMISS_GOOD; + buttonDismiss = std::make_unique( pos_rt.x + 280, pos_rt.y + 221, dismissButtonIcnID, 0, 1 ); + + buttonDismiss->draw(); } - LocalEvent & le = LocalEvent::Get(); + buttonExit.draw(); + int result = Dialog::ZERO; display.render( restorer.rect() ); - while ( le.HandleEvents( ( flags & BUTTONS ) ? Game::isDelayNeeded( { Game::CASTLE_UNIT_DELAY } ) : true ) ) { - if ( !( flags & BUTTONS ) ) { - if ( !le.isMouseRightButtonPressed() ) { - break; - } - - continue; - } - - if ( buttonUpgrade.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonUpgrade.area() ) ? buttonUpgrade.drawOnPress() : buttonUpgrade.drawOnRelease(); + while ( le.HandleEvents( Game::isDelayNeeded( { Game::CASTLE_UNIT_DELAY } ) ) ) { + if ( buttonUpgrade ) { + buttonUpgrade->drawOnState( le.isMouseLeftButtonPressedInArea( buttonUpgrade->area() ) ); } - if ( buttonDismiss.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonDismiss.area() ) ? buttonDismiss.drawOnPress() : buttonDismiss.drawOnRelease(); + if ( buttonDismiss ) { + buttonDismiss->drawOnState( le.isMouseLeftButtonPressedInArea( buttonDismiss->area() ) ); } - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); - if ( buttonUpgrade.isEnabled() && ( le.MouseClickLeft( buttonUpgrade.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::ARMY_UPGRADE_TROOP ) ) ) { + if ( buttonUpgrade && ( le.MouseClickLeft( buttonUpgrade->area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::ARMY_UPGRADE_TROOP ) ) ) { // If this assertion blows up then you are executing this code for a monster which has no upgrades. assert( troop.isAllowUpgrade() ); @@ -600,7 +594,7 @@ int Dialog::ArmyInfo( const Troop & troop, int flags, bool isReflected, const in } } - if ( buttonDismiss.isEnabled() && ( le.MouseClickLeft( buttonDismiss.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::ARMY_DISMISS ) ) + if ( buttonDismiss && ( le.MouseClickLeft( buttonDismiss->area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::ARMY_DISMISS ) ) && Dialog::YES == fheroes2::showStandardTextMessage( troop.GetPluralName( troop.GetCount() ), _( "Are you sure you want to dismiss this army?" ), Dialog::YES | Dialog::NO ) ) { @@ -616,10 +610,10 @@ int Dialog::ArmyInfo( const Troop & troop, int flags, bool isReflected, const in if ( le.isMouseRightButtonPressedInArea( buttonExit.area() ) ) { fheroes2::showStandardTextMessage( _( "Exit" ), _( "Exit this menu." ), 0 ); } - else if ( buttonUpgrade.isEnabled() && le.isMouseRightButtonPressedInArea( buttonUpgrade.area() ) ) { + else if ( buttonUpgrade && le.isMouseRightButtonPressedInArea( buttonUpgrade->area() ) ) { fheroes2::showStandardTextMessage( _( "Upgrade" ), _( "Upgrade your troops." ), 0 ); } - else if ( buttonDismiss.isEnabled() && le.isMouseRightButtonPressedInArea( buttonDismiss.area() ) ) { + else if ( buttonDismiss && le.isMouseRightButtonPressedInArea( buttonDismiss->area() ) ) { fheroes2::showStandardTextMessage( _( "Dismiss" ), _( "Dismiss this army." ), 0 ); } @@ -642,12 +636,12 @@ int Dialog::ArmyInfo( const Troop & troop, int flags, bool isReflected, const in DrawMonsterInfo( pos_rt.getPosition(), troop ); DrawMonster( monsterAnimation, troop, monsterOffset, isReflected, true, dialogRoi ); - if ( buttonUpgrade.isEnabled() ) { - buttonUpgrade.draw(); + if ( buttonUpgrade ) { + buttonUpgrade->draw(); } - if ( buttonDismiss.isEnabled() ) { - buttonDismiss.draw(); + if ( buttonDismiss ) { + buttonDismiss->draw(); } if ( buttonExit.isEnabled() ) { diff --git a/src/fheroes2/dialog/dialog_audio.cpp b/src/fheroes2/dialog/dialog_audio.cpp index 69853b265b7..aa3b0147783 100644 --- a/src/fheroes2/dialog/dialog_audio.cpp +++ b/src/fheroes2/dialog/dialog_audio.cpp @@ -148,6 +148,7 @@ namespace Dialog const fheroes2::Point buttonOffset( 112 + dialogArea.x, 252 + dialogArea.y ); fheroes2::Button buttonOkay( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); + buttonOkay.draw(); display.render(); @@ -156,7 +157,7 @@ namespace Dialog LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOkay.area() ) ? buttonOkay.drawOnPress() : buttonOkay.drawOnRelease(); + buttonOkay.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOkay.area() ) ); if ( le.MouseClickLeft( buttonOkay.area() ) || Game::HotKeyCloseWindow() ) { break; diff --git a/src/fheroes2/dialog/dialog_buyboat.cpp b/src/fheroes2/dialog/dialog_buyboat.cpp index 0cf0f604584..54afe4e15da 100644 --- a/src/fheroes2/dialog/dialog_buyboat.cpp +++ b/src/fheroes2/dialog/dialog_buyboat.cpp @@ -96,15 +96,19 @@ int Dialog::BuyBoat( bool enable ) // message loop while ( le.HandleEvents() ) { - if ( buttonOkay.isEnabled() ) - le.isMouseLeftButtonPressedInArea( buttonOkay.area() ) ? buttonOkay.drawOnPress() : buttonOkay.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + if ( buttonOkay.isEnabled() ) { + buttonOkay.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOkay.area() ) ); + } - if ( buttonOkay.isEnabled() && ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOkay.area() ) ) ) + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); + + if ( buttonOkay.isEnabled() && ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOkay.area() ) ) ) { return Dialog::OK; + } - if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) + if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { return Dialog::CANCEL; + } } return Dialog::ZERO; diff --git a/src/fheroes2/dialog/dialog_chest.cpp b/src/fheroes2/dialog/dialog_chest.cpp index 6362f0e0b55..b11c3817c3e 100644 --- a/src/fheroes2/dialog/dialog_chest.cpp +++ b/src/fheroes2/dialog/dialog_chest.cpp @@ -72,11 +72,11 @@ bool Dialog::SelectGoldOrExp( const std::string & header, const std::string & me const int32_t buttonsYPosition = box.GetArea().y + box.GetArea().height - buttonYesSprite.height(); const int32_t buttonYesXPosition = box.GetArea().x + box.GetArea().width / 2 - buttonYesSprite.width() - 20; - fheroes2::Button button_yes( buttonYesXPosition, buttonsYPosition, buttonYesIcnID, 0, 1 ); + fheroes2::Button buttonYes( buttonYesXPosition, buttonsYPosition, buttonYesIcnID, 0, 1 ); const int32_t buttonNoXPosition = box.GetArea().x + box.GetArea().width / 2 + 20; - fheroes2::Button button_no( buttonNoXPosition, buttonsYPosition, buttonNoIcnID, 0, 1 ); + fheroes2::Button buttonNo( buttonNoXPosition, buttonsYPosition, buttonNoIcnID, 0, 1 ); fheroes2::Rect pos = box.GetArea(); @@ -107,8 +107,8 @@ bool Dialog::SelectGoldOrExp( const std::string & header, const std::string & me fheroes2::FontType::smallWhite() ); text.draw( pos.x + sprite_expr.width() / 2 - text.width() / 2, pos.y + 4, display ); - button_yes.draw(); - button_no.draw(); + buttonYes.draw(); + buttonNo.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); @@ -116,14 +116,14 @@ bool Dialog::SelectGoldOrExp( const std::string & header, const std::string & me // message loop while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( button_yes.area() ) ? button_yes.drawOnPress() : button_yes.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( button_no.area() ) ? button_no.drawOnPress() : button_no.drawOnRelease(); + buttonYes.drawOnState( le.isMouseLeftButtonPressedInArea( buttonYes.area() ) ); + buttonNo.drawOnState( le.isMouseLeftButtonPressedInArea( buttonNo.area() ) ); - if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( button_yes.area() ) ) { + if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonYes.area() ) ) { result = true; break; } - if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( button_no.area() ) ) { + if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonNo.area() ) ) { result = false; break; } diff --git a/src/fheroes2/dialog/dialog_file.cpp b/src/fheroes2/dialog/dialog_file.cpp index 30c979a9930..969ee173bdd 100644 --- a/src/fheroes2/dialog/dialog_file.cpp +++ b/src/fheroes2/dialog/dialog_file.cpp @@ -87,11 +87,11 @@ namespace // dialog menu loop while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonNew.area() ) ? buttonNew.drawOnPress() : buttonNew.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonLoad.area() ) ? buttonLoad.drawOnPress() : buttonLoad.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonSave.area() ) ? buttonSave.drawOnPress() : buttonSave.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonQuit.area() ) ? buttonQuit.drawOnPress() : buttonQuit.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + buttonNew.drawOnState( le.isMouseLeftButtonPressedInArea( buttonNew.area() ) ); + buttonLoad.drawOnState( le.isMouseLeftButtonPressedInArea( buttonLoad.area() ) ); + buttonSave.drawOnState( le.isMouseLeftButtonPressedInArea( buttonSave.area() ) ); + buttonQuit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonQuit.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( le.MouseClickLeft( buttonNew.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_NEW_GAME ) ) { if ( Interface::AdventureMap::Get().EventNewGame() == fheroes2::GameMode::NEW_GAME ) { @@ -111,7 +111,8 @@ namespace return Interface::AdventureMap::Get().EventSaveGame(); } - else if ( le.MouseClickLeft( buttonQuit.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_QUIT ) ) { + + if ( le.MouseClickLeft( buttonQuit.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_QUIT ) ) { if ( Interface::AdventureMap::EventExit() == fheroes2::GameMode::QUIT_GAME ) { result = fheroes2::GameMode::QUIT_GAME; break; @@ -121,7 +122,8 @@ namespace result = fheroes2::GameMode::CANCEL; break; } - else if ( le.isMouseRightButtonPressedInArea( buttonNew.area() ) ) { + + if ( le.isMouseRightButtonPressedInArea( buttonNew.area() ) ) { fheroes2::showStandardTextMessage( _( "New Game" ), _( "Start a single or multi-player game." ), Dialog::ZERO ); } else if ( le.isMouseRightButtonPressedInArea( buttonLoad.area() ) ) { diff --git a/src/fheroes2/dialog/dialog_game_settings.cpp b/src/fheroes2/dialog/dialog_game_settings.cpp index 32cc0250968..78f95f0b8ec 100644 --- a/src/fheroes2/dialog/dialog_game_settings.cpp +++ b/src/fheroes2/dialog/dialog_game_settings.cpp @@ -167,8 +167,9 @@ namespace drawOptions(); - fheroes2::ButtonSprite okayButton( windowRoi.x + 112, windowRoi.y + 252, fheroes2::AGG::GetICN( buttonIcnId, 0 ), fheroes2::AGG::GetICN( buttonIcnId, 1 ) ); - okayButton.draw(); + fheroes2::ButtonSprite buttonOk( windowRoi.x + 112, windowRoi.y + 252, fheroes2::AGG::GetICN( buttonIcnId, 0 ), fheroes2::AGG::GetICN( buttonIcnId, 1 ) ); + + buttonOk.draw(); display.render(); @@ -176,14 +177,9 @@ namespace LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - if ( le.isMouseLeftButtonPressedInArea( okayButton.area() ) ) { - okayButton.drawOnPress(); - } - else { - okayButton.drawOnRelease(); - } + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); - if ( le.MouseClickLeft( okayButton.area() ) || Game::HotKeyCloseWindow() ) { + if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( windowLanguageRoi ) ) { @@ -224,7 +220,7 @@ namespace fheroes2::showStandardTextMessage( _( "Text Support" ), _( "Toggle text support mode to output extra information about windows and events in the game." ), 0 ); } - else if ( le.isMouseRightButtonPressedInArea( okayButton.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } diff --git a/src/fheroes2/dialog/dialog_gameinfo.cpp b/src/fheroes2/dialog/dialog_gameinfo.cpp index 675f37feffc..9cf218ced83 100644 --- a/src/fheroes2/dialog/dialog_gameinfo.cpp +++ b/src/fheroes2/dialog/dialog_gameinfo.cpp @@ -153,6 +153,7 @@ void Dialog::GameInfo() const int buttonOkIcnId = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; fheroes2::Button buttonOk( shadowOffset.x + OK_BUTTON_OFFSET - fheroes2::AGG::GetICN( buttonOkIcnId, 0 ).width() / 2, shadowOffset.y + 426, buttonOkIcnId, 0, 1 ); + buttonOk.draw(); display.render(); @@ -161,10 +162,11 @@ void Dialog::GameInfo() // message loop while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); - if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) + if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) { break; + } if ( le.isMouseRightButtonPressedInArea( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); diff --git a/src/fheroes2/dialog/dialog_graphics_settings.cpp b/src/fheroes2/dialog/dialog_graphics_settings.cpp index 5f7dd09951f..51ba83d7faf 100644 --- a/src/fheroes2/dialog/dialog_graphics_settings.cpp +++ b/src/fheroes2/dialog/dialog_graphics_settings.cpp @@ -158,8 +158,9 @@ namespace drawOptions(); const fheroes2::Point buttonOffset( 112 + windowRoi.x, 252 + windowRoi.y ); - fheroes2::Button okayButton( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); - okayButton.draw(); + fheroes2::Button buttonOk( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); + + buttonOk.draw(); display.render(); @@ -167,14 +168,9 @@ namespace LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - if ( le.isMouseLeftButtonPressedInArea( okayButton.area() ) ) { - okayButton.drawOnPress(); - } - else { - okayButton.drawOnRelease(); - } + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); - if ( le.MouseClickLeft( okayButton.area() ) || Game::HotKeyCloseWindow() ) { + if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( windowResolutionRoi ) ) { @@ -202,7 +198,7 @@ namespace if ( le.isMouseRightButtonPressedInArea( windowSystemInfoRoi ) ) { fheroes2::showStandardTextMessage( _( "System Info" ), _( "Show extra information such as FPS and current time." ), 0 ); } - else if ( le.isMouseRightButtonPressedInArea( okayButton.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } diff --git a/src/fheroes2/dialog/dialog_hotkeys.cpp b/src/fheroes2/dialog/dialog_hotkeys.cpp index e05fb532164..f255bc7657f 100644 --- a/src/fheroes2/dialog/dialog_hotkeys.cpp +++ b/src/fheroes2/dialog/dialog_hotkeys.cpp @@ -300,7 +300,7 @@ namespace fheroes2 LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); listbox.QueueEventProcessing(); diff --git a/src/fheroes2/dialog/dialog_interface_settings.cpp b/src/fheroes2/dialog/dialog_interface_settings.cpp index b40fd75b605..95a913b18d0 100644 --- a/src/fheroes2/dialog/dialog_interface_settings.cpp +++ b/src/fheroes2/dialog/dialog_interface_settings.cpp @@ -185,13 +185,14 @@ namespace drawOptions(); const fheroes2::Point buttonOffset( 112 + windowRoi.x, 252 + windowRoi.y ); - fheroes2::Button okayButton( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); - okayButton.draw(); + fheroes2::Button buttonOk( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); - const auto refreshWindow = [&drawOptions, &emptyDialogRestorer, &okayButton, &display]() { + buttonOk.draw(); + + const auto refreshWindow = [&drawOptions, &emptyDialogRestorer, &buttonOk, &display]() { emptyDialogRestorer.restore(); drawOptions(); - okayButton.draw(); + buttonOk.draw(); display.render( emptyDialogRestorer.rect() ); }; @@ -201,14 +202,9 @@ namespace LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - if ( le.isMouseLeftButtonPressedInArea( okayButton.area() ) ) { - okayButton.drawOnPress(); - } - else { - okayButton.drawOnRelease(); - } + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); - if ( le.MouseClickLeft( okayButton.area() ) || Game::HotKeyCloseWindow() ) { + if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( windowInterfaceTypeRoi ) ) { @@ -255,7 +251,7 @@ namespace if ( le.isMouseRightButtonPressedInArea( windowScrollSpeedRoi ) ) { fheroes2::showStandardTextMessage( _( "Scroll Speed" ), _( "Sets the speed at which you scroll the window." ), 0 ); } - else if ( le.isMouseRightButtonPressedInArea( okayButton.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } diff --git a/src/fheroes2/dialog/dialog_language_selection.cpp b/src/fheroes2/dialog/dialog_language_selection.cpp index cf5348d7c94..3d41e86b73b 100644 --- a/src/fheroes2/dialog/dialog_language_selection.cpp +++ b/src/fheroes2/dialog/dialog_language_selection.cpp @@ -231,8 +231,11 @@ namespace LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) && buttonOk.isEnabled() ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + if ( buttonOk.isEnabled() ) { + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); + } + + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( le.isMouseRightButtonPressedInArea( listRoi ) ) { continue; diff --git a/src/fheroes2/dialog/dialog_levelup.cpp b/src/fheroes2/dialog/dialog_levelup.cpp index 1a2b13c4708..996766042dd 100644 --- a/src/fheroes2/dialog/dialog_levelup.cpp +++ b/src/fheroes2/dialog/dialog_levelup.cpp @@ -110,11 +110,11 @@ namespace fheroes2::Point pt; pt.x = box.GetArea().x + box.GetArea().width / 2 - fheroes2::AGG::GetICN( buttonLearnIcnID, 0 ).width() - 20; pt.y = box.GetArea().y + box.GetArea().height - fheroes2::AGG::GetICN( buttonLearnIcnID, 0 ).height(); - fheroes2::Button button_learn1( pt.x, pt.y, buttonLearnIcnID, 0, 1 ); + fheroes2::Button buttonLearnLeft( pt.x, pt.y, buttonLearnIcnID, 0, 1 ); pt.x = box.GetArea().x + box.GetArea().width / 2 + 20; pt.y = box.GetArea().y + box.GetArea().height - fheroes2::AGG::GetICN( buttonLearnIcnID, 0 ).height(); - fheroes2::Button button_learn2( pt.x, pt.y, buttonLearnIcnID, 0, 1 ); + fheroes2::Button buttonLearnRight( pt.x, pt.y, buttonLearnIcnID, 0, 1 ); const fheroes2::Rect & boxArea = box.GetArea(); fheroes2::Point pos( boxArea.x, boxArea.y ); @@ -155,34 +155,34 @@ namespace pt.y = box.GetArea().y + box.GetArea().height - 35; const int icnHeroes = isEvilInterface ? ICN::EVIL_ARMY_BUTTON : ICN::GOOD_ARMY_BUTTON; - fheroes2::ButtonSprite button_hero + fheroes2::ButtonSprite buttonHero = fheroes2::makeButtonWithBackground( pt.x, pt.y, fheroes2::AGG::GetICN( icnHeroes, 0 ), fheroes2::AGG::GetICN( icnHeroes, 1 ), display ); text.set( std::to_string( hero.GetSecondarySkills().Count() ) + "/" + std::to_string( Heroes::maxNumOfSecSkills ), fheroes2::FontType::normalWhite() ); text.draw( box.GetArea().x + ( box.GetArea().width - text.width() ) / 2, pt.y - 15, display ); - button_learn1.draw(); - button_learn2.draw(); - button_hero.draw(); + buttonLearnLeft.draw(); + buttonLearnRight.draw(); + buttonHero.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); // message loop while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( button_learn1.area() ) ? button_learn1.drawOnPress() : button_learn1.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( button_learn2.area() ) ? button_learn2.drawOnPress() : button_learn2.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( button_hero.area() ) ? button_hero.drawOnPress() : button_hero.drawOnRelease(); + buttonLearnLeft.drawOnState( le.isMouseLeftButtonPressedInArea( buttonLearnLeft.area() ) ); + buttonLearnRight.drawOnState( le.isMouseLeftButtonPressedInArea( buttonLearnRight.area() ) ); + buttonHero.drawOnState( le.isMouseLeftButtonPressedInArea( buttonHero.area() ) ); - if ( le.MouseClickLeft( button_learn1.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) ) { + if ( le.MouseClickLeft( buttonLearnLeft.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) ) { return sec1.Skill(); } - if ( le.MouseClickLeft( button_learn2.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) ) { + if ( le.MouseClickLeft( buttonLearnRight.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) ) { return sec2.Skill(); } - if ( le.MouseClickLeft( button_hero.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) { + if ( le.MouseClickLeft( buttonHero.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) { LocalEvent::Get().reset(); hero.OpenDialog( false, true, true, true, true, false, fheroes2::getLanguageFromAbbreviation( conf.getGameLanguage() ) ); display.render(); @@ -203,7 +203,7 @@ namespace fheroes2::SecondarySkillDialogElement( sec2, hero ).showPopup( Dialog::ZERO ); display.render(); } - else if ( le.isMouseRightButtonPressedInArea( button_hero.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonHero.area() ) ) { fheroes2::showStandardTextMessage( "", _( "View Hero" ), Dialog::ZERO ); } } diff --git a/src/fheroes2/dialog/dialog_marketplace.cpp b/src/fheroes2/dialog/dialog_marketplace.cpp index 77cd1841060..42956e6a3af 100644 --- a/src/fheroes2/dialog/dialog_marketplace.cpp +++ b/src/fheroes2/dialog/dialog_marketplace.cpp @@ -445,16 +445,20 @@ void Dialog::Marketplace( Kingdom & kingdom, bool fromTradingPost ) // message loop while ( le.HandleEvents() ) { - if ( buttonGift.isEnabled() ) - le.isMouseLeftButtonPressedInArea( buttonGift.area() ) ? buttonGift.drawOnPress() : buttonGift.drawOnRelease(); - if ( buttonTrade.isEnabled() ) - le.isMouseLeftButtonPressedInArea( buttonTrade.area() ) ? buttonTrade.drawOnPress() : buttonTrade.drawOnRelease(); - if ( buttonLeft.isEnabled() ) - le.isMouseLeftButtonPressedInArea( buttonLeft.area() ) ? buttonLeft.drawOnPress() : buttonLeft.drawOnRelease(); - if ( buttonRight.isEnabled() ) - le.isMouseLeftButtonPressedInArea( buttonRight.area() ) ? buttonRight.drawOnPress() : buttonRight.drawOnRelease(); + if ( buttonGift.isEnabled() ) { + buttonGift.drawOnState( le.isMouseLeftButtonPressedInArea( buttonGift.area() ) ); + } + if ( buttonTrade.isEnabled() ) { + buttonTrade.drawOnState( le.isMouseLeftButtonPressedInArea( buttonTrade.area() ) ); + } + if ( buttonLeft.isEnabled() ) { + buttonLeft.drawOnState( le.isMouseLeftButtonPressedInArea( buttonLeft.area() ) ); + } + if ( buttonRight.isEnabled() ) { + buttonRight.drawOnState( le.isMouseLeftButtonPressedInArea( buttonRight.area() ) ); + } - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) break; @@ -602,7 +606,7 @@ void Dialog::Marketplace( Kingdom & kingdom, bool fromTradingPost ) // decrease trade resource if ( count_buy - && ( ( buttonLeft.isEnabled() && ( le.MouseClickLeft( gui.buttonLeft.area() ) || timedButtonLeft.isDelayPassed() ) ) + && ( ( buttonLeft.isEnabled() && ( le.MouseClickLeft( buttonLeft.area() ) || timedButtonLeft.isDelayPassed() ) ) || le.isMouseWheelDownInArea( scrollbar.getArea() ) ) ) { count_buy -= Resource::GOLD == resourceTo ? GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ) : 1; diff --git a/src/fheroes2/dialog/dialog_recruit.cpp b/src/fheroes2/dialog/dialog_recruit.cpp index ae795a3fd0c..f7e3559a9cd 100644 --- a/src/fheroes2/dialog/dialog_recruit.cpp +++ b/src/fheroes2/dialog/dialog_recruit.cpp @@ -413,11 +413,12 @@ Troop Dialog::RecruitMonster( const Monster & monster0, const uint32_t available } // When the "Up"/"Down" button is pressed it is shifted 1 pixel down so we need to properly restore the background. - const fheroes2::Rect buttonRoi = button.area(); button.release(); + const fheroes2::Rect & buttonRoi = button.area(); + fheroes2::Copy( background, buttonRoi.x - dialogOffset.x, buttonRoi.y - dialogOffset.y, display, buttonRoi.x, buttonRoi.y, buttonRoi.width, buttonRoi.height ); - // A the non-pressed button is already on the "background" copy so we do not render the button. + // The non-pressed button is already on the "background" copy so we do not render the button. display.render( buttonRoi ); }; @@ -445,9 +446,10 @@ Troop Dialog::RecruitMonster( const Monster & monster0, const uint32_t available bool redraw = false; if ( buttonOk.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); } - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( le.isMouseLeftButtonPressedInArea( buttonUp.area() ) ) { buttonUp.drawOnPress(); @@ -464,10 +466,10 @@ Troop Dialog::RecruitMonster( const Monster & monster0, const uint32_t available } if ( buttonMax.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonMax.area() ) ? buttonMax.drawOnPress() : buttonMax.drawOnRelease(); + buttonMax.drawOnState( le.isMouseLeftButtonPressedInArea( buttonMax.area() ) ); } if ( buttonMin.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonMin.area() ) ? buttonMin.drawOnPress() : buttonMin.drawOnRelease(); + buttonMin.drawOnState( le.isMouseLeftButtonPressedInArea( buttonMin.area() ) ); } bool updateMonsterInfo = false; diff --git a/src/fheroes2/dialog/dialog_resolution.cpp b/src/fheroes2/dialog/dialog_resolution.cpp index 586eccec5cb..f0760656049 100644 --- a/src/fheroes2/dialog/dialog_resolution.cpp +++ b/src/fheroes2/dialog/dialog_resolution.cpp @@ -278,8 +278,8 @@ namespace LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) && buttonOk.isEnabled() ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); const int listId = listBox.getCurrentId(); listBox.QueueEventProcessing(); @@ -295,7 +295,8 @@ namespace selectedResolution = {}; break; } - else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { + + if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } else if ( le.isMouseRightButtonPressedInArea( buttonOk.area() ) ) { diff --git a/src/fheroes2/dialog/dialog_selectcount.cpp b/src/fheroes2/dialog/dialog_selectcount.cpp index 6592d2f5dc0..b74ad9f2f2a 100644 --- a/src/fheroes2/dialog/dialog_selectcount.cpp +++ b/src/fheroes2/dialog/dialog_selectcount.cpp @@ -260,7 +260,10 @@ bool Dialog::inputString( const fheroes2::TextBase & title, const fheroes2::Text while ( le.HandleEvents( Game::isDelayNeeded( { Game::DelayType::CURSOR_BLINK_DELAY } ) ) ) { bool redraw = false; - buttonOk.drawOnState( buttonOk.isEnabled() && le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); + if ( buttonOk.isEnabled() ) { + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); + } + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); buttonVirtualKB.drawOnState( le.isMouseLeftButtonPressedInArea( buttonVirtualKB.area() ) ); @@ -453,11 +456,11 @@ int Dialog::ArmySplitTroop( const int32_t freeSlots, const int32_t redistributeM bool redraw_count = false; if ( buttonMax.isVisible() ) { - le.isMouseLeftButtonPressedInArea( buttonMax.area() ) ? buttonMax.drawOnPress() : buttonMax.drawOnRelease(); + buttonMax.drawOnState( le.isMouseLeftButtonPressedInArea( buttonMax.area() ) ); } if ( buttonMin.isVisible() ) { - le.isMouseLeftButtonPressedInArea( buttonMin.area() ) ? buttonMin.drawOnPress() : buttonMin.drawOnRelease(); + buttonMin.drawOnState( le.isMouseLeftButtonPressedInArea( buttonMin.area() ) ); } if ( fheroes2::processIntegerValueTyping( redistributeMin, redistributeMax, redistributeCount ) ) { @@ -465,13 +468,11 @@ int Dialog::ArmySplitTroop( const int32_t freeSlots, const int32_t redistributeM redraw_count = true; } else if ( buttonMax.isVisible() && le.MouseClickLeft( buttonMax.area() ) ) { - le.isMouseLeftButtonPressedInArea( buttonMax.area() ) ? buttonMax.drawOnPress() : buttonMax.drawOnRelease(); redistributeCount = redistributeMax; valueSelectionElement.setValue( redistributeMax ); redraw_count = true; } else if ( buttonMin.isVisible() && le.MouseClickLeft( buttonMin.area() ) ) { - le.isMouseLeftButtonPressedInArea( buttonMin.area() ) ? buttonMin.drawOnPress() : buttonMin.drawOnRelease(); redistributeCount = redistributeMin; valueSelectionElement.setValue( redistributeMin ); redraw_count = true; @@ -486,7 +487,7 @@ int Dialog::ArmySplitTroop( const int32_t freeSlots, const int32_t redistributeM if ( le.MouseClickLeft( *it ) ) { ssp.setPosition( it->x, it->y ); ssp.show(); - display.render(); + display.render( pos ); } } } @@ -506,7 +507,7 @@ int Dialog::ArmySplitTroop( const int32_t freeSlots, const int32_t redistributeM buttonMin.draw(); } - display.render(); + display.render( pos ); } bres = btnGroups.processEvents(); diff --git a/src/fheroes2/dialog/dialog_selectitems.cpp b/src/fheroes2/dialog/dialog_selectitems.cpp index dff88caea3e..178aa202a93 100644 --- a/src/fheroes2/dialog/dialog_selectitems.cpp +++ b/src/fheroes2/dialog/dialog_selectitems.cpp @@ -1057,8 +1057,8 @@ int Dialog::selectHeroType( const int heroType ) } while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) { return heroColor * 7 + heroRace; @@ -1290,11 +1290,16 @@ void Dialog::selectTownType( int & type, int & color ) townColor = color; } - isCastle ? buttonCastle.drawOnPress() : buttonTown.drawOnPress(); + if ( isCastle ) { + buttonCastle.drawOnPress(); + } + else { + buttonTown.drawOnPress(); + } while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( isCastle ) { if ( le.isMouseLeftButtonPressedInArea( buttonTown.area() ) ) { @@ -1642,8 +1647,8 @@ void Dialog::selectMineType( int32_t & type, int32_t & color ) LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); bool needRedraw = listbox.QueueEventProcessing(); diff --git a/src/fheroes2/dialog/dialog_system_options.cpp b/src/fheroes2/dialog/dialog_system_options.cpp index 9935023fab2..b793a85add6 100644 --- a/src/fheroes2/dialog/dialog_system_options.cpp +++ b/src/fheroes2/dialog/dialog_system_options.cpp @@ -245,13 +245,14 @@ namespace drawOptions(); const fheroes2::Point buttonOffset( 112 + windowRoi.x, 362 + windowRoi.y ); - fheroes2::Button okayButton( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); - okayButton.draw(); + fheroes2::Button buttonOk( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); - const auto refreshWindow = [&drawOptions, &emptyDialogRestorer, &okayButton, &display]() { + buttonOk.draw(); + + const auto refreshWindow = [&drawOptions, &emptyDialogRestorer, &buttonOk, &display]() { emptyDialogRestorer.restore(); drawOptions(); - okayButton.draw(); + buttonOk.draw(); display.render( emptyDialogRestorer.rect() ); }; @@ -261,14 +262,9 @@ namespace LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - if ( le.isMouseLeftButtonPressedInArea( okayButton.area() ) ) { - okayButton.drawOnPress(); - } - else { - okayButton.drawOnRelease(); - } + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); - if ( le.MouseClickLeft( okayButton.area() ) || Game::HotKeyCloseWindow() ) { + if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( windowLanguageRoi ) ) { @@ -393,7 +389,7 @@ namespace else if ( le.isMouseRightButtonPressedInArea( windowBattlesRoi ) ) { fheroes2::showStandardTextMessage( _( "Battles" ), _( "Toggle instant battle mode." ), 0 ); } - else if ( le.isMouseRightButtonPressedInArea( okayButton.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } diff --git a/src/fheroes2/dialog/dialog_thievesguild.cpp b/src/fheroes2/dialog/dialog_thievesguild.cpp index e43eff2f08e..f385f37c71f 100644 --- a/src/fheroes2/dialog/dialog_thievesguild.cpp +++ b/src/fheroes2/dialog/dialog_thievesguild.cpp @@ -455,7 +455,7 @@ void Dialog::ThievesGuild( const bool oracle ) LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; diff --git a/src/fheroes2/editor/editor_castle_details_window.cpp b/src/fheroes2/editor/editor_castle_details_window.cpp index f684a49f9a2..cf2e551148a 100644 --- a/src/fheroes2/editor/editor_castle_details_window.cpp +++ b/src/fheroes2/editor/editor_castle_details_window.cpp @@ -365,8 +365,7 @@ namespace Editor // Build restrict mode button. fheroes2::Button buttonRestrictBuilding( 0, 0, isEvilInterface ? ICN::BUTTON_RESTRICT_EVIL : ICN::BUTTON_RESTRICT_GOOD, 0, 1 ); buttonRestrictBuilding.setPosition( dialogRoi.x + rightPartOffsetX + ( rightPartSizeX - buttonRestrictBuilding.area().width ) / 2, dialogRoi.y + 195 ); - const fheroes2::Rect buttonRestrictBuildingArea( buttonRestrictBuilding.area() ); - fheroes2::addGradientShadow( fheroes2::AGG::GetICN( ICN::BUTTON_RESTRICT_GOOD, 0 ), display, buttonRestrictBuildingArea.getPosition(), { -5, 5 } ); + fheroes2::addGradientShadow( fheroes2::AGG::GetICN( ICN::BUTTON_RESTRICT_GOOD, 0 ), display, buttonRestrictBuilding.area().getPosition(), { -5, 5 } ); buttonRestrictBuilding.draw(); const bool isNeutral = ( color == Color::NONE ); @@ -451,8 +450,7 @@ namespace Editor // Exit button. fheroes2::Button buttonExit( dialogRoi.x + rightPartOffsetX + 50, dialogRoi.y + 430, isEvilInterface ? ICN::BUTTON_SMALL_EXIT_EVIL : ICN::BUTTON_SMALL_EXIT_GOOD, 0, 1 ); - const fheroes2::Rect buttonExitArea( buttonExit.area() ); - fheroes2::addGradientShadow( fheroes2::AGG::GetICN( ICN::BUTTON_SMALL_EXIT_GOOD, 0 ), display, buttonExitArea.getPosition(), { -2, 2 } ); + fheroes2::addGradientShadow( fheroes2::AGG::GetICN( ICN::BUTTON_SMALL_EXIT_GOOD, 0 ), display, buttonExit.area().getPosition(), { -2, 2 } ); buttonExit.draw(); // Status bar. @@ -471,17 +469,17 @@ namespace Editor bool buildingRestriction = false; while ( le.HandleEvents() ) { - buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExitArea ) ); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); - if ( le.MouseClickLeft( buttonExitArea ) || Game::HotKeyCloseWindow() ) { + if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } - if ( le.MouseClickLeft( buttonRestrictBuildingArea ) ) { + if ( le.MouseClickLeft( buttonRestrictBuilding.area() ) ) { buildingRestriction = !buildingRestriction; } - buttonRestrictBuilding.drawOnState( buildingRestriction || le.isMouseLeftButtonPressedInArea( buttonRestrictBuildingArea ) ); + buttonRestrictBuilding.drawOnState( buildingRestriction || le.isMouseLeftButtonPressedInArea( buttonRestrictBuilding.area() ) ); if ( le.isMouseCursorPosInArea( nameArea ) ) { message = _( "Click to change the Castle name. Right-click to reset to default." ); @@ -551,7 +549,7 @@ namespace Editor } } - else if ( le.isMouseCursorPosInArea( buttonRestrictBuildingArea ) ) { + else if ( le.isMouseCursorPosInArea( buttonRestrictBuilding.area() ) ) { message = _( "Toggle building construction restriction mode." ); if ( le.isMouseRightButtonPressed() ) { @@ -590,7 +588,7 @@ namespace Editor message = _( "Set custom Castle Army. Right-click to reset unit." ); } } - else if ( le.isMouseCursorPosInArea( buttonExitArea ) ) { + else if ( le.isMouseCursorPosInArea( buttonExit.area() ) ) { message = _( "Exit Castle Options" ); if ( le.isMouseRightButtonPressed() ) { diff --git a/src/fheroes2/editor/editor_interface.cpp b/src/fheroes2/editor/editor_interface.cpp index 5ead6184e3d..9a6f655de33 100644 --- a/src/fheroes2/editor/editor_interface.cpp +++ b/src/fheroes2/editor/editor_interface.cpp @@ -1107,11 +1107,11 @@ namespace Interface LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonNew.area() ) ? buttonNew.drawOnPress() : buttonNew.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonLoad.area() ) ? buttonLoad.drawOnPress() : buttonLoad.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonSave.area() ) ? buttonSave.drawOnPress() : buttonSave.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonQuit.area() ) ? buttonQuit.drawOnPress() : buttonQuit.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + buttonNew.drawOnState( le.isMouseLeftButtonPressedInArea( buttonNew.area() ) ); + buttonLoad.drawOnState( le.isMouseLeftButtonPressedInArea( buttonLoad.area() ) ); + buttonSave.drawOnState( le.isMouseLeftButtonPressedInArea( buttonSave.area() ) ); + buttonQuit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonQuit.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( le.MouseClickLeft( buttonNew.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_NEW_MAP_MENU ) ) { if ( eventNewMap() == fheroes2::GameMode::EDITOR_NEW_MAP ) { @@ -1132,7 +1132,8 @@ namespace Interface break; } - else if ( le.MouseClickLeft( buttonQuit.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_QUIT ) ) { + + if ( le.MouseClickLeft( buttonQuit.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_QUIT ) ) { if ( EventExit() == fheroes2::GameMode::QUIT_GAME ) { result = fheroes2::GameMode::QUIT_GAME; break; @@ -1141,7 +1142,8 @@ namespace Interface else if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyCloseWindow() ) { break; } - else if ( le.isMouseRightButtonPressedInArea( buttonNew.area() ) ) { + + if ( le.isMouseRightButtonPressedInArea( buttonNew.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 ); diff --git a/src/fheroes2/editor/editor_mainmenu.cpp b/src/fheroes2/editor/editor_mainmenu.cpp index 24d3501ae31..b541ee74705 100644 --- a/src/fheroes2/editor/editor_mainmenu.cpp +++ b/src/fheroes2/editor/editor_mainmenu.cpp @@ -122,8 +122,9 @@ namespace buttons[i].draw(); } - fheroes2::Button cancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); - cancel.draw(); + fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + + buttonCancel.draw(); fheroes2::validateFadeInAndRender(); @@ -131,7 +132,7 @@ namespace while ( le.HandleEvents() ) { for ( size_t i = 0; i < mapSizeCount; ++i ) { - le.isMouseLeftButtonPressedInArea( buttons[i].area() ) ? buttons[i].drawOnPress() : buttons[i].drawOnRelease(); + buttons[i].drawOnState( le.isMouseLeftButtonPressedInArea( buttons[i].area() ) ); if ( le.MouseClickLeft( buttons[i].area() ) || Game::HotKeyPressEvent( mapSizeHotkeys[i] ) ) { return mapSizes[i]; @@ -146,13 +147,13 @@ namespace } } - le.isMouseLeftButtonPressedInArea( cancel.area() ) ? cancel.drawOnPress() : cancel.drawOnRelease(); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); - if ( le.MouseClickLeft( cancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { + if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return Maps::ZERO; } - if ( le.isMouseRightButtonPressedInArea( cancel.area() ) ) { + if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the New Map menu." ), Dialog::ZERO ); } } @@ -179,42 +180,42 @@ namespace Editor const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); - fheroes2::Button newMap( buttonPos.x, buttonPos.y, ICN::BTNEMAIN, 0, 1 ); - fheroes2::Button loadMap( buttonPos.x, buttonPos.y + buttonYStep, ICN::BTNEMAIN, 2, 3 ); - fheroes2::Button cancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + fheroes2::Button buttonNewMap( buttonPos.x, buttonPos.y, ICN::BTNEMAIN, 0, 1 ); + fheroes2::Button buttonLoadMap( buttonPos.x, buttonPos.y + buttonYStep, ICN::BTNEMAIN, 2, 3 ); + fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); - newMap.draw(); - loadMap.draw(); - cancel.draw(); + buttonNewMap.draw(); + buttonLoadMap.draw(); + buttonCancel.draw(); fheroes2::validateFadeInAndRender(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( newMap.area() ) ? newMap.drawOnPress() : newMap.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( loadMap.area() ) ? loadMap.drawOnPress() : loadMap.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( cancel.area() ) ? cancel.drawOnPress() : cancel.drawOnRelease(); + buttonNewMap.drawOnState( le.isMouseLeftButtonPressedInArea( buttonNewMap.area() ) ); + buttonLoadMap.drawOnState( le.isMouseLeftButtonPressedInArea( buttonLoadMap.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); - if ( le.MouseClickLeft( newMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_NEW_MAP_MENU ) ) { + if ( le.MouseClickLeft( buttonNewMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_NEW_MAP_MENU ) ) { return fheroes2::GameMode::EDITOR_NEW_MAP; } - if ( le.MouseClickLeft( loadMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_LOAD_MAP_MENU ) ) { + if ( le.MouseClickLeft( buttonLoadMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_LOAD_MAP_MENU ) ) { return fheroes2::GameMode::EDITOR_LOAD_MAP; } - if ( le.MouseClickLeft( cancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { + if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return fheroes2::GameMode::MAIN_MENU; } - if ( le.isMouseRightButtonPressedInArea( newMap.area() ) ) { + 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( loadMap.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonLoadMap.area() ) ) { fheroes2::showStandardTextMessage( _( "Load Map" ), _( "Load an existing map." ), Dialog::ZERO ); } - else if ( le.isMouseRightButtonPressedInArea( cancel.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } } @@ -232,31 +233,31 @@ namespace Editor const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); - fheroes2::Button scratchMap( buttonPos.x, buttonPos.y, ICN::BTNENEW, 0, 1 ); - fheroes2::Button randomMap( buttonPos.x, buttonPos.y + buttonYStep, ICN::BTNENEW, 2, 3 ); - fheroes2::Button cancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + 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: enable it back once random map generator is ready. - randomMap.disable(); + buttonRandomMap.disable(); - scratchMap.draw(); - randomMap.draw(); - cancel.draw(); + buttonScratchMap.draw(); + buttonRandomMap.draw(); + buttonCancel.draw(); fheroes2::validateFadeInAndRender(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( scratchMap.area() ) ? scratchMap.drawOnPress() : scratchMap.drawOnRelease(); + buttonScratchMap.drawOnState( le.isMouseLeftButtonPressedInArea( buttonScratchMap.area() ) ); - if ( randomMap.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( randomMap.area() ) ? randomMap.drawOnPress() : randomMap.drawOnRelease(); + if ( buttonRandomMap.isEnabled() ) { + buttonRandomMap.drawOnState( le.isMouseLeftButtonPressedInArea( buttonRandomMap.area() ) ); } - le.isMouseLeftButtonPressedInArea( cancel.area() ) ? cancel.drawOnPress() : cancel.drawOnRelease(); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); - if ( le.MouseClickLeft( scratchMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_FROM_SCRATCH_MAP_MENU ) ) { + if ( le.MouseClickLeft( buttonScratchMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_FROM_SCRATCH_MAP_MENU ) ) { const Maps::MapSize mapSize = selectMapSize(); if ( mapSize != Maps::ZERO ) { world.generateForEditor( mapSize ); @@ -272,24 +273,24 @@ namespace Editor return fheroes2::GameMode::EDITOR_NEW_MAP; } - if ( randomMap.isEnabled() && ( le.MouseClickLeft( randomMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_RANDOM_MAP_MENU ) ) ) { + 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; } - if ( le.MouseClickLeft( cancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { + if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return fheroes2::GameMode::EDITOR_MAIN_MENU; } - if ( le.isMouseRightButtonPressedInArea( scratchMap.area() ) ) { + if ( le.isMouseRightButtonPressedInArea( buttonScratchMap.area() ) ) { fheroes2::showStandardTextMessage( _( "From Scratch" ), _( "Start from scratch with a blank map." ), Dialog::ZERO ); } - else if ( le.isMouseRightButtonPressedInArea( randomMap.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonRandomMap.area() ) ) { fheroes2::showStandardTextMessage( _( "Random" ), _( "Create a randomly generated map." ), Dialog::ZERO ); } - else if ( le.isMouseRightButtonPressedInArea( cancel.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the Map Editor main menu." ), Dialog::ZERO ); } } diff --git a/src/fheroes2/editor/editor_map_specs_window.cpp b/src/fheroes2/editor/editor_map_specs_window.cpp index 919dfd00085..4067521c9ac 100644 --- a/src/fheroes2/editor/editor_map_specs_window.cpp +++ b/src/fheroes2/editor/editor_map_specs_window.cpp @@ -2245,28 +2245,23 @@ namespace Editor fheroes2::Button buttonCancel; const int buttonCancelIcn = isEvilInterface ? ICN::BUTTON_SMALL_CANCEL_EVIL : ICN::BUTTON_SMALL_CANCEL_GOOD; background.renderButton( buttonCancel, buttonCancelIcn, 0, 1, { 20, 6 }, fheroes2::StandardWindow::Padding::BOTTOM_RIGHT ); - const fheroes2::Rect buttonCancelRoi( buttonCancel.area() ); fheroes2::Button buttonOk; const int buttonOkIcn = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; - background.renderButton( buttonOk, buttonOkIcn, 0, 1, { 20 + buttonCancelRoi.width + 10, 6 }, fheroes2::StandardWindow::Padding::BOTTOM_RIGHT ); - const fheroes2::Rect buttonOkRoi( buttonOk.area() ); + background.renderButton( buttonOk, buttonOkIcn, 0, 1, { 20 + buttonCancel.area().width + 10, 6 }, fheroes2::StandardWindow::Padding::BOTTOM_RIGHT ); fheroes2::Button buttonRumors; const int buttonRumorsIcn = isEvilInterface ? ICN::BUTTON_RUMORS_EVIL : ICN::BUTTON_RUMORS_GOOD; background.renderButton( buttonRumors, buttonRumorsIcn, 0, 1, { 20, 6 }, fheroes2::StandardWindow::Padding::BOTTOM_LEFT ); - const fheroes2::Rect buttonRumorsRoi( buttonRumors.area() ); fheroes2::Button buttonEvents; const int buttonEventsIcn = isEvilInterface ? ICN::BUTTON_EVENTS_EVIL : ICN::BUTTON_EVENTS_GOOD; - background.renderButton( buttonEvents, buttonEventsIcn, 0, 1, { 20 + buttonRumorsRoi.width + 10, 6 }, fheroes2::StandardWindow::Padding::BOTTOM_LEFT ); - const fheroes2::Rect buttonEventsRoi( buttonEvents.area() ); + background.renderButton( buttonEvents, buttonEventsIcn, 0, 1, { 20 + buttonRumors.area().width + 10, 6 }, fheroes2::StandardWindow::Padding::BOTTOM_LEFT ); fheroes2::Button buttonLanguage; const int buttonLanguageIcn = isEvilInterface ? ICN::BUTTON_LANGUAGE_EVIL : ICN::BUTTON_LANGUAGE_GOOD; - background.renderButton( buttonLanguage, buttonLanguageIcn, 0, 1, { 20 + buttonRumorsRoi.width + buttonEventsRoi.width + 2 * 10, 6 }, + background.renderButton( buttonLanguage, buttonLanguageIcn, 0, 1, { 20 + buttonRumors.area().width + buttonEvents.area().width + 2 * 10, 6 }, fheroes2::StandardWindow::Padding::BOTTOM_LEFT ); - const fheroes2::Rect buttonLanguageRoi( buttonLanguage.area() ); auto renderMapName = [&text, &mapFormat, &display, &scenarioBox, &mapNameRoi, &scenarioBoxRoi]() { text.set( mapFormat.name, fheroes2::FontType::normalWhite(), mapFormat.mainLanguage ); @@ -2299,19 +2294,19 @@ namespace Editor display.render( background.totalArea() ); while ( le.HandleEvents() ) { - buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOkRoi ) ); - buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancelRoi ) ); - buttonRumors.drawOnState( le.isMouseLeftButtonPressedInArea( buttonRumorsRoi ) ); - buttonEvents.drawOnState( le.isMouseLeftButtonPressedInArea( buttonEventsRoi ) ); - buttonLanguage.drawOnState( le.isMouseLeftButtonPressedInArea( buttonLanguageRoi ) ); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); + buttonRumors.drawOnState( le.isMouseLeftButtonPressedInArea( buttonRumors.area() ) ); + buttonEvents.drawOnState( le.isMouseLeftButtonPressedInArea( buttonEvents.area() ) ); + buttonLanguage.drawOnState( le.isMouseLeftButtonPressedInArea( buttonLanguage.area() ) ); victoryDroplistButton.drawOnState( le.isMouseLeftButtonPressedInArea( victoryDroplistButtonRoi ) ); lossDroplistButton.drawOnState( le.isMouseLeftButtonPressedInArea( lossDroplistButtonRoi ) ); - if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancelRoi ) ) { + if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { return false; } - if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOkRoi ) ) { + if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOk.area() ) ) { break; } @@ -2323,7 +2318,7 @@ namespace Editor lossConditionUI.render( display, false ); display.render( lossConditionUIRoi ); } - else if ( le.MouseClickLeft( buttonRumorsRoi ) ) { + else if ( le.MouseClickLeft( buttonRumors.area() ) ) { auto temp = mapFormat.rumors; if ( openRumorWindow( temp, mapFormat.mainLanguage ) ) { mapFormat.rumors = std::move( temp ); @@ -2331,7 +2326,7 @@ namespace Editor display.render( background.totalArea() ); } - else if ( le.MouseClickLeft( buttonEventsRoi ) ) { + else if ( le.MouseClickLeft( buttonEvents.area() ) ) { auto temp = mapFormat.dailyEvents; if ( openDailyEventsWindow( temp, mapFormat.humanPlayerColors, mapFormat.computerPlayerColors, mapFormat.mainLanguage ) ) { mapFormat.dailyEvents = std::move( temp ); @@ -2339,7 +2334,7 @@ namespace Editor display.render( background.totalArea() ); } - else if ( le.MouseClickLeft( buttonLanguageRoi ) ) { + else if ( le.MouseClickLeft( buttonLanguage.area() ) ) { const std::vector supportedLanguages = fheroes2::getSupportedLanguages(); const fheroes2::SupportedLanguage language = fheroes2::selectLanguage( supportedLanguages, mapFormat.mainLanguage, false ); if ( language != mapFormat.mainLanguage ) { @@ -2419,19 +2414,19 @@ namespace Editor display.render( fheroes2::getBoundaryRect( lossTextRoi, lossConditionUIRoi ) ); } - else if ( le.isMouseRightButtonPressedInArea( buttonCancelRoi ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } - else if ( le.isMouseRightButtonPressedInArea( buttonOkRoi ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Click to accept the changes made." ), Dialog::ZERO ); } - else if ( le.isMouseRightButtonPressedInArea( buttonRumorsRoi ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonRumors.area() ) ) { fheroes2::showStandardTextMessage( _( "Rumors" ), _( "Click to edit custom rumors." ), Dialog::ZERO ); } - else if ( le.isMouseRightButtonPressedInArea( buttonEventsRoi ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonEvents.area() ) ) { fheroes2::showStandardTextMessage( _( "Events" ), _( "Click to edit daily events." ), Dialog::ZERO ); } - else if ( le.isMouseRightButtonPressedInArea( buttonLanguageRoi ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonLanguage.area() ) ) { fheroes2::showStandardTextMessage( _( "Language" ), _( "Click to change the language of the map." ), Dialog::ZERO ); } else if ( le.isMouseRightButtonPressedInArea( mapNameRoi ) ) { diff --git a/src/fheroes2/editor/editor_options.cpp b/src/fheroes2/editor/editor_options.cpp index 49852a243e0..82ae55f9979 100644 --- a/src/fheroes2/editor/editor_options.cpp +++ b/src/fheroes2/editor/editor_options.cpp @@ -167,21 +167,17 @@ namespace drawOptions(); - fheroes2::ButtonSprite okayButton( windowRoi.x + 112, windowRoi.y + 252, fheroes2::AGG::GetICN( buttonIcnId, 0 ), fheroes2::AGG::GetICN( buttonIcnId, 1 ) ); - okayButton.draw(); + fheroes2::ButtonSprite buttonOk( windowRoi.x + 112, windowRoi.y + 252, fheroes2::AGG::GetICN( buttonIcnId, 0 ), fheroes2::AGG::GetICN( buttonIcnId, 1 ) ); + + buttonOk.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - if ( le.isMouseLeftButtonPressedInArea( okayButton.area() ) ) { - okayButton.drawOnPress(); - } - else { - okayButton.drawOnRelease(); - } + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); - if ( le.MouseClickLeft( okayButton.area() ) || Game::HotKeyCloseWindow() ) { + if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( windowLanguageRoi ) ) { @@ -221,7 +217,7 @@ namespace else if ( le.isMouseRightButtonPressedInArea( windowPassabilityRoi ) ) { fheroes2::showStandardTextMessage( _( "Passability" ), _( "Toggle display of objects' passability." ), 0 ); } - else if ( le.isMouseRightButtonPressedInArea( okayButton.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } } diff --git a/src/fheroes2/game/game_campaign.cpp b/src/fheroes2/game/game_campaign.cpp index 23d3c3c2b30..7952a104bce 100644 --- a/src/fheroes2/game/game_campaign.cpp +++ b/src/fheroes2/game/game_campaign.cpp @@ -1029,7 +1029,7 @@ namespace LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) { break; @@ -1504,11 +1504,11 @@ fheroes2::GameMode Game::SelectCampaignScenario( const fheroes2::GameMode prevMo bool updateDisplay = false; while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonViewIntro.area() ) ? buttonViewIntro.drawOnPress() : buttonViewIntro.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonDifficulty.area() ) ? buttonDifficulty.drawOnPress() : buttonDifficulty.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonRestart.area() ) ? buttonRestart.drawOnPress() : buttonRestart.drawOnRelease(); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); + buttonViewIntro.drawOnState( le.isMouseLeftButtonPressedInArea( buttonViewIntro.area() ) ); + buttonDifficulty.drawOnState( le.isMouseLeftButtonPressedInArea( buttonDifficulty.area() ) ); + buttonRestart.drawOnState( le.isMouseLeftButtonPressedInArea( buttonRestart.area() ) ); for ( uint32_t i = 0; i < bonusChoiceCount; ++i ) { if ( le.isMouseLeftButtonPressedInArea( choiceArea[i] ) || ( i < hotKeyBonusChoice.size() && HotKeyPressEvent( hotKeyBonusChoice[i] ) ) ) { diff --git a/src/fheroes2/game/game_highscores.cpp b/src/fheroes2/game/game_highscores.cpp index 7059ba7e6ac..fc5ff474185 100644 --- a/src/fheroes2/game/game_highscores.cpp +++ b/src/fheroes2/game/game_highscores.cpp @@ -380,8 +380,8 @@ fheroes2::GameMode Game::DisplayHighScores( const bool isCampaign ) LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( Game::isDelayNeeded( { Game::MAPS_DELAY } ) ) ) { - le.isMouseLeftButtonPressedInArea( buttonOtherHighScore.area() ) ? buttonOtherHighScore.drawOnPress() : buttonOtherHighScore.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonOtherHighScore.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOtherHighScore.area() ) ); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); if ( le.MouseClickLeft( buttonExit.area() ) || HotKeyCloseWindow() ) { if ( isAfterGameCompletion || isDefaultScreenSize ) { diff --git a/src/fheroes2/game/game_interface.cpp b/src/fheroes2/game/game_interface.cpp index 86f46eee7ae..a2b70c817f7 100644 --- a/src/fheroes2/game/game_interface.cpp +++ b/src/fheroes2/game/game_interface.cpp @@ -253,7 +253,7 @@ int32_t Interface::AdventureMap::GetDimensionDoorDestination( const int32_t from if ( radarRect & mp ) { cursor.SetThemes( Cursor::POINTER ); - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } diff --git a/src/fheroes2/game/game_loadgame.cpp b/src/fheroes2/game/game_loadgame.cpp index e2af1a1249e..0503f39658f 100644 --- a/src/fheroes2/game/game_loadgame.cpp +++ b/src/fheroes2/game/game_loadgame.cpp @@ -94,18 +94,21 @@ fheroes2::GameMode Game::LoadMulti() 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 buttonCancelGame( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); buttonHotSeat.draw(); - buttonCancelGame.draw(); + buttonCancel.draw(); buttonNetwork.disable(); display.render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonHotSeat.area() ) ? buttonHotSeat.drawOnPress() : buttonHotSeat.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancelGame.area() ) ? buttonCancelGame.drawOnPress() : buttonCancelGame.drawOnRelease(); + buttonHotSeat.drawOnState( le.isMouseLeftButtonPressedInArea( buttonHotSeat.area() ) ); + if ( buttonNetwork.isEnabled() ) { + buttonNetwork.drawOnState( le.isMouseLeftButtonPressedInArea( buttonNetwork.area() ) ); + } + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( le.MouseClickLeft( buttonHotSeat.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_HOTSEAT ) ) { if ( ListFiles::IsEmpty( GetSaveDir(), GetSaveFileExtension( Game::TYPE_HOTSEAT ) ) ) { @@ -115,17 +118,17 @@ fheroes2::GameMode Game::LoadMulti() return fheroes2::GameMode::LOAD_HOT_SEAT; } } - else if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancelGame.area() ) ) { + else if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { return fheroes2::GameMode::LOAD_GAME; } // right info - else if ( le.isMouseRightButtonPressedInArea( buttonHotSeat.area() ) ) { + 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( buttonCancelGame.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } } @@ -155,7 +158,7 @@ fheroes2::GameMode Game::LoadGame() fheroes2::Button buttonMultiplayerGame( 0, 0, ICN::BUTTON_MULTIPLAYER_GAME, 0, 1 ); fheroes2::Button buttonCancel( 0, 0, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); - std::array buttons{ &buttonStandardGame, &buttonCampaignGame, &buttonMultiplayerGame, &buttonCancel }; + const std::array buttons{ &buttonStandardGame, &buttonCampaignGame, &buttonMultiplayerGame, &buttonCancel }; if ( !isSuccessionWarsCampaignPresent() ) { buttonCampaignGame.disable(); @@ -177,7 +180,7 @@ fheroes2::GameMode Game::LoadGame() while ( le.HandleEvents() ) { for ( fheroes2::ButtonBase * button : buttons ) { - le.isMouseLeftButtonPressedInArea( button->area() ) ? button->drawOnPress() : button->drawOnRelease(); + button->drawOnState( le.isMouseLeftButtonPressedInArea( button->area() ) ); } if ( le.MouseClickLeft( buttonStandardGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_STANDARD ) ) { @@ -199,10 +202,12 @@ fheroes2::GameMode Game::LoadGame() else if ( le.MouseClickLeft( buttonMultiplayerGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_MULTI ) ) { return fheroes2::GameMode::LOAD_MULTI; } - else if ( le.MouseClickLeft( buttonCancel.area() ) || HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) ) { + + if ( le.MouseClickLeft( buttonCancel.area() ) || HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) ) { return fheroes2::GameMode::MAIN_MENU; } - else if ( le.isMouseRightButtonPressedInArea( buttonStandardGame.area() ) ) { + + 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() ) ) { diff --git a/src/fheroes2/game/game_mainmenu.cpp b/src/fheroes2/game/game_mainmenu.cpp index bbc206a2c5d..08f5f329b76 100644 --- a/src/fheroes2/game/game_mainmenu.cpp +++ b/src/fheroes2/game/game_mainmenu.cpp @@ -67,6 +67,7 @@ namespace { uint32_t frame; fheroes2::Button & button; + const fheroes2::Rect & buttonArea; bool isOver; bool wasOver; }; @@ -309,12 +310,14 @@ fheroes2::GameMode Game::MainMenu( const bool isFirstGameRun ) const bool isPOLPresent = conf.isPriceOfLoyaltySupported(); - std::vector buttons{ ButtonInfo{ NEWGAME_DEFAULT, buttonNewGame, false, false }, ButtonInfo{ LOADGAME_DEFAULT, buttonLoadGame, false, false }, - ButtonInfo{ HIGHSCORES_DEFAULT, buttonHighScores, false, false }, ButtonInfo{ CREDITS_DEFAULT, buttonCredits, false, false }, - ButtonInfo{ QUIT_DEFAULT, buttonQuit, false, false } }; + std::vector buttons{ ButtonInfo{ NEWGAME_DEFAULT, buttonNewGame, buttonNewGame.area(), false, false }, + ButtonInfo{ LOADGAME_DEFAULT, buttonLoadGame, buttonLoadGame.area(), false, false }, + ButtonInfo{ HIGHSCORES_DEFAULT, buttonHighScores, buttonHighScores.area(), false, false }, + ButtonInfo{ CREDITS_DEFAULT, buttonCredits, buttonCredits.area(), false, false }, + ButtonInfo{ QUIT_DEFAULT, buttonQuit, buttonQuit.area(), false, false } }; if ( isPOLPresent ) { - buttons.emplace_back( ButtonInfo{ EDITOR_DEFAULT, buttonEditor, false, false } ); + buttons.emplace_back( ButtonInfo{ EDITOR_DEFAULT, buttonEditor, buttonEditor.area(), false, false } ); } for ( size_t i = 0; le.hasMouseMoved() && i < buttons.size(); ++i ) { @@ -337,22 +340,17 @@ fheroes2::GameMode Game::MainMenu( const bool isFirstGameRun ) bool redrawScreen = false; - for ( size_t i = 0; i < buttons.size(); ++i ) { - buttons[i].wasOver = buttons[i].isOver; + for ( ButtonInfo & button : buttons ) { + button.wasOver = button.isOver; - if ( le.isMouseLeftButtonPressedInArea( buttons[i].button.area() ) ) { - buttons[i].button.drawOnPress(); - } - else { - buttons[i].button.drawOnRelease(); - } + button.button.drawOnState( le.isMouseLeftButtonPressedInArea( button.buttonArea ) ); - buttons[i].isOver = le.isMouseCursorPosInArea( buttons[i].button.area() ); + button.isOver = le.isMouseCursorPosInArea( button.buttonArea ); - if ( buttons[i].isOver != buttons[i].wasOver ) { - uint32_t frame = buttons[i].frame; + if ( button.isOver != button.wasOver ) { + uint32_t frame = button.frame; - if ( buttons[i].isOver && !buttons[i].wasOver ) + if ( button.isOver && !button.wasOver ) ++frame; if ( !redrawScreen ) { @@ -393,26 +391,35 @@ fheroes2::GameMode Game::MainMenu( const bool isFirstGameRun ) return fheroes2::GameMode::MAIN_MENU; } - else if ( isPOLPresent && ( HotKeyPressEvent( HotKeyEvent::EDITOR_MAIN_MENU ) || le.MouseClickLeft( buttonEditor.area() ) ) ) { + + if ( isPOLPresent && ( HotKeyPressEvent( HotKeyEvent::EDITOR_MAIN_MENU ) || le.MouseClickLeft( buttonEditor.area() ) ) ) { return fheroes2::GameMode::EDITOR_MAIN_MENU; } // right info - if ( le.isMouseRightButtonPressedInArea( buttonQuit.area() ) ) + if ( le.isMouseRightButtonPressedInArea( buttonQuit.area() ) ) { fheroes2::showStandardTextMessage( _( "Quit" ), _( "Quit Heroes of Might and Magic II and return to the operating system." ), Dialog::ZERO ); - else if ( le.isMouseRightButtonPressedInArea( buttonLoadGame.area() ) ) + } + else if ( le.isMouseRightButtonPressedInArea( buttonLoadGame.area() ) ) { fheroes2::showStandardTextMessage( _( "Load Game" ), _( "Load a previously saved game." ), Dialog::ZERO ); - else if ( le.isMouseRightButtonPressedInArea( buttonCredits.area() ) ) + } + else if ( le.isMouseRightButtonPressedInArea( buttonCredits.area() ) ) { fheroes2::showStandardTextMessage( _( "Credits" ), _( "View the credits screen." ), Dialog::ZERO ); - else if ( le.isMouseRightButtonPressedInArea( buttonHighScores.area() ) ) + } + else if ( le.isMouseRightButtonPressedInArea( buttonHighScores.area() ) ) { fheroes2::showStandardTextMessage( _( "High Scores" ), _( "View the high scores screen." ), Dialog::ZERO ); - else if ( le.isMouseRightButtonPressedInArea( buttonNewGame.area() ) ) + } + else if ( le.isMouseRightButtonPressedInArea( buttonNewGame.area() ) ) { fheroes2::showStandardTextMessage( _( "New Game" ), _( "Start a single or multi-player game." ), Dialog::ZERO ); + } else if ( isPOLPresent && le.isMouseRightButtonPressedInArea( buttonEditor.area() ) ) { - fheroes2::showStandardTextMessage( _( "Editor" ), _( "Create new or modify existing maps." ), Dialog::ZERO ); + { + fheroes2::showStandardTextMessage( _( "Editor" ), _( "Create new or modify existing maps." ), Dialog::ZERO ); + } } - else if ( le.isMouseRightButtonPressedInArea( settingsArea ) ) + else if ( le.isMouseRightButtonPressedInArea( settingsArea ) ) { fheroes2::showStandardTextMessage( _( "Game Settings" ), _( "Change language, resolution and settings of the game." ), Dialog::ZERO ); + } if ( validateAnimationDelay( MAIN_MENU_DELAY ) ) { const fheroes2::Sprite & lantern12 = fheroes2::AGG::GetICN( ICN::SHNGANIM, ICN::getAnimatedIcnIndex( ICN::SHNGANIM, 0, lantern_frame ) ); diff --git a/src/fheroes2/game/game_newgame.cpp b/src/fheroes2/game/game_newgame.cpp index 3907505df2f..0611b4618bf 100644 --- a/src/fheroes2/game/game_newgame.cpp +++ b/src/fheroes2/game/game_newgame.cpp @@ -202,19 +202,19 @@ fheroes2::GameMode Game::CampaignSelection() 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 buttonCancelGame( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); buttonSuccessionWars.draw(); buttonPriceOfLoyalty.draw(); - buttonCancelGame.draw(); + buttonCancel.draw(); fheroes2::Display::instance().render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonSuccessionWars.area() ) ? buttonSuccessionWars.drawOnPress() : buttonSuccessionWars.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonPriceOfLoyalty.area() ) ? buttonPriceOfLoyalty.drawOnPress() : buttonPriceOfLoyalty.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancelGame.area() ) ? buttonCancelGame.drawOnPress() : buttonCancelGame.drawOnRelease(); + buttonSuccessionWars.drawOnState( le.isMouseLeftButtonPressedInArea( buttonSuccessionWars.area() ) ); + buttonPriceOfLoyalty.drawOnState( le.isMouseLeftButtonPressedInArea( buttonPriceOfLoyalty.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); if ( le.MouseClickLeft( buttonSuccessionWars.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_NEW_ORIGINAL_CAMPAIGN ) ) { return fheroes2::GameMode::NEW_SUCCESSION_WARS_CAMPAIGN; @@ -222,7 +222,7 @@ fheroes2::GameMode Game::CampaignSelection() 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( buttonCancelGame.area() ) ) { + if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { return fheroes2::GameMode::MAIN_MENU; } @@ -230,10 +230,10 @@ fheroes2::GameMode Game::CampaignSelection() fheroes2::showStandardTextMessage( _( "Original Campaign" ), _( "Either Roland's or Archibald's campaign from the original Heroes of Might and Magic II." ), Dialog::ZERO ); } - if ( le.isMouseRightButtonPressedInArea( buttonPriceOfLoyalty.area() ) ) { + 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 ); } - if ( le.isMouseRightButtonPressedInArea( buttonCancelGame.area() ) ) { + else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } } @@ -484,32 +484,36 @@ fheroes2::GameMode Game::NewNetwork() 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 buttonCancelGame( buttonPos.x, buttonPos.y + buttonYStep * 2, ICN::BTNMP, 8, 9 ); + fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 2, ICN::BTNMP, 8, 9 ); buttonHost.draw(); buttonGuest.draw(); - buttonCancelGame.draw(); + buttonCancel.draw(); fheroes2::Display::instance().render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonHost.area() ) ? buttonHost.drawOnPress() : buttonHost.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonGuest.area() ) ? buttonGuest.drawOnPress() : buttonGuest.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancelGame.area() ) ? buttonCancelGame.drawOnPress() : buttonCancelGame.drawOnRelease(); + buttonHost.drawOnState( le.isMouseLeftButtonPressedInArea( buttonHost.area() ) ); + buttonGuest.drawOnState( le.isMouseLeftButtonPressedInArea( buttonGuest.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); - if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancelGame.area() ) ) + if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { return fheroes2::GameMode::MAIN_MENU; + } // right info - if ( le.isMouseRightButtonPressedInArea( buttonHost.area() ) ) + 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 ); - if ( le.isMouseRightButtonPressedInArea( buttonGuest.area() ) ) + } + 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 ); - if ( le.isMouseRightButtonPressedInArea( buttonCancelGame.area() ) ) + } + else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); + } } return fheroes2::GameMode::MAIN_MENU; @@ -539,7 +543,7 @@ fheroes2::GameMode Game::NewGame( const bool isProbablyDemoVersion ) 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::BTNBATTLEONLY, 0, 1 ); fheroes2::Button buttonSettings( buttonPos.x, buttonPos.y + buttonYStep * 4, ICN::BUTTON_LARGE_CONFIG, 0, 1 ); - fheroes2::Button buttonCancelGame( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); if ( !isSuccessionWarsCampaignPresent() ) { buttonCampaignGame.disable(); @@ -550,7 +554,7 @@ fheroes2::GameMode Game::NewGame( const bool isProbablyDemoVersion ) buttonMultiGame.draw(); buttonBattleGame.draw(); buttonSettings.draw(); - buttonCancelGame.draw(); + buttonCancel.draw(); fheroes2::validateFadeInAndRender(); @@ -565,45 +569,54 @@ fheroes2::GameMode Game::NewGame( const bool isProbablyDemoVersion ) } while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonStandardGame.area() ) ? buttonStandardGame.drawOnPress() : buttonStandardGame.drawOnRelease(); - + buttonStandardGame.drawOnState( le.isMouseLeftButtonPressedInArea( buttonStandardGame.area() ) ); if ( buttonCampaignGame.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonCampaignGame.area() ) ? buttonCampaignGame.drawOnPress() : buttonCampaignGame.drawOnRelease(); + buttonCampaignGame.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCampaignGame.area() ) ); } - le.isMouseLeftButtonPressedInArea( buttonMultiGame.area() ) ? buttonMultiGame.drawOnPress() : buttonMultiGame.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonBattleGame.area() ) ? buttonBattleGame.drawOnPress() : buttonBattleGame.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonSettings.area() ) ? buttonSettings.drawOnPress() : buttonSettings.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancelGame.area() ) ? buttonCancelGame.drawOnPress() : buttonCancelGame.drawOnRelease(); + buttonMultiGame.drawOnState( le.isMouseLeftButtonPressedInArea( buttonMultiGame.area() ) ); + buttonBattleGame.drawOnState( le.isMouseLeftButtonPressedInArea( buttonBattleGame.area() ) ); + buttonSettings.drawOnState( le.isMouseLeftButtonPressedInArea( buttonSettings.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); - if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_STANDARD ) || le.MouseClickLeft( buttonStandardGame.area() ) ) + 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 ( 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() ) ) + } + 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( buttonCancelGame.area() ) ) + if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { return fheroes2::GameMode::MAIN_MENU; - - if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_BATTLEONLY ) || le.MouseClickLeft( buttonBattleGame.area() ) ) + } + if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_BATTLEONLY ) || le.MouseClickLeft( buttonBattleGame.area() ) ) { return fheroes2::GameMode::NEW_BATTLE_ONLY; + } - if ( le.isMouseRightButtonPressedInArea( buttonStandardGame.area() ) ) + 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() ) ) + } + 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() ) ) + } + 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() ) ) + } + 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() ) ) + } + else if ( le.isMouseRightButtonPressedInArea( buttonSettings.area() ) ) { fheroes2::showStandardTextMessage( _( "Game Settings" ), _( "Change language, resolution and settings of the game." ), Dialog::ZERO ); - else if ( le.isMouseRightButtonPressedInArea( buttonCancelGame.area() ) ) + } + else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); + } } return fheroes2::GameMode::QUIT_GAME; @@ -624,10 +637,10 @@ fheroes2::GameMode Game::NewMulti() 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 buttonCancelGame( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); + fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); buttonHotSeat.draw(); - buttonCancelGame.draw(); + buttonCancel.draw(); buttonNetwork.disable(); fheroes2::Display::instance().render(); @@ -635,22 +648,29 @@ fheroes2::GameMode Game::NewMulti() LocalEvent & le = LocalEvent::Get(); // newgame loop while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonHotSeat.area() ) ? buttonHotSeat.drawOnPress() : buttonHotSeat.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancelGame.area() ) ? buttonCancelGame.drawOnPress() : buttonCancelGame.drawOnRelease(); + buttonHotSeat.drawOnState( le.isMouseLeftButtonPressedInArea( buttonHotSeat.area() ) ); + if ( buttonNetwork.isEnabled() ) { + buttonNetwork.drawOnState( le.isMouseLeftButtonPressedInArea( buttonNetwork.area() ) ); + } + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); - if ( le.MouseClickLeft( buttonHotSeat.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_HOTSEAT ) ) + if ( le.MouseClickLeft( buttonHotSeat.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_HOTSEAT ) ) { return fheroes2::GameMode::NEW_HOT_SEAT; - if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancelGame.area() ) ) + } + if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { return fheroes2::GameMode::MAIN_MENU; + } // right info - if ( le.isMouseRightButtonPressedInArea( buttonHotSeat.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 ); - if ( le.isMouseRightButtonPressedInArea( buttonCancelGame.area() ) ) + } + else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); + } } return fheroes2::GameMode::QUIT_GAME; @@ -682,41 +702,51 @@ uint8_t Game::SelectCountPlayers() LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( button2Players.area() ) ? button2Players.drawOnPress() : button2Players.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( button3Players.area() ) ? button3Players.drawOnPress() : button3Players.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( button4Players.area() ) ? button4Players.drawOnPress() : button4Players.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( button5Players.area() ) ? button5Players.drawOnPress() : button5Players.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( button6Players.area() ) ? button6Players.drawOnPress() : button6Players.drawOnRelease(); - - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); - - if ( le.MouseClickLeft( button2Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_2 ) ) + button2Players.drawOnState( le.isMouseLeftButtonPressedInArea( button2Players.area() ) ); + button3Players.drawOnState( le.isMouseLeftButtonPressedInArea( button3Players.area() ) ); + button4Players.drawOnState( le.isMouseLeftButtonPressedInArea( button4Players.area() ) ); + button5Players.drawOnState( le.isMouseLeftButtonPressedInArea( button5Players.area() ) ); + button6Players.drawOnState( le.isMouseLeftButtonPressedInArea( button6Players.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( 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 ) ) + } + if ( le.MouseClickLeft( button3Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_3 ) ) { return 3; - if ( le.MouseClickLeft( button4Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_4 ) ) + } + if ( le.MouseClickLeft( button4Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_4 ) ) { return 4; - if ( le.MouseClickLeft( button5Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_5 ) ) + } + if ( le.MouseClickLeft( button5Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_5 ) ) { return 5; - if ( le.MouseClickLeft( button6Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_6 ) ) + } + if ( le.MouseClickLeft( button6Players.area() ) || le.isKeyPressed( fheroes2::Key::KEY_6 ) ) { return 6; - - if ( HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) + } + if ( HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { return 0; + } // right info - if ( le.isMouseRightButtonPressedInArea( button2Players.area() ) ) + if ( le.isMouseRightButtonPressedInArea( button2Players.area() ) ) { fheroes2::showStandardTextMessage( _( "2 Players" ), _( "Play with 2 human players, and optionally, up to 4 additional computer players." ), Dialog::ZERO ); - if ( le.isMouseRightButtonPressedInArea( button3Players.area() ) ) + } + 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 ); - if ( le.isMouseRightButtonPressedInArea( button4Players.area() ) ) + } + 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 ); - if ( le.isMouseRightButtonPressedInArea( button5Players.area() ) ) + } + 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 ); - if ( le.isMouseRightButtonPressedInArea( button6Players.area() ) ) + } + else if ( le.isMouseRightButtonPressedInArea( button6Players.area() ) ) { fheroes2::showStandardTextMessage( _( "6 Players" ), _( "Play with 6 human players." ), Dialog::ZERO ); - if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) + } + else if ( le.isMouseRightButtonPressedInArea( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); + } } return 0; diff --git a/src/fheroes2/game/game_scenarioinfo.cpp b/src/fheroes2/game/game_scenarioinfo.cpp index 32a41cda2e9..41530d71ac4 100644 --- a/src/fheroes2/game/game_scenarioinfo.cpp +++ b/src/fheroes2/game/game_scenarioinfo.cpp @@ -312,9 +312,9 @@ namespace } // press button - le.isMouseLeftButtonPressedInArea( buttonSelectMaps.area() ) ? buttonSelectMaps.drawOnPress() : buttonSelectMaps.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); + buttonSelectMaps.drawOnState( le.isMouseLeftButtonPressedInArea( buttonSelectMaps.area() ) ); + buttonOk.drawOnState( le.isMouseLeftButtonPressedInArea( buttonOk.area() ) ); + buttonCancel.drawOnState( le.isMouseLeftButtonPressedInArea( buttonCancel.area() ) ); // click select if ( HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_SELECT_MAP ) || le.MouseClickLeft( buttonSelectMaps.area() ) ) { @@ -350,7 +350,8 @@ namespace result = fheroes2::GameMode::MAIN_MENU; break; } - else if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOk.area() ) ) { + + if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOk.area() ) ) { DEBUG_LOG( DBG_GAME, DBG_INFO, "select maps: " << conf.getCurrentMapInfo().filename << ", difficulty: " << Difficulty::String( Game::getDifficulty() ) ) result = fheroes2::GameMode::START_GAME; @@ -358,7 +359,8 @@ namespace fheroes2::fadeOutDisplay(); break; } - else if ( le.MouseClickLeft( roi ) ) { + + if ( le.MouseClickLeft( roi ) ) { const int32_t index = GetRectIndex( coordDifficulty, le.getMouseCursorPos() ); // select difficulty diff --git a/src/fheroes2/gui/interface_list.h b/src/fheroes2/gui/interface_list.h index 990854989bf..44980d3ac01 100644 --- a/src/fheroes2/gui/interface_list.h +++ b/src/fheroes2/gui/interface_list.h @@ -57,11 +57,11 @@ namespace Interface public: explicit ListBox( const fheroes2::Point & pt = fheroes2::Point() ) : ptRedraw( pt ) - , _timedButtonPgUp( [this]() { return buttonPgUp.isPressed(); } ) - , _timedButtonPgDn( [this]() { return buttonPgDn.isPressed(); } ) + , _timedButtonPgUp( [this]() { return _buttonPgUp.isPressed(); } ) + , _timedButtonPgDn( [this]() { return _buttonPgDn.isPressed(); } ) { - buttonPgUp.subscribe( &_timedButtonPgUp ); - buttonPgDn.subscribe( &_timedButtonPgDn ); + _buttonPgUp.subscribe( &_timedButtonPgUp ); + _buttonPgDn.subscribe( &_timedButtonPgDn ); } ~ListBox() override = default; @@ -102,14 +102,14 @@ namespace Interface void SetScrollButtonUp( int icn, uint32_t index1, uint32_t index2, const fheroes2::Point & pos ) { - buttonPgUp.setICNInfo( icn, index1, index2 ); - buttonPgUp.setPosition( pos.x, pos.y ); + _buttonPgUp.setICNInfo( icn, index1, index2 ); + _buttonPgUp.setPosition( pos.x, pos.y ); } void SetScrollButtonDn( int icn, uint32_t index1, uint32_t index2, const fheroes2::Point & pos ) { - buttonPgDn.setICNInfo( icn, index1, index2 ); - buttonPgDn.setPosition( pos.x, pos.y ); + _buttonPgDn.setICNInfo( icn, index1, index2 ); + _buttonPgDn.setPosition( pos.x, pos.y ); } void setScrollBarArea( const fheroes2::Rect & area ) @@ -176,8 +176,8 @@ namespace Interface RedrawBackground( ptRedraw ); - buttonPgUp.draw(); - buttonPgDn.draw(); + _buttonPgUp.draw(); + _buttonPgDn.draw(); _scrollbar.redraw(); Verify(); // reset values if they are wrong @@ -334,8 +334,8 @@ namespace Interface { LocalEvent & le = LocalEvent::Get(); - le.isMouseLeftButtonPressedInArea( buttonPgUp.area() ) ? buttonPgUp.drawOnPress() : buttonPgUp.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonPgDn.area() ) ? buttonPgDn.drawOnPress() : buttonPgDn.drawOnRelease(); + _buttonPgUp.drawOnState( le.isMouseLeftButtonPressedInArea( _buttonPgUp.area() ) ); + _buttonPgDn.drawOnState( le.isMouseLeftButtonPressedInArea( _buttonPgDn.area() ) ); if ( !IsValid() ) { return false; @@ -407,7 +407,7 @@ namespace Interface return true; } - if ( ( le.MouseClickLeft( buttonPgUp.area() ) || le.isMouseWheelUpInArea( rtAreaItems ) || le.isMouseWheelUpInArea( _scrollbar.getArea() ) + if ( ( le.MouseClickLeft( _buttonPgUp.area() ) || le.isMouseWheelUpInArea( rtAreaItems ) || le.isMouseWheelUpInArea( _scrollbar.getArea() ) || _timedButtonPgUp.isDelayPassed() ) && ( _topId > 0 ) ) { needRedraw = true; @@ -417,7 +417,7 @@ namespace Interface return true; } - if ( ( le.MouseClickLeft( buttonPgDn.area() ) || le.isMouseWheelDownInArea( rtAreaItems ) || le.isMouseWheelDownInArea( _scrollbar.getArea() ) + if ( ( le.MouseClickLeft( _buttonPgDn.area() ) || le.isMouseWheelDownInArea( rtAreaItems ) || le.isMouseWheelDownInArea( _scrollbar.getArea() ) || _timedButtonPgDn.isDelayPassed() ) && ( _topId + maxItems < _size() ) ) { needRedraw = true; @@ -527,8 +527,8 @@ namespace Interface fheroes2::Rect rtAreaItems; - fheroes2::Button buttonPgUp; - fheroes2::Button buttonPgDn; + fheroes2::Button _buttonPgUp; + fheroes2::Button _buttonPgDn; fheroes2::Scrollbar _scrollbar; diff --git a/src/fheroes2/gui/ui_button.cpp b/src/fheroes2/gui/ui_button.cpp index c8443070f32..887c6001397 100644 --- a/src/fheroes2/gui/ui_button.cpp +++ b/src/fheroes2/gui/ui_button.cpp @@ -292,6 +292,8 @@ namespace fheroes2 { _isEnabled = true; notifySubscriber(); + + _updateReleasedArea(); } void ButtonBase::disable() @@ -299,6 +301,8 @@ namespace fheroes2 _isEnabled = false; _isPressed = false; // button can't be disabled and pressed notifySubscriber(); + + _updateReleasedArea(); } void ButtonBase::show() @@ -322,11 +326,11 @@ namespace fheroes2 if ( isPressed() ) { // button can't be disabled and pressed const Sprite & sprite = _getPressed(); - Blit( sprite, output, _offsetX + sprite.x(), _offsetY + sprite.y() ); + Blit( sprite, output, _areaPressed ); } else { const Sprite & sprite = isEnabled() ? _getReleased() : _getDisabled(); - Blit( sprite, output, _offsetX + sprite.x(), _offsetY + sprite.y() ); + Blit( sprite, output, _areaReleased ); } return true; @@ -368,12 +372,6 @@ namespace fheroes2 return true; } - Rect ButtonBase::area() const - { - const Sprite & sprite = isPressed() ? _getPressed() : _getReleased(); - return { _offsetX + sprite.x(), _offsetY + sprite.y(), sprite.width(), sprite.height() }; - } - const Sprite & ButtonBase::_getDisabled() const { const Sprite & sprite = _getReleased(); diff --git a/src/fheroes2/gui/ui_button.h b/src/fheroes2/gui/ui_button.h index 008f6a68568..f0ee5667ab8 100644 --- a/src/fheroes2/gui/ui_button.h +++ b/src/fheroes2/gui/ui_button.h @@ -97,6 +97,11 @@ namespace fheroes2 void setPosition( const int32_t offsetX, const int32_t offsetY ) { + _areaPressed.x += offsetX - _offsetX; + _areaReleased.x += offsetX - _offsetX; + _areaPressed.y += offsetY - _offsetY; + _areaReleased.y += offsetY - _offsetY; + _offsetX = offsetX; _offsetY = offsetY; } @@ -120,21 +125,45 @@ namespace fheroes2 return drawOnRelease( output ); } - Rect area() const; + const Rect & area() const + { + return isPressed() ? _areaPressed : _areaReleased; + } protected: virtual const Sprite & _getPressed() const = 0; virtual const Sprite & _getReleased() const = 0; virtual const Sprite & _getDisabled() const; + void _updateButtonAreas() + { + _updatePressedArea(); + _updateReleasedArea(); + } + private: int32_t _offsetX{ 0 }; int32_t _offsetY{ 0 }; + Rect _areaPressed{ _offsetX, _offsetY, 0, 0 }; + Rect _areaReleased{ _offsetX, _offsetY, 0, 0 }; + bool _isPressed{ false }; bool _isEnabled{ true }; bool _isVisible{ true }; + void _updatePressedArea() + { + const Sprite & pressed = _getPressed(); + _areaPressed = { _offsetX + pressed.x(), _offsetY + pressed.y(), pressed.width(), pressed.height() }; + } + + void _updateReleasedArea() + { + const Sprite & released = isEnabled() ? _getReleased() : _getDisabled(); + _areaReleased = { _offsetX + released.x(), _offsetY + released.y(), released.width(), released.height() }; + } + mutable const Sprite * _releasedSprite = nullptr; mutable std::unique_ptr _disabledSprite; }; @@ -154,7 +183,7 @@ namespace fheroes2 , _releasedIndex( releasedIndex ) , _pressedIndex( pressedIndex ) { - // Do nothing. + _updateButtonAreas(); } ~Button() override = default; @@ -164,12 +193,16 @@ namespace fheroes2 _icnId = icnId; _releasedIndex = releasedIndex; _pressedIndex = pressedIndex; + + _updateButtonAreas(); } void setICNIndexes( const uint32_t releasedIndex, const uint32_t pressedIndex ) { _releasedIndex = releasedIndex; _pressedIndex = pressedIndex; + + _updateButtonAreas(); } protected: @@ -198,7 +231,7 @@ namespace fheroes2 , _pressed( std::move( pressed ) ) , _disabled( std::move( disabled ) ) { - // Do nothing. + _updateButtonAreas(); } ButtonSprite( const ButtonSprite & ) = delete; @@ -214,6 +247,8 @@ namespace fheroes2 _released = released; _pressed = pressed; _disabled = disabled; + + _updateButtonAreas(); } protected: diff --git a/src/fheroes2/gui/ui_keyboard.cpp b/src/fheroes2/gui/ui_keyboard.cpp index e12b137fa13..3853e1e50bd 100644 --- a/src/fheroes2/gui/ui_keyboard.cpp +++ b/src/fheroes2/gui/ui_keyboard.cpp @@ -662,6 +662,7 @@ namespace int32_t xOffset = offset.x + offsets[i]; for ( auto & buttonInfo : buttonLayout[i] ) { buttonInfo.button.setPosition( xOffset, yOffset ); + if ( buttonInfo.isInvertedRenderingLogic ) { buttonInfo.button.press(); } @@ -694,10 +695,10 @@ namespace for ( auto & buttonRow : buttonLayout ) { for ( auto & buttonInfo : buttonRow ) { if ( buttonInfo.isInvertedRenderingLogic ) { - le.isMouseLeftButtonPressedInArea( buttonInfo.button.area() ) ? buttonInfo.button.drawOnRelease() : buttonInfo.button.drawOnPress(); + buttonInfo.button.drawOnState( !le.isMouseLeftButtonPressedInArea( buttonInfo.button.area() ) ); } else { - le.isMouseLeftButtonPressedInArea( buttonInfo.button.area() ) ? buttonInfo.button.drawOnPress() : buttonInfo.button.drawOnRelease(); + buttonInfo.button.drawOnState( le.isMouseLeftButtonPressedInArea( buttonInfo.button.area() ) ); } } } diff --git a/src/fheroes2/heroes/heroes_dialog.cpp b/src/fheroes2/heroes/heroes_dialog.cpp index 23ec629798f..20dbb68eaa1 100644 --- a/src/fheroes2/heroes/heroes_dialog.cpp +++ b/src/fheroes2/heroes/heroes_dialog.cpp @@ -347,32 +347,39 @@ int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disable // Hero dismiss button. dst_pt.x = dialogRoi.x + 9; dst_pt.y = dialogRoi.y + 378; - const fheroes2::Sprite & dismissReleased = fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_DISMISS, 0 ); - fheroes2::ButtonSprite buttonDismiss( dst_pt.x, dst_pt.y - dismissReleased.height() / 2, dismissReleased, fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_DISMISS, 1 ), - fheroes2::AGG::GetICN( ICN::DISMISS_HERO_DISABLED_BUTTON, 0 ) ); - if ( inCastle() || readonly || disableDismiss || Modes( NOTDISMISS ) ) { - buttonDismiss.disable(); - } + std::unique_ptr buttonDismiss; + + if ( !isEditor && !readonly && !disableDismiss ) { + const fheroes2::Sprite & dismissReleased = fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_DISMISS, 0 ); + buttonDismiss = std::make_unique( dst_pt.x, dst_pt.y - dismissReleased.height() / 2, dismissReleased, + fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_DISMISS, 1 ), + fheroes2::AGG::GetICN( ICN::DISMISS_HERO_DISABLED_BUTTON, 0 ) ); + + if ( inCastle() || readonly || disableDismiss || Modes( NOTDISMISS ) ) { + buttonDismiss->disable(); + } - if ( isEditor || readonly || disableDismiss ) { - buttonDismiss.hide(); - } - else { fheroes2::addGradientShadow( dismissReleased, display, { dst_pt.x, dst_pt.y - dismissReleased.height() / 2 }, { -3, 5 } ); + + buttonDismiss->draw(); } // Hero Patrol mode button (used in Editor). - const fheroes2::Sprite & patrolReleased = fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_PATROL, 0 ); - fheroes2::ButtonSprite buttonPatrol( dst_pt.x, dst_pt.y - patrolReleased.height() / 2, patrolReleased, fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_PATROL, 1 ) ); + std::unique_ptr buttonPatrol; + if ( isEditor ) { + const fheroes2::Sprite & patrolReleased = fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_PATROL, 0 ); + buttonPatrol = std::make_unique( dst_pt.x, dst_pt.y - patrolReleased.height() / 2, patrolReleased, + fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_PATROL, 1 ) ); + fheroes2::addGradientShadow( patrolReleased, display, { dialogRoi.x + 9, dst_pt.y - patrolReleased.height() / 2 }, { -3, 5 } ); + if ( Modes( PATROL ) ) { - buttonPatrol.press(); + buttonPatrol->press(); } - } - else { - buttonPatrol.hide(); + + buttonPatrol->draw(); } // Exit button. @@ -389,8 +396,6 @@ int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disable buttonPrevHero.draw(); buttonNextHero.draw(); - buttonDismiss.draw(); - buttonPatrol.draw(); buttonExit.draw(); // Fade-in hero dialog. @@ -413,7 +418,7 @@ int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disable // dialog menu loop while ( le.HandleEvents() ) { // Exit this dialog. - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { // Exit the dialog handling loop to close it. @@ -455,15 +460,10 @@ int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disable } // Dismiss hero. - else if ( buttonDismiss.isEnabled() && buttonDismiss.isVisible() ) { - if ( le.isMouseLeftButtonPressedInArea( buttonDismiss.area() ) || HotKeyPressEvent( Game::HotKeyEvent::ARMY_DISMISS ) ) { - buttonDismiss.drawOnPress(); - } - else { - buttonDismiss.drawOnRelease(); - } + else if ( buttonDismiss && buttonDismiss->isEnabled() ) { + buttonDismiss->drawOnState( ( le.isMouseLeftButtonPressedInArea( buttonDismiss->area() ) || HotKeyPressEvent( Game::HotKeyEvent::ARMY_DISMISS ) ) ); - if ( ( le.MouseClickLeft( buttonDismiss.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::ARMY_DISMISS ) ) + if ( ( le.MouseClickLeft( buttonDismiss->area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::ARMY_DISMISS ) ) && Dialog::YES == fheroes2::showStandardTextMessage( GetName(), _( "Are you sure you want to dismiss this Hero?" ), Dialog::YES | Dialog::NO ) ) { // Fade-out hero dialog. fheroes2::fadeOutDisplay( dialogRoi, !isDefaultScreenSize ); @@ -474,7 +474,7 @@ int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disable // Previous hero. if ( buttonPrevHero.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonPrevHero.area() ) ? buttonPrevHero.drawOnPress() : buttonPrevHero.drawOnRelease(); + buttonPrevHero.drawOnState( le.isMouseLeftButtonPressedInArea( buttonPrevHero.area() ) ); if ( le.MouseClickLeft( buttonPrevHero.area() ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) || timedButtonPrevHero.isDelayPassed() ) { return Dialog::PREV; } @@ -482,7 +482,7 @@ int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disable // Next hero. if ( buttonNextHero.isEnabled() ) { - le.isMouseLeftButtonPressedInArea( buttonNextHero.area() ) ? buttonNextHero.drawOnPress() : buttonNextHero.drawOnRelease(); + buttonNextHero.drawOnState( le.isMouseLeftButtonPressedInArea( buttonNextHero.area() ) ); if ( le.MouseClickLeft( buttonNextHero.area() ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) || timedButtonNextHero.isDelayPassed() ) { return Dialog::NEXT; } @@ -605,24 +605,24 @@ int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disable Dialog::QuickInfo( *this, true ); } } - else if ( buttonPatrol.isVisible() && le.isMouseCursorPosInArea( buttonPatrol.area() ) ) { - if ( le.isMouseLeftButtonPressed() && buttonPatrol.isReleased() && !Modes( PATROL ) ) { - buttonPatrol.drawOnPress(); + else if ( buttonPatrol && le.isMouseCursorPosInArea( buttonPatrol->area() ) ) { + if ( le.isMouseLeftButtonPressed() && buttonPatrol->isReleased() && !Modes( PATROL ) ) { + buttonPatrol->drawOnPress(); int32_t value = static_cast( _patrolDistance ); if ( Dialog::SelectCount( _( "Set patrol radius in tiles" ), 0, 255, value ) ) { SetModes( PATROL ); _patrolDistance = static_cast( value ); } else { - buttonPatrol.drawOnRelease(); + buttonPatrol->drawOnRelease(); ResetModes( PATROL ); } } - else if ( le.MouseClickLeft() && buttonPatrol.isPressed() && Modes( PATROL ) ) { + else if ( le.MouseClickLeft() && buttonPatrol->isPressed() && Modes( PATROL ) ) { ResetModes( PATROL ); } if ( !Modes( PATROL ) ) { - buttonPatrol.drawOnRelease(); + buttonPatrol->drawOnRelease(); } } else if ( isEditor ) { @@ -699,7 +699,7 @@ int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disable if ( le.isMouseCursorPosInArea( buttonExit.area() ) ) { message = _( "Exit Hero Screen" ); } - else if ( buttonDismiss.isVisible() && le.isMouseCursorPosInArea( buttonDismiss.area() ) ) { + else if ( buttonDismiss && le.isMouseCursorPosInArea( buttonDismiss->area() ) ) { if ( inCastle() ) { message = _( "You cannot dismiss a hero in a castle" ); } @@ -708,7 +708,7 @@ int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disable StringReplace( message, "%{name}", name ); StringReplace( message, "%{race}", Race::String( _race ) ); } - else if ( buttonDismiss.isEnabled() ) { + else if ( buttonDismiss->isEnabled() ) { message = _( "Dismiss %{name} the %{race}" ); StringReplace( message, "%{name}", name ); StringReplace( message, "%{race}", Race::String( _race ) ); @@ -746,8 +746,8 @@ int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disable else if ( le.isMouseCursorPosInArea( titleRoi ) ) { message = _( "Click to change hero's name. Right-click to reset to default." ); } - else if ( le.isMouseCursorPosInArea( buttonPatrol.area() ) ) { - if ( buttonPatrol.isPressed() ) { + else if ( buttonPatrol && le.isMouseCursorPosInArea( buttonPatrol->area() ) ) { + if ( buttonPatrol->isPressed() ) { message = _( "Hero is in patrol mode in %{tiles} tiles radius. Click to disable it." ); StringReplace( message, "%{tiles}", _patrolDistance ); } diff --git a/src/fheroes2/heroes/heroes_meeting.cpp b/src/fheroes2/heroes/heroes_meeting.cpp index b08f7917c61..c43e3c2ad7c 100644 --- a/src/fheroes2/heroes/heroes_meeting.cpp +++ b/src/fheroes2/heroes/heroes_meeting.cpp @@ -418,7 +418,7 @@ void Heroes::MeetingDialog( Heroes & otherHero ) // message loop while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); if ( le.isMouseLeftButtonPressedInArea( moveArmyToHero2.area() ) || HotKeyHoldEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) ) { moveArmyToHero2.drawOnPress(); diff --git a/src/fheroes2/kingdom/kingdom_overview.cpp b/src/fheroes2/kingdom/kingdom_overview.cpp index 469377a98bf..2cbca4a9b88 100644 --- a/src/fheroes2/kingdom/kingdom_overview.cpp +++ b/src/fheroes2/kingdom/kingdom_overview.cpp @@ -848,7 +848,7 @@ void Kingdom::openOverviewDialog() // dialog menu loop while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); if ( le.isMouseRightButtonPressedInArea( buttonHeroes.area() ) ) { fheroes2::showStandardTextMessage( _( "Heroes" ), _( "View Heroes." ), Dialog::ZERO ); diff --git a/src/fheroes2/kingdom/puzzle.cpp b/src/fheroes2/kingdom/puzzle.cpp index 99430d166b4..ff4c64e3ec1 100644 --- a/src/fheroes2/kingdom/puzzle.cpp +++ b/src/fheroes2/kingdom/puzzle.cpp @@ -120,7 +120,7 @@ namespace int alpha = 250; while ( alpha >= 0 && le.HandleEvents( Game::isDelayNeeded( delayTypes ) ) ) { - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); // If exit button was pressed before reveal animation is finished, return true to indicate early exit. if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { return true; @@ -177,6 +177,7 @@ namespace fheroes2::Button buttonExit( radarArea.x + 32, radarArea.y + radarArea.height - 37, ( isEvilInterface ? ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_EVIL : ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_GOOD ), 0, 1 ); + buttonExit.draw(); drawPuzzle( pzl, sf, fheroes2::borderWidthPx, fheroes2::borderWidthPx ); @@ -190,7 +191,7 @@ namespace LocalEvent & le = LocalEvent::Get(); while ( !earlyExit && le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } @@ -268,7 +269,7 @@ namespace LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() && !earlyExit ) { - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } diff --git a/src/fheroes2/kingdom/view_world.cpp b/src/fheroes2/kingdom/view_world.cpp index 0e9a8a45c4d..0b046dfb8cf 100644 --- a/src/fheroes2/kingdom/view_world.cpp +++ b/src/fheroes2/kingdom/view_world.cpp @@ -651,12 +651,14 @@ void ViewWorld::ViewWorldWindow( const int32_t color, const ViewWorldMode mode, // Zoom button const fheroes2::Point buttonZoomPosition( display.width() - fheroes2::radarWidthPx + 16, 2 * fheroes2::borderWidthPx + fheroes2::radarWidthPx + 128 ); fheroes2::Button buttonZoom( buttonZoomPosition.x, buttonZoomPosition.y, ( isEvilInterface ? ICN::LGNDXTRE : ICN::LGNDXTRA ), 0, 1 ); + buttonZoom.draw(); // Exit button const fheroes2::Point buttonExitPosition( display.width() - fheroes2::radarWidthPx + 16, 2 * fheroes2::borderWidthPx + fheroes2::radarWidthPx + 236 ); fheroes2::Button buttonExit( buttonExitPosition.x, buttonExitPosition.y, ( isEvilInterface ? ICN::BUTTON_VIEWWORLD_EXIT_EVIL : ICN::BUTTON_VIEWWORLD_EXIT_GOOD ), 0, 1 ); + buttonExit.draw(); // Fade-in View World screen. @@ -678,8 +680,8 @@ void ViewWorld::ViewWorldWindow( const int32_t color, const ViewWorldMode mode, // message loop LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { - le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); - le.isMouseLeftButtonPressedInArea( buttonZoom.area() ) ? buttonZoom.drawOnPress() : buttonZoom.drawOnRelease(); + buttonExit.drawOnState( le.isMouseLeftButtonPressedInArea( buttonExit.area() ) ); + buttonZoom.drawOnState( le.isMouseLeftButtonPressedInArea( buttonZoom.area() ) ); bool changed = false; From fc979dc19ae51f1af013fec43480e1e3704e17ec Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Fri, 6 Dec 2024 08:04:30 +0800 Subject: [PATCH 20/30] Add range values for default experience (#9318) --- src/fheroes2/heroes/heroes_indicator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fheroes2/heroes/heroes_indicator.cpp b/src/fheroes2/heroes/heroes_indicator.cpp index 9a0202d114d..d86f0b94cff 100644 --- a/src/fheroes2/heroes/heroes_indicator.cpp +++ b/src/fheroes2/heroes/heroes_indicator.cpp @@ -202,7 +202,8 @@ void ExperienceIndicator::Redraw() const const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::HSICONS, 1 ); fheroes2::Blit( sprite, display, _area.x, _area.y ); - const fheroes2::Text text( _isDefault ? "-" : std::to_string( _hero->GetExperience() ), fheroes2::FontType::smallWhite() ); + // For the default range of experience see Heroes::GetStartingXp() method. + const fheroes2::Text text( _isDefault ? "40-90" : std::to_string( _hero->GetExperience() ), fheroes2::FontType::smallWhite() ); text.draw( _area.x + 17 - text.width() / 2, _area.y + 25, display ); } From 36ed19b8297db852a7a0d36cc70a5afd46f54ea8 Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Sat, 7 Dec 2024 05:13:10 +0300 Subject: [PATCH 21/30] Modernize the clang-format config file syntax (#9320) --- .clang-format | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.clang-format b/.clang-format index 67d34cee8c9..28447e4012b 100644 --- a/.clang-format +++ b/.clang-format @@ -1,8 +1,10 @@ --- AccessModifierOffset: -4 AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false +AlignConsecutiveAssignments: + Enabled: false +AlignConsecutiveDeclarations: + Enabled: false AlignEscapedNewlinesLeft: Right AlignOperands: true AlignTrailingComments: false From 2a2cc74350c38b5ea9f4f15b5ab1de517f89fd89 Mon Sep 17 00:00:00 2001 From: big4billy <143273129+big4billy@users.noreply.github.com> Date: Sun, 8 Dec 2024 02:43:58 +0100 Subject: [PATCH 22/30] Hungarian language update (#9321) --- files/lang/hu.po | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/files/lang/hu.po b/files/lang/hu.po index 1d08004ee71..fc04e4737da 100644 --- a/files/lang/hu.po +++ b/files/lang/hu.po @@ -6187,12 +6187,14 @@ msgid "Keyboard|ABC" msgstr "ABC" msgid "The entered value is invalid." -msgstr "" +msgstr "A megadott érték helytelen." msgid "" "The entered value is out of range.\n" "It should be not less than %{minValue} and not more than %{maxValue}." msgstr "" +"A megadható érték nem lehet kevesebb, mint %{minValue} és nem lehet több, mint %{maxValue},\n" +"de a megadott érték ezen a tartományon kívül van." msgid "Kingdom Income" msgstr "Királyság jövedelme" @@ -6257,7 +6259,7 @@ msgid "Dutch" msgstr "holland" msgid "Hungarian" -msgstr "magyar" +msgstr "Magyar" msgid "Danish" msgstr "dán" @@ -8691,10 +8693,10 @@ msgid "speed|Instant" msgstr "Itt terem" msgid "Click this button to adjust the level of zoom." -msgstr "" +msgstr "Nyomd meg ezt a gombot a nagyítás mértékének beállításához." msgid "Zoom" -msgstr "" +msgstr "Nagyítás" msgid "week|Squirrel" msgstr "Mókus" From 5ce77ced7fd7b5e62e11e3541afb49de0c473e69 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 17:03:16 +0800 Subject: [PATCH 23/30] Update translation files (#9329) --- docs/json/lang_hu.json | 2 +- files/lang/hu.po | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/json/lang_hu.json b/docs/json/lang_hu.json index ab7ba1517a6..2f1e48e51de 100644 --- a/docs/json/lang_hu.json +++ b/docs/json/lang_hu.json @@ -1 +1 @@ -{"schemaVersion":1,"label":"Hungarian","message":"99%","color":"green"} +{"schemaVersion":1,"label":"Hungarian","message":"100%","color":"green"} diff --git a/files/lang/hu.po b/files/lang/hu.po index fc04e4737da..0d31b69e364 100644 --- a/files/lang/hu.po +++ b/files/lang/hu.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-08 01:56+0000\n" "PO-Revision-Date: 2023-09-04 21:55+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Hungarian \n" @@ -6193,7 +6193,8 @@ msgid "" "The entered value is out of range.\n" "It should be not less than %{minValue} and not more than %{maxValue}." msgstr "" -"A megadható érték nem lehet kevesebb, mint %{minValue} és nem lehet több, mint %{maxValue},\n" +"A megadható érték nem lehet kevesebb, mint %{minValue} és nem lehet több, " +"mint %{maxValue},\n" "de a megadott érték ezen a tartományon kívül van." msgid "Kingdom Income" From 2804353ac3520742bcc04f6644b2965101de3768 Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Sun, 8 Dec 2024 16:26:36 +0300 Subject: [PATCH 24/30] Use a separate directory for binaries when building using GNU Make (#9328) --- .github/workflows/make.yml | 53 +++--- .github/workflows/translation_update.yml | 2 +- Makefile | 42 ++--- Makefile.switch | 17 +- Makefile.vita | 33 ++-- docs/INSTALL.md | 7 +- docs/Makefile | 6 +- docs/TRANSLATION.md | 8 +- files/lang/Makefile | 2 +- src/Makefile | 176 ------------------ src/dist/Makefile | 164 +++++++++++++--- src/{ => dist}/Makefile.all | 2 +- src/{ => dist}/Makefile.bsd | 2 +- src/{ => dist}/Makefile.osx | 2 +- src/{ => dist}/Makefile.switch | 2 +- src/{ => dist}/Makefile.vita | 2 +- src/{ => dist}/engine/Makefile | 16 +- src/dist/fheroes2/Makefile | 54 ++++++ src/{ => dist}/thirdparty/libsmacker/Makefile | 13 +- src/{ => dist}/tools/Makefile | 21 ++- src/fheroes2/CMakeLists.txt | 9 +- 21 files changed, 313 insertions(+), 320 deletions(-) delete mode 100644 src/Makefile rename src/{ => dist}/Makefile.all (95%) rename src/{ => dist}/Makefile.bsd (95%) rename src/{ => dist}/Makefile.osx (96%) rename src/{ => dist}/Makefile.switch (96%) rename src/{ => dist}/Makefile.vita (97%) rename src/{ => dist}/engine/Makefile (85%) create mode 100644 src/dist/fheroes2/Makefile rename src/{ => dist}/thirdparty/libsmacker/Makefile (86%) rename src/{ => dist}/tools/Makefile (80%) diff --git a/.github/workflows/make.yml b/.github/workflows/make.yml index 3db4330c271..287d504bf7f 100644 --- a/.github/workflows/make.yml +++ b/.github/workflows/make.yml @@ -34,14 +34,14 @@ jobs: LICENSE ./docs/GRAPHICAL_ASSETS.md ./script/agg/extract_agg.sh - ./src/tools/82m2wav - ./src/tools/bin2txt - ./src/tools/extractor - ./src/tools/h2dmgr - ./src/tools/icn2img - ./src/tools/pal2img - ./src/tools/til2img - ./src/tools/xmi2midi + ./src/dist/tools/82m2wav + ./src/dist/tools/bin2txt + ./src/dist/tools/extractor + ./src/dist/tools/h2dmgr + ./src/dist/tools/icn2img + ./src/dist/tools/pal2img + ./src/dist/tools/til2img + ./src/dist/tools/xmi2midi release_name: Ubuntu x86-64 (Linux) build with SDL2 (latest commit) release_tag: fheroes2-linux-sdl2_dev - name: Linux x86-64 SDL2 Debug @@ -79,14 +79,14 @@ jobs: LICENSE ./docs/GRAPHICAL_ASSETS.md ./script/agg/extract_agg.sh - ./src/tools/82m2wav - ./src/tools/bin2txt - ./src/tools/extractor - ./src/tools/h2dmgr - ./src/tools/icn2img - ./src/tools/pal2img - ./src/tools/til2img - ./src/tools/xmi2midi + ./src/dist/tools/82m2wav + ./src/dist/tools/bin2txt + ./src/dist/tools/extractor + ./src/dist/tools/h2dmgr + ./src/dist/tools/icn2img + ./src/dist/tools/pal2img + ./src/dist/tools/til2img + ./src/dist/tools/xmi2midi release_name: Ubuntu ARM64 (Linux) build with SDL2 (latest commit) release_tag: fheroes2-linux-arm-sdl2_dev - name: Linux ARM64 SDL2 Debug @@ -124,14 +124,14 @@ jobs: LICENSE ./docs/GRAPHICAL_ASSETS.md ./script/agg/extract_agg.sh - ./src/tools/82m2wav - ./src/tools/bin2txt - ./src/tools/extractor - ./src/tools/h2dmgr - ./src/tools/icn2img - ./src/tools/pal2img - ./src/tools/til2img - ./src/tools/xmi2midi + ./src/dist/tools/82m2wav + ./src/dist/tools/bin2txt + ./src/dist/tools/extractor + ./src/dist/tools/h2dmgr + ./src/dist/tools/icn2img + ./src/dist/tools/pal2img + ./src/dist/tools/til2img + ./src/dist/tools/xmi2midi release_name: macOS x86-64 build with SDL2 (latest commit) release_tag: fheroes2-osx-sdl2_dev - name: macOS SDL2 App Bundle @@ -175,11 +175,6 @@ jobs: run: | make -j 2 env: ${{ matrix.config.env }} - - name: Create app bundle - if: ${{ startsWith( matrix.config.os, 'macos-' ) }} - run: | - make -j 2 bundle - env: ${{ matrix.config.env }} - name: Create package if: ${{ matrix.config.package_name != '' && matrix.config.package_files != '' }} run: | diff --git a/.github/workflows/translation_update.yml b/.github/workflows/translation_update.yml index 7370bcb0229..95e871874e6 100644 --- a/.github/workflows/translation_update.yml +++ b/.github/workflows/translation_update.yml @@ -33,7 +33,7 @@ jobs: echo "PR_BRANCH=$PR_BRANCH" >> "$GITHUB_ENV" - name: Generate POT run: | - make -C src/dist -j 2 pot + make -C src/dist/fheroes2 -j 2 pot - name: Merge PO with POT run: | make -C files/lang -j 2 merge diff --git a/Makefile b/Makefile index 758e01ac208..5ea7e2f4372 100644 --- a/Makefile +++ b/Makefile @@ -30,34 +30,34 @@ # FHEROES2_MACOS_APP_BUNDLE: create a Mac app bundle (only valid when building on macOS) # FHEROES2_DATA: set the built-in path to the fheroes2 data directory (e.g. /usr/share/fheroes2) -TARGET := fheroes2 +PROJECT_NAME := fheroes2 PROJECT_VERSION := $(file < version.txt) -.PHONY: all bundle clean +.PHONY: all clean all: - $(MAKE) -C src + $(MAKE) -C src/dist $(MAKE) -C files/lang -ifndef FHEROES2_MACOS_APP_BUNDLE - @cp src/dist/$(TARGET) . -endif - -bundle: ifdef FHEROES2_MACOS_APP_BUNDLE - @mkdir -p "src/dist/$(TARGET).app/Contents/Resources/translations" - @mkdir -p "src/dist/$(TARGET).app/Contents/Resources/h2d" - @mkdir -p "src/dist/$(TARGET).app/Contents/MacOS" - @cp ./src/resources/fheroes2.icns "src/dist/$(TARGET).app/Contents/Resources" - @cp ./files/lang/*.mo "src/dist/$(TARGET).app/Contents/Resources/translations" - @cp ./files/data/*.h2d "src/dist/$(TARGET).app/Contents/Resources/h2d" - @sed -e "s/\$${MACOSX_BUNDLE_EXECUTABLE_NAME}/$(TARGET)/" -e "s/\$${MACOSX_BUNDLE_ICON_FILE}/fheroes2.icns/" -e "s/\$${MACOSX_BUNDLE_GUI_IDENTIFIER}/com.fheroes2.$(TARGET)/" -e "s/\$${MACOSX_BUNDLE_BUNDLE_NAME}/$(TARGET)/" -e "s/\$${MACOSX_BUNDLE_BUNDLE_VERSION}/$(PROJECT_VERSION)/" -e "s/\$${MACOSX_BUNDLE_SHORT_VERSION_STRING}/$(PROJECT_VERSION)/" ./src/resources/Info.plist.in > "src/dist/$(TARGET).app/Contents/Info.plist" - @mv "src/dist/$(TARGET)" "src/dist/$(TARGET).app/Contents/MacOS" - @dylibbundler -od -b -x "src/dist/$(TARGET).app/Contents/MacOS/$(TARGET)" -d "src/dist/$(TARGET).app/Contents/libs" - @cp -R "src/dist/$(TARGET).app" . + mkdir -p fheroes2.app/Contents/Resources/translations + mkdir -p fheroes2.app/Contents/Resources/h2d + mkdir -p fheroes2.app/Contents/MacOS + cp src/resources/fheroes2.icns fheroes2.app/Contents/Resources + cp files/lang/*.mo fheroes2.app/Contents/Resources/translations + cp files/data/*.h2d fheroes2.app/Contents/Resources/h2d + sed -e "s/\$${MACOSX_BUNDLE_BUNDLE_NAME}/$(PROJECT_NAME)/" \ + -e "s/\$${MACOSX_BUNDLE_BUNDLE_VERSION}/$(PROJECT_VERSION)/" \ + -e "s/\$${MACOSX_BUNDLE_EXECUTABLE_NAME}/fheroes2/" \ + -e "s/\$${MACOSX_BUNDLE_GUI_IDENTIFIER}/org.fheroes2.$(PROJECT_NAME)/" \ + -e "s/\$${MACOSX_BUNDLE_ICON_FILE}/fheroes2.icns/" \ + -e "s/\$${MACOSX_BUNDLE_SHORT_VERSION_STRING}/$(PROJECT_VERSION)/" src/resources/Info.plist.in > fheroes2.app/Contents/Info.plist + cp src/dist/fheroes2/fheroes2 fheroes2.app/Contents/MacOS + dylibbundler -od -b -x fheroes2.app/Contents/MacOS/fheroes2 -d fheroes2.app/Contents/libs +else + cp src/dist/fheroes2/fheroes2 . endif clean: - $(MAKE) -C src clean + $(MAKE) -C src/dist clean $(MAKE) -C files/lang clean - @rm -f ./$(TARGET) - @rm -rf ./$(TARGET).app + rm -rf fheroes2 fheroes2.app diff --git a/Makefile.switch b/Makefile.switch index 1dcf96ca34e..86293484ddf 100644 --- a/Makefile.switch +++ b/Makefile.switch @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2021 - 2023 # +# Copyright (C) 2021 - 2024 # # # # 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,9 +24,6 @@ # FHEROES2_WITH_DEBUG: build in debug mode # FHEROES2_DATA: set the built-in path to the fheroes2 data directory (e.g. /usr/share/fheroes2) -TARGET := fheroes2 -MAKE := make - PROJECT_TITLE := fheroes2 PROJECT_AUTHOR := fheroes2 resurrection team PROJECT_VERSION := $(file < version.txt) @@ -36,11 +33,11 @@ PATH := $(PATH):/opt/devkitpro/portlibs/switch/bin/:/opt/devkitpro/tools/bin/ .PHONY: all clean all: - $(MAKE) -f Makefile -C src PLATFORM=switch - @cp src/dist/$(TARGET) $(TARGET).elf - nacptool --create "$(PROJECT_TITLE)" "$(PROJECT_AUTHOR)" "$(PROJECT_VERSION)" $(TARGET).nacp - elf2nro $(TARGET).elf $(TARGET).nro --icon=files/images/platform/switch/icon.jpg --nacp=$(TARGET).nacp + $(MAKE) -C src/dist PLATFORM=switch + cp src/dist/fheroes2/fheroes2 fheroes2.elf + nacptool --create "$(PROJECT_TITLE)" "$(PROJECT_AUTHOR)" "$(PROJECT_VERSION)" fheroes2.nacp + elf2nro fheroes2.elf fheroes2.nro --icon=files/images/platform/switch/icon.jpg --nacp=fheroes2.nacp clean: - $(MAKE) -f Makefile -C src clean - @rm -rf $(TARGET).elf $(TARGET).nacp $(TARGET).nro + $(MAKE) -C src/dist clean + rm -f fheroes2.elf fheroes2.nacp fheroes2.nro diff --git a/Makefile.vita b/Makefile.vita index 6b2be96c063..18a547c6b23 100644 --- a/Makefile.vita +++ b/Makefile.vita @@ -24,20 +24,15 @@ # FHEROES2_WITH_DEBUG: build in debug mode # FHEROES2_DATA: set the built-in path to the fheroes2 data directory (e.g. /usr/share/fheroes2) -TARGET := fheroes2 -MAKE := make - PROJECT_TITLE := fheroes2 PROJECT_TITLEID := FHOMM0002 PROJECT_VERSION := $(file < version.txt) -.PHONY: all clean $(TARGET).vpk param.sfo $(TARGET).elf translations - -all: package +.PHONY: all clean fheroes2.vpk param.sfo fheroes2.elf translations -package: $(TARGET).vpk +all: fheroes2.vpk -$(TARGET).vpk: eboot.bin param.sfo translations +fheroes2.vpk: eboot.bin param.sfo translations vita-pack-vpk -s param.sfo -b eboot.bin \ --add files/images/platform/psv/sce_sys/icon0.png=sce_sys/icon0.png \ --add files/images/platform/psv/sce_sys/livearea/contents/bg.png=sce_sys/livearea/contents/bg.png \ @@ -45,29 +40,29 @@ $(TARGET).vpk: eboot.bin param.sfo translations --add files/images/platform/psv/sce_sys/livearea/contents/template.xml=sce_sys/livearea/contents/template.xml \ --add files/data=files/data \ --add files/lang/vita_temp=files/lang \ - $(TARGET).vpk - @rm -r files/lang/vita_temp + fheroes2.vpk + rm -r files/lang/vita_temp -translations: $(TARGET).elf +translations: fheroes2.elf $(MAKE) -C files/lang mkdir -p files/lang/vita_temp cp files/lang/*.mo files/lang/vita_temp -eboot.bin: $(TARGET).velf - vita-make-fself $(TARGET).velf eboot.bin +eboot.bin: fheroes2.velf + vita-make-fself fheroes2.velf eboot.bin param.sfo: vita-mksfoex -s TITLE_ID="$(PROJECT_TITLEID)" -s APP_VER="$(PROJECT_VERSION)" "$(PROJECT_TITLE)" param.sfo -$(TARGET).velf: $(TARGET).elf +fheroes2.velf: fheroes2.elf arm-vita-eabi-strip -g $< vita-elf-create $< $@ -$(TARGET).elf: - $(MAKE) -f Makefile -C src PLATFORM=vita - @cp src/dist/$(TARGET) $(TARGET).elf +fheroes2.elf: + $(MAKE) -C src/dist PLATFORM=vita + cp src/dist/fheroes2/fheroes2 fheroes2.elf clean: - $(MAKE) -f Makefile -C src clean + $(MAKE) -C src/dist clean $(MAKE) -C files/lang clean - @rm -rf $(TARGET).velf $(TARGET).elf $(TARGET).vpk eboot.bin param.sfo + rm -f fheroes2.velf fheroes2.elf fheroes2.vpk eboot.bin param.sfo diff --git a/docs/INSTALL.md b/docs/INSTALL.md index a53c18eb2d4..537a59ff063 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -126,11 +126,10 @@ Follow the [**instructions below**](#macos-resources) to gather resources necess ### macOS native app -* Download the source and compile with the `-DMACOS_APP_BUNDLE=ON` option (if using CMake) or using the following commands (with make): +* Download the source and compile with the `-DMACOS_APP_BUNDLE=ON` option (if using CMake) or using the following command (with make): ```sh make FHEROES2_MACOS_APP_BUNDLE=ON -make FHEROES2_MACOS_APP_BUNDLE=ON bundle ``` Follow the [**instructions below**](#macos-resources) to gather resources necessary for fheroes2 to function as expected. @@ -159,8 +158,8 @@ Once you obtain the fheroes2 executable using any of the options above, you shou * If you don't have a legally purchased copy of the original game, you can download and install the demo version of the original game by running the download demo script. The script can be run from the following paths depending on how you installed fheroes2: * `fheroes2-install-demo` if you used a package manager (MacPorts or Homebrew); - * `script/homm2/download_demo_version_for_app_bundles.sh` if you built from source using the [**macOS native app**](#macos-native-app) method; - * `script/homm2/download_demo_version.sh` for all other cases. + * `script/demo/download_demo_version_for_app_bundle.sh` if you built from source using the [**macOS native app**](#macos-native-app) method; + * `script/demo/download_demo_version.sh` for all other cases. ## Linux diff --git a/docs/Makefile b/docs/Makefile index f758764e5ac..18761f24d8c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2022 # +# Copyright (C) 2022 - 2024 # # # # 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 # @@ -37,7 +37,7 @@ endif .PHONY: all clean all: - @sed -e '$(SEDEX)' fheroes2.6.in > fheroes2.6 + sed -e '$(SEDEX)' fheroes2.6.in > fheroes2.6 clean: - @rm -f fheroes2.6 + rm -f fheroes2.6 diff --git a/docs/TRANSLATION.md b/docs/TRANSLATION.md index c6b98639f7f..d802e14e147 100644 --- a/docs/TRANSLATION.md +++ b/docs/TRANSLATION.md @@ -144,10 +144,10 @@ example creature names or castle buildings. ## Updating PO templates and translatable strings in PO files -Currently all PO files are automatically updated with new strings after each commit that brings changes to the ingame text. If for whatever -reason you still need to update strings locally, this can be achieved by running the command below in `src/dist` to generate a new portable -object template (POT) file. Windows users will need to set up an environment that lets them run `make`, like Windows Subsystem for Linux (WSL) -or [**Cygwin**](https://www.cygwin.com/)/[**MSYS2**](https://www.msys2.org/). +Currently all PO files are automatically updated with new strings after each commit that brings changes to the ingame text. If for whatever reason +you still need to update strings locally, this can be achieved by running the command below in `src/dist/fheroes2` to generate a new portable object +template (POT) file. Windows users will need to set up an environment that lets them run `make`, like Windows Subsystem for Linux (WSL) or +[**Cygwin**](https://www.cygwin.com/)/[**MSYS2**](https://www.msys2.org/). ```bash make pot diff --git a/files/lang/Makefile b/files/lang/Makefile index af73ba3e4ac..6c731317eef 100644 --- a/files/lang/Makefile +++ b/files/lang/Makefile @@ -26,7 +26,7 @@ MSGFMT = sed -e '1,20 s/UTF-8/$(1)/' $< | $(ICONV) -f utf-8 -t $(1) | if msgfmt all: $(patsubst %.po, %.mo, $(wildcard *.po)) -merge: ../../src/dist/fheroes2.pot +merge: ../../src/dist/fheroes2/fheroes2.pot for i in $(wildcard *.po); do msgmerge -U --no-location $$i $<; done # Czech, Hungarian, Polish and Slovak versions use CP1250 diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index 97f7b38d1a9..00000000000 --- a/src/Makefile +++ /dev/null @@ -1,176 +0,0 @@ -########################################################################### -# fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2021 - 2024 # -# # -# This program is free software; you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation; either version 2 of the License, or # -# (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program; if not, write to the # -# Free Software Foundation, Inc., # -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # -########################################################################### - -TARGET := fheroes2 - -# -# Common build options for fheroes2 and third-party libraries -# - -# Common flags for both C and C++ compilers -CCFLAGS := -fsigned-char -pthread -# Flags for the C compiler -CFLAGS := $(CFLAGS) -# Flags for the C++ compiler -CXXFLAGS := $(CXXFLAGS) -# Flags for the preprocessor -CPPFLAGS := $(CPPFLAGS) -# Flags for the linker -LDFLAGS := $(LDFLAGS) -pthread -# Flags for additional libraries -LDLIBS := $(LDLIBS) - -ifdef FHEROES2_WITH_DEBUG -CCFLAGS := $(CCFLAGS) -O0 -g -else -CCFLAGS := $(CCFLAGS) -O3 -endif - -ifneq ($(or $(FHEROES2_WITH_ASAN),$(FHEROES2_WITH_TSAN)),) -SANITIZERS := undefined - -ifdef FHEROES2_WITH_ASAN -SANITIZERS := $(SANITIZERS),address -endif -ifdef FHEROES2_WITH_TSAN -SANITIZERS := $(SANITIZERS),thread -endif - -CCFLAGS := $(CCFLAGS) -fsanitize=$(SANITIZERS) -LDFLAGS := $(LDFLAGS) -fsanitize=$(SANITIZERS) -endif - -# -# Platform-specific build options -# - -ifndef PLATFORM -ifndef OS -OS := $(shell uname) -endif - -ifeq ($(OS),FreeBSD) -PLATFORM := bsd -endif -ifeq ($(OS),Darwin) -PLATFORM := osx -endif -ifeq ($(OS),Linux) -PLATFORM := all -endif -ifeq ($(OS),Haiku) -PLATFORM := all -endif -endif - -include Makefile.$(PLATFORM) - -# -# Build options for third-party libraries -# - -CCFLAGS_TP := $(CCFLAGS) -CFLAGS_TP := $(CFLAGS) -CXXFLAGS_TP := $(CXXFLAGS) -CPPFLAGS_TP := $(CPPFLAGS) - -# -# Build options for fheroes2 -# - -# *FLAGS_FH2 can be passed from platform-specific Makefiles -CCFLAGS := $(CCFLAGS) $(CCFLAGS_FH2) -CFLAGS := $(CFLAGS) $(CFLAGS_FH2) -CXXFLAGS := $(CXXFLAGS) $(CXXFLAGS_FH2) -std=c++17 -CPPFLAGS := $(CPPFLAGS) $(CPPFLAGS_FH2) - -ifdef FHEROES2_WITH_SYSTEM_SMACKER -LIBS := $(LIBS) -lsmacker -endif -LIBS := $(LIBS) -lz $(LDLIBS) - -ifdef FHEROES2_WITH_DEBUG -CCFLAGS := $(CCFLAGS) -DWITH_DEBUG -endif -ifdef FHEROES2_WITH_IMAGE -CCFLAGS := $(CCFLAGS) -DWITH_IMAGE -endif -ifdef FHEROES2_DATA -CCFLAGS := $(CCFLAGS) -DFHEROES2_DATA="$(FHEROES2_DATA)" -endif - -# TODO: Add -Wconversion -Wsign-conversion flags once we fix all the corresponding code smells -CCWARNOPTS := -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wdouble-promotion -Wfloat-conversion -Wfloat-equal \ - -Wredundant-decls -Wshadow -Wswitch-default -Wundef -Wunused -CFLAGS := $(CFLAGS) $(CCWARNOPTS) -CXXFLAGS := $(CXXFLAGS) $(CCWARNOPTS) -Wctor-dtor-privacy -Wextra-semi -Wmissing-declarations -Wold-style-cast \ - -Woverloaded-virtual -Wsuggest-override - -ifdef FHEROES2_STRICT_COMPILATION -CCFLAGS := $(CCFLAGS) -Werror -endif - -# -# SDL-related build options -# - -# SDL_FLAGS can already be defined in a platform-specific Makefile -ifeq ($(origin SDL_FLAGS),undefined) -SDL_FLAGS := $(shell sdl2-config --cflags) - -ifdef FHEROES2_WITH_IMAGE -SDL_FLAGS := $(SDL_FLAGS) $(shell libpng-config --cflags) -endif -endif - -# SDL_LIBS can already be defined in a platform-specific Makefile -ifeq ($(origin SDL_LIBS),undefined) -SDL_LIBS := -lSDL2_mixer $(shell sdl2-config --libs) - -ifdef FHEROES2_WITH_IMAGE -SDL_LIBS := $(SDL_LIBS) -lSDL2_image $(shell libpng-config --libs) -endif -endif - -CCFLAGS := $(CCFLAGS) $(SDL_FLAGS) -LIBS := $(SDL_LIBS) $(LIBS) - -export CC CXX AR CCFLAGS CFLAGS CXXFLAGS CPPFLAGS LDFLAGS LIBS PLATFORM - -.PHONY: all clean - -all: -ifndef FHEROES2_WITH_SYSTEM_SMACKER - $(MAKE) -C thirdparty/libsmacker CCFLAGS="$(CCFLAGS_TP)" CFLAGS="$(CFLAGS_TP)" CXXFLAGS="$(CXXFLAGS_TP)" CPPFLAGS="$(CPPFLAGS_TP)" -endif - $(MAKE) -C engine - $(MAKE) -C dist -ifdef FHEROES2_WITH_TOOLS - $(MAKE) -C tools -endif - $(MAKE) -C dist pot - -clean: -ifndef FHEROES2_WITH_SYSTEM_SMACKER - $(MAKE) -C thirdparty/libsmacker clean -endif - $(MAKE) -C tools clean - $(MAKE) -C dist clean - $(MAKE) -C engine clean diff --git a/src/dist/Makefile b/src/dist/Makefile index 7405f9f8f19..4e1c76db630 100644 --- a/src/dist/Makefile +++ b/src/dist/Makefile @@ -18,42 +18,156 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### -TARGET := fheroes2 +# +# Common build options for fheroes2 and third-party libraries +# -LIBENGINE := ../engine/libengine.a -CCFLAGS := $(CCFLAGS) -I../engine +# Common flags for both C and C++ compilers +CCFLAGS := -fsigned-char -pthread +# Flags for the C compiler +CFLAGS := $(CFLAGS) +# Flags for the C++ compiler +CXXFLAGS := $(CXXFLAGS) +# Flags for the preprocessor +CPPFLAGS := $(CPPFLAGS) +# Flags for the linker +LDFLAGS := $(LDFLAGS) -pthread +# Flags for additional libraries +LDLIBS := $(LDLIBS) -ifndef FHEROES2_WITH_SYSTEM_SMACKER -LIBENGINE := $(LIBENGINE) ../thirdparty/libsmacker/libsmacker.a -CCFLAGS := $(CCFLAGS) -I../thirdparty/libsmacker +ifdef FHEROES2_WITH_DEBUG +CCFLAGS := $(CCFLAGS) -O0 -g +else +CCFLAGS := $(CCFLAGS) -O3 +endif + +ifneq ($(or $(FHEROES2_WITH_ASAN),$(FHEROES2_WITH_TSAN)),) +SANITIZERS := undefined + +ifdef FHEROES2_WITH_ASAN +SANITIZERS := $(SANITIZERS),address +endif +ifdef FHEROES2_WITH_TSAN +SANITIZERS := $(SANITIZERS),thread +endif + +CCFLAGS := $(CCFLAGS) -fsanitize=$(SANITIZERS) +LDFLAGS := $(LDFLAGS) -fsanitize=$(SANITIZERS) +endif + +# +# Platform-specific build options +# + +ifndef PLATFORM +ifndef OS +OS := $(shell uname) endif -SOURCEROOT := ../fheroes2 -SOURCEDIR := $(filter %/,$(wildcard $(SOURCEROOT)/*/)) -POT := $(TARGET).pot +ifeq ($(OS),FreeBSD) +PLATFORM := bsd +endif +ifeq ($(OS),Darwin) +PLATFORM := osx +endif +ifeq ($(OS),Linux) +PLATFORM := all +endif +ifeq ($(OS),Haiku) +PLATFORM := all +endif +endif -SEARCH := $(wildcard $(SOURCEROOT)/*/*.cpp) +include Makefile.$(PLATFORM) -.PHONY: all clean pot +# +# Build options for third-party libraries +# -all: $(TARGET) +CCFLAGS_TP := $(CCFLAGS) +CFLAGS_TP := $(CFLAGS) +CXXFLAGS_TP := $(CXXFLAGS) +CPPFLAGS_TP := $(CPPFLAGS) -$(TARGET): $(notdir $(patsubst %.cpp, %.o, $(SEARCH))) $(LIBENGINE) - @echo "lnk: $@" - $(CXX) -o $@ $^ $(LIBS) $(LDFLAGS) +# +# Build options for fheroes2 +# -pot: $(wildcard $(SEARCH)) - @echo "gen: $(POT)" - @xgettext -d $(TARGET) -C -F -k_ -k_n:1,2 -o $(POT) $(sort $(wildcard $(SEARCH))) - @sed -i~ -e 's/, c-format//' $(POT) +# *FLAGS_FH2 can be passed from platform-specific Makefiles +CCFLAGS := $(CCFLAGS) $(CCFLAGS_FH2) +CFLAGS := $(CFLAGS) $(CFLAGS_FH2) +CXXFLAGS := $(CXXFLAGS) $(CXXFLAGS_FH2) -std=c++17 +CPPFLAGS := $(CPPFLAGS) $(CPPFLAGS_FH2) -VPATH := $(SOURCEDIR) +ifdef FHEROES2_WITH_SYSTEM_SMACKER +LIBS := $(LIBS) -lsmacker +endif +LIBS := $(LIBS) -lz $(LDLIBS) -%.o: %.cpp - $(CXX) -c -MD $(addprefix -I, $(SOURCEDIR)) $< $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) +ifdef FHEROES2_WITH_DEBUG +CCFLAGS := $(CCFLAGS) -DWITH_DEBUG +endif +ifdef FHEROES2_WITH_IMAGE +CCFLAGS := $(CCFLAGS) -DWITH_IMAGE +endif +ifdef FHEROES2_DATA +CCFLAGS := $(CCFLAGS) -DFHEROES2_DATA="$(FHEROES2_DATA)" +endif -include $(wildcard *.d) +# TODO: Add -Wconversion -Wsign-conversion flags once we fix all the corresponding code smells +CCWARNOPTS := -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wdouble-promotion -Wfloat-conversion -Wfloat-equal \ + -Wredundant-decls -Wshadow -Wswitch-default -Wundef -Wunused +CFLAGS := $(CFLAGS) $(CCWARNOPTS) +CXXFLAGS := $(CXXFLAGS) $(CCWARNOPTS) -Wctor-dtor-privacy -Wextra-semi -Wmissing-declarations -Wold-style-cast \ + -Woverloaded-virtual -Wsuggest-override + +ifdef FHEROES2_STRICT_COMPILATION +CCFLAGS := $(CCFLAGS) -Werror +endif + +# +# SDL-related build options +# + +# SDL_FLAGS can already be defined in a platform-specific Makefile +ifeq ($(origin SDL_FLAGS),undefined) +SDL_FLAGS := $(shell sdl2-config --cflags) + +ifdef FHEROES2_WITH_IMAGE +SDL_FLAGS := $(SDL_FLAGS) $(shell libpng-config --cflags) +endif +endif + +# SDL_LIBS can already be defined in a platform-specific Makefile +ifeq ($(origin SDL_LIBS),undefined) +SDL_LIBS := -lSDL2_mixer $(shell sdl2-config --libs) + +ifdef FHEROES2_WITH_IMAGE +SDL_LIBS := $(SDL_LIBS) -lSDL2_image $(shell libpng-config --libs) +endif +endif + +CCFLAGS := $(CCFLAGS) $(SDL_FLAGS) +LIBS := $(SDL_LIBS) $(LIBS) + +export CC CXX AR CCFLAGS CFLAGS CXXFLAGS CPPFLAGS LDFLAGS LIBS PLATFORM + +.PHONY: all clean + +all: +ifndef FHEROES2_WITH_SYSTEM_SMACKER + $(MAKE) -C thirdparty/libsmacker CCFLAGS="$(CCFLAGS_TP)" CFLAGS="$(CFLAGS_TP)" CXXFLAGS="$(CXXFLAGS_TP)" CPPFLAGS="$(CPPFLAGS_TP)" +endif + $(MAKE) -C engine + $(MAKE) -C fheroes2 +ifdef FHEROES2_WITH_TOOLS + $(MAKE) -C tools +endif clean: - rm -f *.pot *.pot~ *.o *.d *.exe $(TARGET) - rm -rf *.app +ifndef FHEROES2_WITH_SYSTEM_SMACKER + $(MAKE) -C thirdparty/libsmacker clean +endif + $(MAKE) -C engine clean + $(MAKE) -C fheroes2 clean + $(MAKE) -C tools clean diff --git a/src/Makefile.all b/src/dist/Makefile.all similarity index 95% rename from src/Makefile.all rename to src/dist/Makefile.all index 1d979d8c982..9dcb8b7a3f8 100644 --- a/src/Makefile.all +++ b/src/dist/Makefile.all @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2022 # +# Copyright (C) 2022 - 2024 # # # # 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 # diff --git a/src/Makefile.bsd b/src/dist/Makefile.bsd similarity index 95% rename from src/Makefile.bsd rename to src/dist/Makefile.bsd index d7dcdf64690..f213320b502 100644 --- a/src/Makefile.bsd +++ b/src/dist/Makefile.bsd @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2022 # +# Copyright (C) 2022 - 2024 # # # # 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 # diff --git a/src/Makefile.osx b/src/dist/Makefile.osx similarity index 96% rename from src/Makefile.osx rename to src/dist/Makefile.osx index af7f67659c5..3c0a472cb4b 100644 --- a/src/Makefile.osx +++ b/src/dist/Makefile.osx @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2022 # +# Copyright (C) 2022 - 2024 # # # # 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 # diff --git a/src/Makefile.switch b/src/dist/Makefile.switch similarity index 96% rename from src/Makefile.switch rename to src/dist/Makefile.switch index 97cad378f3e..5475a5f3c25 100644 --- a/src/Makefile.switch +++ b/src/dist/Makefile.switch @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2022 - 2023 # +# Copyright (C) 2022 - 2024 # # # # 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 # diff --git a/src/Makefile.vita b/src/dist/Makefile.vita similarity index 97% rename from src/Makefile.vita rename to src/dist/Makefile.vita index 71dd617ed42..dd8d9701f31 100644 --- a/src/Makefile.vita +++ b/src/dist/Makefile.vita @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2021 - 2023 # +# Copyright (C) 2021 - 2024 # # # # 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 # diff --git a/src/engine/Makefile b/src/dist/engine/Makefile similarity index 85% rename from src/engine/Makefile rename to src/dist/engine/Makefile index 5bd08e2c565..2914164f2ba 100644 --- a/src/engine/Makefile +++ b/src/dist/engine/Makefile @@ -18,23 +18,27 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### -TARGET := libengine - ifndef FHEROES2_WITH_SYSTEM_SMACKER -CCFLAGS := $(CCFLAGS) -I../thirdparty/libsmacker +CCFLAGS := $(CCFLAGS) -I../../thirdparty/libsmacker endif +SOURCEROOT := ../../engine +SOURCEDIRS := $(SOURCEROOT) +SOURCES := $(wildcard $(SOURCEDIRS)/*.cpp) + .PHONY: all clean -all: $(TARGET).a +all: libengine.a -$(TARGET).a: $(patsubst %.cpp, %.o, $(wildcard *.cpp)) +libengine.a: $(notdir $(patsubst %.cpp, %.o, $(SOURCES))) $(AR) crvs $@ $^ +VPATH := $(SOURCEDIRS) + %.o: %.cpp $(CXX) -c -MD $< $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) include $(wildcard *.d) clean: - rm -f *.a *.so *.d *.o + rm -f *.d *.o libengine.a diff --git a/src/dist/fheroes2/Makefile b/src/dist/fheroes2/Makefile new file mode 100644 index 00000000000..19082a83023 --- /dev/null +++ b/src/dist/fheroes2/Makefile @@ -0,0 +1,54 @@ +########################################################################### +# fheroes2: https://github.com/ihhub/fheroes2 # +# Copyright (C) 2021 - 2024 # +# # +# This program is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +########################################################################### + +LIBENGINE := ../engine/libengine.a +CCFLAGS := $(CCFLAGS) -I../../engine + +ifndef FHEROES2_WITH_SYSTEM_SMACKER +LIBENGINE := $(LIBENGINE) ../thirdparty/libsmacker/libsmacker.a +CCFLAGS := $(CCFLAGS) -I../../thirdparty/libsmacker +endif + +SOURCEROOT := ../../fheroes2 +SOURCEDIRS := $(filter %/,$(wildcard $(SOURCEROOT)/*/)) +SOURCES := $(wildcard $(SOURCEROOT)/*/*.cpp) + +.PHONY: all pot clean + +all: fheroes2 pot + +pot: fheroes2.pot + +fheroes2: $(notdir $(patsubst %.cpp, %.o, $(SOURCES))) $(LIBENGINE) + $(CXX) -o $@ $^ $(LIBS) $(LDFLAGS) + +fheroes2.pot: $(SOURCES) + xgettext -d fheroes2 -C -F -k_ -k_n:1,2 -o fheroes2.pot $(sort $(SOURCES)) + sed -i~ -e 's/, c-format//' fheroes2.pot + +VPATH := $(SOURCEDIRS) + +%.o: %.cpp + $(CXX) -c -MD $(addprefix -I, $(SOURCEDIRS)) $< $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) + +include $(wildcard *.d) + +clean: + rm -f *.d *.o fheroes2 fheroes2.pot fheroes2.pot~ diff --git a/src/thirdparty/libsmacker/Makefile b/src/dist/thirdparty/libsmacker/Makefile similarity index 86% rename from src/thirdparty/libsmacker/Makefile rename to src/dist/thirdparty/libsmacker/Makefile index 39b517543ab..3a5786b254f 100644 --- a/src/thirdparty/libsmacker/Makefile +++ b/src/dist/thirdparty/libsmacker/Makefile @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2022 # +# Copyright (C) 2022 - 2024 # # # # 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 # @@ -18,19 +18,22 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### -TARGET := libsmacker +SOURCEROOT := ../../../thirdparty/libsmacker +SOURCEDIRS := $(SOURCEROOT) .PHONY: all clean -all: $(TARGET).a +all: libsmacker.a -$(TARGET).a: smacker.o +libsmacker.a: smacker.o $(AR) crvs $@ $^ +VPATH := $(SOURCEDIRS) + smacker.o: smacker.c $(CC) -c -MD $< $(CCFLAGS) $(CFLAGS) $(CPPFLAGS) include $(wildcard *.d) clean: - rm -f *.a *.so *.d *.o + rm -f *.d *.o libsmacker.a diff --git a/src/tools/Makefile b/src/dist/tools/Makefile similarity index 80% rename from src/tools/Makefile rename to src/dist/tools/Makefile index 8fb05bdc18f..403aced02c5 100644 --- a/src/tools/Makefile +++ b/src/dist/tools/Makefile @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2021 - 2023 # +# Copyright (C) 2021 - 2024 # # # # 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 # @@ -21,15 +21,24 @@ TARGETS := 82m2wav bin2txt extractor h2dmgr icn2img pal2img til2img xmi2midi LIBENGINE := ../engine/libengine.a -CCFLAGS := $(CCFLAGS) -I../engine +CCFLAGS := $(CCFLAGS) -I../../engine + +SOURCEROOT := ../../tools +SOURCEDIRS := $(SOURCEROOT) .PHONY: all clean all: $(TARGETS) -$(TARGETS): $(addsuffix .cpp, $(TARGETS)) $(LIBENGINE) - $(CXX) $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) -c $@.cpp - $(CXX) -o $@ $@.o $(LIBENGINE) $(LIBS) $(LDFLAGS) +$(TARGETS): %: %.o $(LIBENGINE) + $(CXX) -o $@ $^ $(LIBS) $(LDFLAGS) + +VPATH := $(SOURCEDIRS) + +%.o: %.cpp + $(CXX) -c -MD $< $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) + +include $(wildcard *.d) clean: - rm -f *.o *.exe $(TARGETS) + rm -f *.d *.o $(TARGETS) diff --git a/src/fheroes2/CMakeLists.txt b/src/fheroes2/CMakeLists.txt index 9792475ab73..91a04b6e094 100644 --- a/src/fheroes2/CMakeLists.txt +++ b/src/fheroes2/CMakeLists.txt @@ -45,13 +45,12 @@ if(MACOS_APP_BUNDLE) target_link_libraries(fheroes2 "-framework CoreFoundation") set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME ${CMAKE_PROJECT_NAME}) - set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_EXECUTABLE_NAME ${CMAKE_PROJECT_NAME}) - set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../resources/Info.plist.in) + set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION}) + set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_EXECUTABLE_NAME fheroes2) + set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER org.fheroes2.${CMAKE_PROJECT_NAME}) set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_ICON_FILE fheroes2.icns) - set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER com.fheroes2.${CMAKE_PROJECT_NAME}) + set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../resources/Info.plist.in) set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${CMAKE_PROJECT_VERSION}) - set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION}) - set_target_properties(fheroes2 PROPERTIES OUTPUT_NAME ${CMAKE_PROJECT_NAME}) else(MACOS_APP_BUNDLE) cmake_path( ABSOLUTE_PATH FHEROES2_DATA From 26d752ab78de1f160a8833717ada58e2e553fff1 Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Tue, 10 Dec 2024 08:08:45 +0800 Subject: [PATCH 25/30] Use monster abilities instead of hardcoded monster IDs (#8816) --- src/fheroes2/battle/battle_action.cpp | 4 +- src/fheroes2/battle/battle_troop.cpp | 162 ++++++--------- src/fheroes2/battle/battle_troop.h | 6 +- src/fheroes2/monster/monster.cpp | 7 +- src/fheroes2/monster/monster.h | 2 + src/fheroes2/monster/monster_info.cpp | 282 +++++++++++++------------- src/fheroes2/monster/monster_info.h | 68 +++++-- src/fheroes2/spell/spell.h | 16 ++ 8 files changed, 283 insertions(+), 264 deletions(-) diff --git a/src/fheroes2/battle/battle_action.cpp b/src/fheroes2/battle/battle_action.cpp index be73de99319..06f99045599 100644 --- a/src/fheroes2/battle/battle_action.cpp +++ b/src/fheroes2/battle/battle_action.cpp @@ -915,8 +915,8 @@ Battle::TargetsInfo Battle::Arena::GetTargetsForDamage( const Unit & attacker, U { const std::vector & attackerAbilities = fheroes2::getMonsterData( attacker.GetID() ).battleStats.abilities; - const auto abilityIter = std::find( attackerAbilities.begin(), attackerAbilities.end(), fheroes2::MonsterAbility( fheroes2::MonsterAbilityType::ENEMY_HALVING ) ); - if ( abilityIter != attackerAbilities.end() ) { + if ( const auto abilityIter = std::find( attackerAbilities.begin(), attackerAbilities.end(), fheroes2::MonsterAbilityType::ENEMY_HALVING ); + abilityIter != attackerAbilities.end() ) { const uint32_t halvingDamage = ( defender.GetCount() / 2 + defender.GetCount() % 2 ) * defender.Monster::GetHitPoints(); if ( halvingDamage > res.damage && _randomGenerator.Get( 1, 100 ) <= abilityIter->percentage ) { // Replaces damage, not adds extra damage diff --git a/src/fheroes2/battle/battle_troop.cpp b/src/fheroes2/battle/battle_troop.cpp index 3d5d6305e50..abb14cffe59 100644 --- a/src/fheroes2/battle/battle_troop.cpp +++ b/src/fheroes2/battle/battle_troop.cpp @@ -562,42 +562,31 @@ uint32_t Battle::Unit::CalculateDamageUnit( const Unit & enemy, double dmg ) con // The retaliatory damage of a blinded unit is halved if ( _blindRetaliation ) { + // Petrified units cannot attack, respectively, there should be no retaliation + assert( !enemy.Modes( SP_STONE ) ); + dmg /= 2; } // A petrified unit takes only half of the damage if ( enemy.Modes( SP_STONE ) ) { + // Petrified units cannot attack, respectively, there should be no retaliation + assert( !_blindRetaliation ); + dmg /= 2; } - switch ( GetID() ) { - case Monster::CRUSADER: - if ( enemy.isUndead() ) { - dmg *= 2; - } - break; - case Monster::FIRE_ELEMENT: - if ( enemy.GetID() == Monster::WATER_ELEMENT ) { - dmg *= 2; - } - break; - case Monster::WATER_ELEMENT: - if ( enemy.GetID() == Monster::FIRE_ELEMENT ) { - dmg *= 2; - } - break; - case Monster::AIR_ELEMENT: - if ( enemy.GetID() == Monster::EARTH_ELEMENT ) { - dmg *= 2; - } - break; - case Monster::EARTH_ELEMENT: - if ( enemy.GetID() == Monster::AIR_ELEMENT ) { - dmg *= 2; - } - break; - default: - break; + // If multiple options are suitable at the same time, the damage should be doubled only once + if ( ( isAbilityPresent( fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD ) && enemy.isAbilityPresent( fheroes2::MonsterAbilityType::UNDEAD ) ) + || ( isAbilityPresent( fheroes2::MonsterAbilityType::EARTH_CREATURE ) + && enemy.isWeaknessPresent( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_EARTH_CREATURES ) ) + || ( isAbilityPresent( fheroes2::MonsterAbilityType::AIR_CREATURE ) + && enemy.isWeaknessPresent( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_AIR_CREATURES ) ) + || ( isAbilityPresent( fheroes2::MonsterAbilityType::FIRE_CREATURE ) + && enemy.isWeaknessPresent( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_FIRE_CREATURES ) ) + || ( isAbilityPresent( fheroes2::MonsterAbilityType::WATER_CREATURE ) + && enemy.isWeaknessPresent( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_WATER_CREATURES ) ) ) { + dmg *= 2; } int r = GetAttack() - enemy.GetDefense(); @@ -975,7 +964,7 @@ void Battle::Unit::PostAttackAction( const Unit & enemy ) ResetModes( LUCK_GOOD | LUCK_BAD ); } -void Battle::Unit::SetBlindRetaliation( bool value ) +void Battle::Unit::SetBlindRetaliation( const bool value ) { _blindRetaliation = value; } @@ -1126,9 +1115,8 @@ int32_t Battle::Unit::evaluateThreatForUnit( const Unit & defender ) const { const std::vector & attackerAbilities = fheroes2::getMonsterData( id ).battleStats.abilities; - const auto spellCasterAbilityIter - = std::find( attackerAbilities.begin(), attackerAbilities.end(), fheroes2::MonsterAbility( fheroes2::MonsterAbilityType::SPELL_CASTER ) ); - if ( spellCasterAbilityIter != attackerAbilities.end() ) { + if ( const auto abilityIter = std::find( attackerAbilities.begin(), attackerAbilities.end(), fheroes2::MonsterAbilityType::SPELL_CASTER ); + abilityIter != attackerAbilities.end() ) { const auto getDefenderDamage = [&defender]() { if ( defender.Modes( SP_CURSE ) ) { return defender.GetDamageMin(); @@ -1141,14 +1129,14 @@ int32_t Battle::Unit::evaluateThreatForUnit( const Unit & defender ) const return ( defender.GetDamageMin() + defender.GetDamageMax() ) / 2; }; - switch ( spellCasterAbilityIter->value ) { + switch ( abilityIter->value ) { case Spell::BLIND: case Spell::PARALYZE: case Spell::PETRIFY: // Creature's built-in magic resistance (not 100% immunity but resistance, as, for example, with Dwarves) never works against the built-in magic of // another creature (for example, Unicorn's Blind ability). Only the probability of triggering the built-in magic matters. - if ( defender.AllowApplySpell( spellCasterAbilityIter->value, nullptr ) ) { - attackerThreat += static_cast( getDefenderDamage() ) * spellCasterAbilityIter->percentage / 100.0; + if ( defender.AllowApplySpell( abilityIter->value, nullptr ) ) { + attackerThreat += static_cast( getDefenderDamage() ) * abilityIter->percentage / 100.0; } break; case Spell::DISPEL: @@ -1157,8 +1145,8 @@ int32_t Battle::Unit::evaluateThreatForUnit( const Unit & defender ) const case Spell::CURSE: // Creature's built-in magic resistance (not 100% immunity but resistance, as, for example, with Dwarves) never works against the built-in magic of // another creature (for example, Unicorn's Blind ability). Only the probability of triggering the built-in magic matters. - if ( defender.AllowApplySpell( spellCasterAbilityIter->value, nullptr ) ) { - attackerThreat += static_cast( getDefenderDamage() ) * spellCasterAbilityIter->percentage / 100.0 / 10.0; + if ( defender.AllowApplySpell( abilityIter->value, nullptr ) ) { + attackerThreat += static_cast( getDefenderDamage() ) * abilityIter->percentage / 100.0 / 10.0; } break; default: @@ -1307,11 +1295,11 @@ void Battle::Unit::SpellModesAction( const Spell & spell, uint32_t duration, con } } -void Battle::Unit::SpellApplyDamage( const Spell & spell, const uint32_t spellPoints, const HeroBase * applyingHero, TargetInfo & target ) +void Battle::Unit::SpellApplyDamage( const Spell & spell, const uint32_t spellPower, const HeroBase * applyingHero, TargetInfo & target ) { assert( spell.isDamage() ); - const uint32_t dmg = CalculateSpellDamage( spell, spellPoints, applyingHero, target.damage, false /* ignore defending hero */ ); + const uint32_t dmg = CalculateSpellDamage( spell, spellPower, applyingHero, target.damage, false /* ignore defending hero */ ); // apply damage if ( dmg ) { @@ -1320,76 +1308,46 @@ void Battle::Unit::SpellApplyDamage( const Spell & spell, const uint32_t spellPo } } -uint32_t Battle::Unit::CalculateSpellDamage( const Spell & spell, uint32_t spellPoints, const HeroBase * applyingHero, const uint32_t targetDamage, +uint32_t Battle::Unit::CalculateSpellDamage( const Spell & spell, uint32_t spellPower, const HeroBase * applyingHero, const uint32_t targetDamage, const bool ignoreDefendingHero ) const { assert( spell.isDamage() ); - // TODO: use fheroes2::getSpellDamage function to remove code duplication. - uint32_t dmg = spell.Damage() * spellPoints; + uint32_t dmg = spell.Damage() * spellPower; - switch ( GetID() ) { - case Monster::IRON_GOLEM: - case Monster::STEEL_GOLEM: - switch ( spell.GetID() ) { - // 50% damage - case Spell::COLDRAY: - case Spell::COLDRING: - case Spell::FIREBALL: - case Spell::FIREBLAST: - case Spell::LIGHTNINGBOLT: - case Spell::CHAINLIGHTNING: - case Spell::ELEMENTALSTORM: - case Spell::ARMAGEDDON: - dmg /= 2; - break; - default: - break; + // If multiple options are suitable at the same time, then the abilities are considered first (in order from more specific to less specific), + // and then the weaknesses are considered (also in order from more specific to less specific) + { + const std::vector & abilities = fheroes2::getMonsterData( GetID() ).battleStats.abilities; + const std::vector & weaknesses = fheroes2::getMonsterData( GetID() ).battleStats.weaknesses; + + // Abilities + // + if ( const auto certainSpellDmgRedIter + = std::find( abilities.begin(), abilities.end(), + std::make_pair( fheroes2::MonsterAbilityType::CERTAIN_SPELL_DAMAGE_REDUCTION, static_cast( spell.GetID() ) ) ); + certainSpellDmgRedIter != abilities.end() ) { + dmg = dmg * certainSpellDmgRedIter->percentage / 100; } - break; - - case Monster::WATER_ELEMENT: - switch ( spell.GetID() ) { - // 200% damage - case Spell::FIREBALL: - case Spell::FIREBLAST: - dmg *= 2; - break; - default: - break; + else if ( const auto elementalSpellDmgRedIter = std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbilityType::ELEMENTAL_SPELL_DAMAGE_REDUCTION ); + elementalSpellDmgRedIter != abilities.end() && spell.isElementalSpell() ) { + dmg = dmg * elementalSpellDmgRedIter->percentage / 100; } - break; - - case Monster::AIR_ELEMENT: - switch ( spell.GetID() ) { - // 200% damage - case Spell::ELEMENTALSTORM: - case Spell::LIGHTNINGBOLT: - case Spell::CHAINLIGHTNING: - dmg *= 2; - break; - default: - break; + // + // Weaknesses + // + else if ( const auto certainSpellExtraDmgIter + = std::find( weaknesses.begin(), weaknesses.end(), + std::make_pair( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, static_cast( spell.GetID() ) ) ); + certainSpellExtraDmgIter != weaknesses.end() ) { + dmg = dmg * ( 100 + certainSpellExtraDmgIter->percentage ) / 100; } - break; - - case Monster::FIRE_ELEMENT: - switch ( spell.GetID() ) { - // 200% damage - case Spell::COLDRAY: - case Spell::COLDRING: + else if ( ( isWeaknessPresent( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_FIRE_SPELLS ) && spell.isFire() ) + || ( isWeaknessPresent( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_COLD_SPELLS ) && spell.isCold() ) ) { dmg *= 2; - break; - default: - break; } - break; - - default: - break; } - // check artifact if ( applyingHero ) { const HeroBase * defendingHero = GetCommander(); const bool useDefendingHeroArts = defendingHero && !ignoreDefendingHero; @@ -1457,7 +1415,6 @@ uint32_t Battle::Unit::CalculateSpellDamage( const Spell & spell, uint32_t spell } } - // update orders damage if ( spell.GetID() == Spell::CHAINLIGHTNING ) { switch ( targetDamage ) { case 0: @@ -1610,18 +1567,19 @@ uint32_t Battle::Unit::GetMagicResist( const Spell & spell, const HeroBase * app int Battle::Unit::GetSpellMagic( Rand::DeterministicRandomGenerator & randomGenerator ) const { const std::vector & abilities = fheroes2::getMonsterData( GetID() ).battleStats.abilities; - const auto foundAbility = std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( fheroes2::MonsterAbilityType::SPELL_CASTER ) ); - if ( foundAbility == abilities.end() ) { + + const auto abilityIter = std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbilityType::SPELL_CASTER ); + if ( abilityIter == abilities.end() ) { // Not a spell caster. return Spell::NONE; } - if ( randomGenerator.Get( 1, 100 ) > foundAbility->percentage ) { + if ( randomGenerator.Get( 1, 100 ) > abilityIter->percentage ) { // No luck to cast the spell. return Spell::NONE; } - return foundAbility->value; + return abilityIter->value; } bool Battle::Unit::isHaveDamage() const diff --git a/src/fheroes2/battle/battle_troop.h b/src/fheroes2/battle/battle_troop.h index e3f1ebc6da1..ec91ea79522 100644 --- a/src/fheroes2/battle/battle_troop.h +++ b/src/fheroes2/battle/battle_troop.h @@ -209,9 +209,9 @@ namespace Battle void PostAttackAction( const Unit & enemy ); // Sets whether a unit performs a retaliatory attack while being blinded (i.e. with reduced efficiency) - void SetBlindRetaliation( bool value ); + void SetBlindRetaliation( const bool value ); - uint32_t CalculateSpellDamage( const Spell & spell, uint32_t spellPoints, const HeroBase * applyingHero, const uint32_t targetDamage, + uint32_t CalculateSpellDamage( const Spell & spell, uint32_t spellPower, const HeroBase * applyingHero, const uint32_t targetDamage, const bool ignoreDefendingHero ) const; bool SwitchAnimation( int rule, bool reverse = false ); @@ -301,7 +301,7 @@ namespace Battle uint32_t Resurrect( const uint32_t points, const bool allowToExceedInitialCount, const bool isTemporary ); // Applies a damage-causing spell to this unit - void SpellApplyDamage( const Spell & spell, const uint32_t spellPoints, const HeroBase * applyingHero, TargetInfo & target ); + void SpellApplyDamage( const Spell & spell, const uint32_t spellPower, const HeroBase * applyingHero, TargetInfo & target ); // Applies a restoring or reviving spell to this unit void SpellRestoreAction( const Spell & spell, const uint32_t spellPoints, const HeroBase * applyingHero ); // Applies a spell to this unit that changes its parameters diff --git a/src/fheroes2/monster/monster.cpp b/src/fheroes2/monster/monster.cpp index 4f3412032e4..2dfa901475c 100644 --- a/src/fheroes2/monster/monster.cpp +++ b/src/fheroes2/monster/monster.cpp @@ -221,9 +221,12 @@ uint32_t Monster::GetRNDSize() const bool Monster::isAbilityPresent( const fheroes2::MonsterAbilityType abilityType ) const { - const std::vector & abilities = fheroes2::getMonsterData( id ).battleStats.abilities; + return fheroes2::isAbilityPresent( fheroes2::getMonsterData( id ).battleStats.abilities, abilityType ); +} - return std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( abilityType ) ) != abilities.end(); +bool Monster::isWeaknessPresent( const fheroes2::MonsterWeaknessType weaknessType ) const +{ + return fheroes2::isWeaknessPresent( fheroes2::getMonsterData( id ).battleStats.weaknesses, weaknessType ); } Monster Monster::GetDowngrade() const diff --git a/src/fheroes2/monster/monster.h b/src/fheroes2/monster/monster.h index 40f225f6b65..8ce788f4f15 100644 --- a/src/fheroes2/monster/monster.h +++ b/src/fheroes2/monster/monster.h @@ -292,6 +292,8 @@ class Monster bool isAbilityPresent( const fheroes2::MonsterAbilityType abilityType ) const; + bool isWeaknessPresent( const fheroes2::MonsterWeaknessType weaknessType ) const; + double GetMonsterStrength( int attack = -1, int defense = -1 ) const; int ICNMonh() const; diff --git a/src/fheroes2/monster/monster_info.cpp b/src/fheroes2/monster/monster_info.cpp index 0bf90756d26..09f4ad392b2 100644 --- a/src/fheroes2/monster/monster_info.cpp +++ b/src/fheroes2/monster/monster_info.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include "icn.h" #include "m82.h" @@ -43,85 +42,78 @@ namespace { std::vector monsterData; - bool isAbilityPresent( const std::vector & abilities, const fheroes2::MonsterAbilityType abilityType ) - { - return std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( abilityType ) ) != abilities.end(); - } - double getMonsterBaseStrength( const fheroes2::MonsterData & data ) { const fheroes2::MonsterBattleStats & battleStats = data.battleStats; const std::vector & abilities = battleStats.abilities; - const double effectiveHP = battleStats.hp * ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ) ? 1.4 : 1 ); + const double effectiveHP = battleStats.hp * ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ) ? 1.4 : 1 ); const bool isArchers = ( battleStats.shots > 0 ); double damagePotential = ( battleStats.damageMin + battleStats.damageMax ) / 2.0; - if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_SHOOTING ) ) { + if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_SHOOTING ) ) { // How can it be that this ability is assigned not to a shooter? assert( isArchers ); damagePotential *= 2; } - else if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK ) ) { + else if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK ) ) { // Melee attacker will lose potential on second attack after retaliation - damagePotential *= isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ) ? 2 : 1.75; + damagePotential *= fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ) ? 2 : 1.75; } - if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD ) ) { + if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD ) ) { damagePotential *= 1.15; // 15% of all Monsters are Undead, deals double damage. } - if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ) ) { + if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ) ) { damagePotential *= 1.2; } - if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ALWAYS_RETALIATE ) ) { + if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ALWAYS_RETALIATE ) ) { damagePotential *= 1.25; } - if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK ) - || isAbilityPresent( abilities, fheroes2::MonsterAbilityType::AREA_SHOT ) ) { + if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK ) + || fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::AREA_SHOT ) ) { damagePotential *= 1.3; } double monsterSpecial = 1.0; if ( isArchers ) { - monsterSpecial += isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_MELEE_PENALTY ) ? 0.5 : 0.4; + monsterSpecial += fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_MELEE_PENALTY ) ? 0.5 : 0.4; } - if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::FLYING ) ) { + if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::FLYING ) ) { monsterSpecial += 0.3; } - if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ENEMY_HALVING ) ) { + if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ENEMY_HALVING ) ) { monsterSpecial += 1; } - if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::SOUL_EATER ) ) { + if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::SOUL_EATER ) ) { monsterSpecial += 2; } - if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::HP_DRAIN ) ) { + if ( fheroes2::isAbilityPresent( abilities, fheroes2::MonsterAbilityType::HP_DRAIN ) ) { monsterSpecial += 0.3; } - auto foundAbility = std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( fheroes2::MonsterAbilityType::SPELL_CASTER ) ); - - if ( foundAbility != abilities.end() ) { + if ( const auto abilityIter = std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbilityType::SPELL_CASTER ); abilityIter != abilities.end() ) { // This is a tricky evaluation. Spell casting ability depends on a type of spell and chance to inflict the spell. - switch ( foundAbility->value ) { + switch ( abilityIter->value ) { case Spell::PARALYZE: case Spell::BLIND: case Spell::PETRIFY: - monsterSpecial += foundAbility->percentage / 100.0; + monsterSpecial += abilityIter->percentage / 100.0; break; case Spell::DISPEL: case Spell::CURSE: // These spell are very weak and do not impact much during battle. - monsterSpecial += foundAbility->percentage / 100.0 / 10.0; + monsterSpecial += abilityIter->percentage / 100.0 / 10.0; break; default: // Did you add a new spell casting ability? Add the logic above! @@ -471,8 +463,10 @@ namespace monsterData[Monster::BOAR].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::IRON_GOLEM].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL_SPELL_DAMAGE_REDUCTION, 50, 0 ); + monsterData[Monster::IRON_GOLEM].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::CERTAIN_SPELL_DAMAGE_REDUCTION, 50, Spell::ARMAGEDDON ); monsterData[Monster::STEEL_GOLEM].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL_SPELL_DAMAGE_REDUCTION, 50, 0 ); + monsterData[Monster::STEEL_GOLEM].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::CERTAIN_SPELL_DAMAGE_REDUCTION, 50, Spell::ARMAGEDDON ); monsterData[Monster::ROC].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::ROC].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); @@ -535,28 +529,36 @@ namespace monsterData[Monster::NOMAD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::AIR_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL ); + monsterData[Monster::AIR_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::AIR_CREATURE ); monsterData[Monster::AIR_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::METEORSHOWER ); - monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100, - Spell::LIGHTNINGBOLT ); monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100, Spell::CHAINLIGHTNING ); monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100, Spell::ELEMENTALSTORM ); + monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100, + Spell::LIGHTNINGBOLT ); + monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_EARTH_CREATURES ); monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL ); - monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::LIGHTNINGBOLT ); + monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::EARTH_CREATURE ); monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::CHAINLIGHTNING ); monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::ELEMENTALSTORM ); + monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::LIGHTNINGBOLT ); monsterData[Monster::EARTH_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100, Spell::METEORSHOWER ); + monsterData[Monster::EARTH_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_AIR_CREATURES ); monsterData[Monster::FIRE_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL ); + monsterData[Monster::FIRE_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FIRE_CREATURE ); monsterData[Monster::FIRE_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FIRE_SPELL_IMMUNITY ); - monsterData[Monster::FIRE_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD_SPELL ); + monsterData[Monster::FIRE_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_COLD_SPELLS ); + monsterData[Monster::FIRE_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_WATER_CREATURES ); monsterData[Monster::WATER_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL ); + monsterData[Monster::WATER_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::WATER_CREATURE ); monsterData[Monster::WATER_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::COLD_SPELL_IMMUNITY ); - monsterData[Monster::WATER_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE_SPELL ); + monsterData[Monster::WATER_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_FIRE_SPELLS ); + monsterData[Monster::WATER_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::DOUBLE_DAMAGE_FROM_FIRE_CREATURES ); // Calculate base value of monster strength. for ( fheroes2::MonsterData & data : monsterData ) { @@ -606,15 +608,15 @@ namespace fheroes2 return monsterData[monsterId]; } - std::string getMonsterAbilityDescription( const MonsterAbility & ability, const bool ignoreBasicAbility ) + std::string getMonsterAbilityDescription( const MonsterAbility & ability, const bool ignoreBasicAbilities ) { switch ( ability.type ) { case MonsterAbilityType::NONE: - return ignoreBasicAbility ? "" : _( "None" ); + return ignoreBasicAbilities ? "" : _( "None" ); case MonsterAbilityType::DOUBLE_SHOOTING: return _( "Double shot" ); case MonsterAbilityType::DOUBLE_HEX_SIZE: - return ignoreBasicAbility ? "" : _( "2-hex monster" ); + return ignoreBasicAbilities ? "" : _( "2-hex monster" ); case MonsterAbilityType::DOUBLE_MELEE_ATTACK: return _( "Double strike" ); case MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD: @@ -640,6 +642,11 @@ namespace fheroes2 } case MonsterAbilityType::ELEMENTAL_SPELL_DAMAGE_REDUCTION: return std::to_string( ability.percentage ) + _( "% damage from Elemental spells" ); + case MonsterAbilityType::CERTAIN_SPELL_DAMAGE_REDUCTION: { + std::string str = _( "% damage from %{spell} spell" ); + StringReplace( str, "%{spell}", Spell( ability.value ).GetName() ); + return std::to_string( ability.percentage ) + str; + } case MonsterAbilityType::SPELL_CASTER: if ( ability.value == Spell::DISPEL ) { return std::to_string( ability.percentage ) + _( "% chance to Dispel beneficial spells" ); @@ -666,7 +673,7 @@ namespace fheroes2 case MonsterAbilityType::TWO_CELL_MELEE_ATTACK: return _( "Two hexes attack" ); case MonsterAbilityType::FLYING: - return ignoreBasicAbility ? "" : _( "Flyer" ); + return ignoreBasicAbilities ? "" : _( "Flyer" ); case MonsterAbilityType::ALWAYS_RETALIATE: return _( "Always retaliates" ); case MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK: @@ -674,7 +681,7 @@ namespace fheroes2 case MonsterAbilityType::NO_MELEE_PENALTY: return _( "No melee penalty" ); case MonsterAbilityType::DRAGON: - return ignoreBasicAbility ? "" : _( "Dragon" ); + return ignoreBasicAbilities ? "" : _( "Dragon" ); case MonsterAbilityType::UNDEAD: return _( "Undead" ); case MonsterAbilityType::NO_ENEMY_RETALIATION: @@ -690,7 +697,15 @@ namespace fheroes2 case MonsterAbilityType::SOUL_EATER: return _( "Soul Eater" ); case MonsterAbilityType::ELEMENTAL: - return ignoreBasicAbility ? _( "No Morale" ) : _( "Elemental" ); + return ignoreBasicAbilities ? _( "No Morale" ) : _( "Elemental" ); + case MonsterAbilityType::EARTH_CREATURE: + return ignoreBasicAbilities ? "" : _( "Earth creature" ); + case MonsterAbilityType::AIR_CREATURE: + return ignoreBasicAbilities ? "" : _( "Air creature" ); + case MonsterAbilityType::FIRE_CREATURE: + return ignoreBasicAbilities ? "" : _( "Fire creature" ); + case MonsterAbilityType::WATER_CREATURE: + return ignoreBasicAbilities ? "" : _( "Water creature" ); default: break; } @@ -699,20 +714,28 @@ namespace fheroes2 return ""; } - std::string getMonsterWeaknessDescription( const MonsterWeakness & weakness, const bool ignoreBasicAbility ) + std::string getMonsterWeaknessDescription( const MonsterWeakness & weakness, const bool ignoreBasicWeaknesses ) { switch ( weakness.type ) { case MonsterWeaknessType::NONE: - return ignoreBasicAbility ? "" : _( "None" ); - case MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE_SPELL: - return _( "200% damage from Fire spells" ); - case MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD_SPELL: - return _( "200% damage from Cold spells" ); + return ignoreBasicWeaknesses ? "" : _( "None" ); + case MonsterWeaknessType::DOUBLE_DAMAGE_FROM_FIRE_SPELLS: + return _( "Double damage from Fire spells" ); + case MonsterWeaknessType::DOUBLE_DAMAGE_FROM_COLD_SPELLS: + return _( "Double damage from Cold spells" ); case MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL: { std::string str = _( "% damage from %{spell} spell" ); StringReplace( str, "%{spell}", Spell( weakness.value ).GetName() ); return std::to_string( weakness.percentage + 100 ) + str; } + case MonsterWeaknessType::DOUBLE_DAMAGE_FROM_EARTH_CREATURES: + return ignoreBasicWeaknesses ? "" : _( "Double damage from Earth creatures" ); + case MonsterWeaknessType::DOUBLE_DAMAGE_FROM_AIR_CREATURES: + return ignoreBasicWeaknesses ? "" : _( "Double damage from Air creatures" ); + case MonsterWeaknessType::DOUBLE_DAMAGE_FROM_FIRE_CREATURES: + return ignoreBasicWeaknesses ? "" : _( "Double damage from Fire creatures" ); + case MonsterWeaknessType::DOUBLE_DAMAGE_FROM_WATER_CREATURES: + return ignoreBasicWeaknesses ? "" : _( "Double damage from Water creatures" ); default: break; } @@ -773,91 +796,82 @@ namespace fheroes2 { std::vector output; - const MonsterBattleStats & battleStats = getMonsterData( monsterId ).battleStats; + const auto listSpells = []( const std::vector & sortedSpells ) { + std::string result; - const std::vector & abilities = battleStats.abilities; + for ( size_t i = 0; i < sortedSpells.size(); ++i ) { + if ( i > 0 ) { + result += ", "; + } - std::map> immuneToSpells; - for ( const MonsterAbility & ability : abilities ) { - if ( ability.type == MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL ) { - immuneToSpells[ability.percentage].emplace_back( ability.value ); - continue; - } - const std::string abilityDescription = getMonsterAbilityDescription( ability, true ); - if ( !abilityDescription.empty() ) { - output.emplace_back( abilityDescription + '.' ); + if ( sortedSpells[i] == Spell::LIGHTNINGBOLT ) { + result += _( "Lightning" ); + } + else { + result += Spell( sortedSpells[i] ).GetName(); + } } - } - for ( auto spellInfoIter = immuneToSpells.begin(); spellInfoIter != immuneToSpells.end(); ++spellInfoIter ) { - assert( !spellInfoIter->second.empty() ); + result += '.'; - std::string temp; + return result; + }; - if ( spellInfoIter->first == 100 ) { - temp += _( "Immune to " ); - } - else { - temp += std::to_string( spellInfoIter->first ) + _( "% immunity to " ); - } + const MonsterBattleStats & battleStats = getMonsterData( monsterId ).battleStats; - const std::vector sortedSpells = replaceMassSpells( spellInfoIter->second ); + { + std::map> immuneToSpells; + std::map> reducedDamageFromSpells; - for ( size_t i = 0; i < sortedSpells.size(); ++i ) { - if ( i > 0 ) { - temp += ", "; + for ( const MonsterAbility & ability : battleStats.abilities ) { + if ( ability.type == MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL ) { + immuneToSpells[ability.percentage].emplace_back( ability.value ); + continue; } - if ( sortedSpells[i] == Spell::LIGHTNINGBOLT ) { - temp += _( "Lightning" ); + if ( ability.type == MonsterAbilityType::CERTAIN_SPELL_DAMAGE_REDUCTION ) { + reducedDamageFromSpells[ability.percentage].emplace_back( ability.value ); + continue; } - else { - temp += Spell( sortedSpells[i] ).GetName(); + + if ( const std::string description = getMonsterAbilityDescription( ability, true ); !description.empty() ) { + output.emplace_back( description + '.' ); } } - temp += '.'; - output.emplace_back( std::move( temp ) ); - } + for ( const auto & [percentage, spells] : immuneToSpells ) { + assert( !spells.empty() ); - std::map> extraDamageSpells; - const std::vector & weaknesses = battleStats.weaknesses; - for ( const MonsterWeakness & weakness : weaknesses ) { - if ( weakness.type == MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL ) { - extraDamageSpells[weakness.percentage].emplace_back( weakness.value ); - continue; + output.emplace_back( ( percentage == 100 ? _( "Immune to " ) : std::to_string( percentage ) + _( "% immunity to " ) ) + + listSpells( replaceMassSpells( spells ) ) ); } - const std::string weaknessDescription = getMonsterWeaknessDescription( weakness, true ); - if ( !weaknessDescription.empty() ) { - output.emplace_back( weaknessDescription + '.' ); + for ( const auto & [percentage, spells] : reducedDamageFromSpells ) { + assert( !spells.empty() ); + + output.emplace_back( std::to_string( percentage ) + _( "% damage from " ) + listSpells( replaceMassSpells( spells ) ) ); } } - for ( auto spellInfoIter = extraDamageSpells.begin(); spellInfoIter != extraDamageSpells.end(); ++spellInfoIter ) { - assert( !spellInfoIter->second.empty() ); + { + std::map> extraDamageFromSpells; - std::string temp; - - temp += std::to_string( spellInfoIter->first + 100 ) + _( "% damage from " ); - - const std::vector sortedSpells = replaceMassSpells( spellInfoIter->second ); - - for ( size_t i = 0; i < sortedSpells.size(); ++i ) { - if ( i > 0 ) { - temp += ", "; + for ( const MonsterWeakness & weakness : battleStats.weaknesses ) { + if ( weakness.type == MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL ) { + extraDamageFromSpells[weakness.percentage].emplace_back( weakness.value ); + continue; } - if ( sortedSpells[i] == Spell::LIGHTNINGBOLT ) { - temp += _( "Lightning" ); - } - else { - temp += Spell( sortedSpells[i] ).GetName(); + if ( const std::string description = getMonsterWeaknessDescription( weakness, true ); !description.empty() ) { + output.emplace_back( description + '.' ); } } - temp += '.'; - output.emplace_back( std::move( temp ) ); + for ( const auto & [percentage, spells] : extraDamageFromSpells ) { + assert( !spells.empty() ); + + output.emplace_back( std::to_string( percentage + 100 ) + _( "% damage from " ) + listSpells( replaceMassSpells( spells ) ) ); + } } return output; @@ -869,72 +883,50 @@ namespace fheroes2 Spell spell( spellId ); - std::vector::const_iterator foundAbility; - if ( spell.isMindInfluence() ) { - foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::MIND_SPELL_IMMUNITY ) ); - if ( foundAbility != abilities.end() ) { + if ( std::find( abilities.begin(), abilities.end(), MonsterAbilityType::MIND_SPELL_IMMUNITY ) != abilities.end() ) { return 100; } - foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::UNDEAD ) ); - if ( foundAbility != abilities.end() ) { + if ( std::find( abilities.begin(), abilities.end(), MonsterAbilityType::UNDEAD ) != abilities.end() ) { return 100; } - foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::ELEMENTAL ) ); - if ( foundAbility != abilities.end() ) { + if ( std::find( abilities.begin(), abilities.end(), MonsterAbilityType::ELEMENTAL ) != abilities.end() ) { return 100; } } - if ( spell.isAliveOnly() ) { - foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::UNDEAD ) ); - if ( foundAbility != abilities.end() ) { - return 100; - } + if ( spell.isAliveOnly() && std::find( abilities.begin(), abilities.end(), MonsterAbilityType::UNDEAD ) != abilities.end() ) { + return 100; } - if ( spell.isUndeadOnly() ) { - foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::UNDEAD ) ); - if ( foundAbility == abilities.end() ) { - return 100; - } + if ( spell.isUndeadOnly() && std::find( abilities.begin(), abilities.end(), MonsterAbilityType::UNDEAD ) == abilities.end() ) { + return 100; } - if ( spell.isCold() ) { - foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::COLD_SPELL_IMMUNITY ) ); - if ( foundAbility != abilities.end() ) { - return 100; - } + if ( spell.isCold() && std::find( abilities.begin(), abilities.end(), MonsterAbilityType::COLD_SPELL_IMMUNITY ) != abilities.end() ) { + return 100; } - if ( spell.isFire() ) { - foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::FIRE_SPELL_IMMUNITY ) ); - if ( foundAbility != abilities.end() ) { - return 100; - } + if ( spell.isFire() && std::find( abilities.begin(), abilities.end(), MonsterAbilityType::FIRE_SPELL_IMMUNITY ) != abilities.end() ) { + return 100; } - if ( spell == Spell::COLDRAY || spell == Spell::COLDRING || spell == Spell::FIREBALL || spell == Spell::FIREBLAST || spell == Spell::LIGHTNINGBOLT - || spell == Spell::CHAINLIGHTNING || spell == Spell::ELEMENTALSTORM ) { - foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::ELEMENTAL_SPELL_IMMUNITY ) ); - if ( foundAbility != abilities.end() ) { - return 100; - } + if ( spell.isElementalSpell() && std::find( abilities.begin(), abilities.end(), MonsterAbilityType::ELEMENTAL_SPELL_IMMUNITY ) != abilities.end() ) { + return 100; } uint32_t spellResistance = 0; // Find magic immunity for every spell. - foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::MAGIC_RESISTANCE ) ); - if ( foundAbility != abilities.end() ) { - if ( foundAbility->percentage == 100 ) { + if ( const auto abilityIter = std::find( abilities.begin(), abilities.end(), MonsterAbilityType::MAGIC_RESISTANCE ); abilityIter != abilities.end() ) { + if ( abilityIter->percentage == 100 ) { // Immune to everything. return 100; } if ( spell.isDamage() || spell.isApplyToEnemies() ) { - spellResistance = foundAbility->percentage; + spellResistance = abilityIter->percentage; } } @@ -946,4 +938,14 @@ namespace fheroes2 return spellResistance; } + + bool isAbilityPresent( const std::vector & abilities, const MonsterAbilityType abilityType ) + { + return std::find( abilities.begin(), abilities.end(), abilityType ) != abilities.end(); + } + + bool isWeaknessPresent( const std::vector & weaknesses, const MonsterWeaknessType weaknessType ) + { + return std::find( weaknesses.begin(), weaknesses.end(), weaknessType ) != weaknesses.end(); + } } diff --git a/src/fheroes2/monster/monster_info.h b/src/fheroes2/monster/monster_info.h index 33cc82a4e45..d16a558b1c2 100644 --- a/src/fheroes2/monster/monster_info.h +++ b/src/fheroes2/monster/monster_info.h @@ -23,6 +23,7 @@ #include #include +#include #include #include "resource.h" @@ -34,14 +35,18 @@ namespace fheroes2 enum class MonsterAbilityType : int { - // Basic abilities. + // Basic abilities (usually not shown in the unit description). NONE, DOUBLE_HEX_SIZE, FLYING, DRAGON, + EARTH_CREATURE, + AIR_CREATURE, + FIRE_CREATURE, + WATER_CREATURE, + // Advanced abilities (shown in the unit description). UNDEAD, ELEMENTAL, - // Advanced abilities. DOUBLE_SHOOTING, DOUBLE_MELEE_ATTACK, DOUBLE_DAMAGE_TO_UNDEAD, @@ -52,6 +57,7 @@ namespace fheroes2 COLD_SPELL_IMMUNITY, IMMUNE_TO_CERTAIN_SPELL, ELEMENTAL_SPELL_DAMAGE_REDUCTION, + CERTAIN_SPELL_DAMAGE_REDUCTION, SPELL_CASTER, HP_REGENERATION, TWO_CELL_MELEE_ATTACK, @@ -68,11 +74,15 @@ namespace fheroes2 enum class MonsterWeaknessType : int { - // Basic abilities. + // Basic weaknesses (usually not shown in the unit description). NONE, - // Advanced abilities. - EXTRA_DAMAGE_FROM_FIRE_SPELL, - EXTRA_DAMAGE_FROM_COLD_SPELL, + DOUBLE_DAMAGE_FROM_EARTH_CREATURES, + DOUBLE_DAMAGE_FROM_AIR_CREATURES, + DOUBLE_DAMAGE_FROM_FIRE_CREATURES, + DOUBLE_DAMAGE_FROM_WATER_CREATURES, + // Advanced weaknesses (shown in the unit description). + DOUBLE_DAMAGE_FROM_FIRE_SPELLS, + DOUBLE_DAMAGE_FROM_COLD_SPELLS, EXTRA_DAMAGE_FROM_CERTAIN_SPELL }; @@ -82,17 +92,26 @@ namespace fheroes2 : type( type_ ) , percentage( 0 ) , value( 0 ) - {} + { + // Do nothing. + } MonsterAbility( const MonsterAbilityType type_, const uint32_t percentage_, const uint32_t value_ ) : type( type_ ) , percentage( percentage_ ) , value( value_ ) - {} + { + // Do nothing. + } + + bool operator==( const MonsterAbilityType anotherType ) const + { + return type == anotherType; + } - bool operator==( const MonsterAbility & another ) const + bool operator==( const std::pair & typeValuePair ) const { - return type == another.type; + return type == typeValuePair.first && value == typeValuePair.second; } MonsterAbilityType type; @@ -108,19 +127,33 @@ namespace fheroes2 : type( type_ ) , percentage( 0 ) , value( 0 ) - {} + { + // Do nothing. + } explicit MonsterWeakness( const MonsterWeaknessType type_, const uint32_t percentage_, const uint32_t value_ ) : type( type_ ) , percentage( percentage_ ) , value( value_ ) - {} + { + // Do nothing. + } bool operator<( const MonsterWeakness & another ) const { return type < another.type || ( type == another.type && value < another.value ); } + bool operator==( const MonsterWeaknessType anotherType ) const + { + return type == anotherType; + } + + bool operator==( const std::pair & typeValuePair ) const + { + return type == typeValuePair.first && value == typeValuePair.second; + } + MonsterWeaknessType type; uint32_t percentage; @@ -177,7 +210,9 @@ namespace fheroes2 , sounds( sounds_ ) , battleStats( battleStats_ ) , generalStats( generalStats_ ) - {} + { + // Do nothing. + } int icnId; @@ -192,13 +227,16 @@ namespace fheroes2 const MonsterData & getMonsterData( const int monsterId ); - std::string getMonsterAbilityDescription( const MonsterAbility & ability, const bool ignoreBasicAbility ); - std::string getMonsterWeaknessDescription( const MonsterWeakness & weakness, const bool ignoreBasicAbility ); + std::string getMonsterAbilityDescription( const MonsterAbility & ability, const bool ignoreBasicAbilities ); + std::string getMonsterWeaknessDescription( const MonsterWeakness & weakness, const bool ignoreBasicWeaknesses ); std::string getMonsterDescription( const int monsterId ); // To be utilized in future. std::vector getMonsterPropertiesDescription( const int monsterId ); uint32_t getSpellResistance( const int monsterId, const int spellId ); + + bool isAbilityPresent( const std::vector & abilities, const MonsterAbilityType abilityType ); + bool isWeaknessPresent( const std::vector & weaknesses, const MonsterWeaknessType weaknessType ); } #endif diff --git a/src/fheroes2/spell/spell.h b/src/fheroes2/spell/spell.h index 7a60e32958d..50e5bd23104 100644 --- a/src/fheroes2/spell/spell.h +++ b/src/fheroes2/spell/spell.h @@ -225,6 +225,22 @@ class Spell return id == COLDRAY || id == COLDRING; } + bool isElementalSpell() const + { + switch ( id ) { + case COLDRAY: + case COLDRING: + case FIREBALL: + case FIREBLAST: + case LIGHTNINGBOLT: + case CHAINLIGHTNING: + case ELEMENTALSTORM: + return true; + default: + return false; + } + } + bool isBuiltinOnly() const { return id == PETRIFY; From 6b8fb085a9bf5bf0415f77e7742bac65e91de522 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:09:53 +0800 Subject: [PATCH 26/30] Update translation files (#9333) --- docs/json/lang_cs.json | 2 +- docs/json/lang_hu.json | 2 +- docs/json/lang_sk.json | 2 +- files/lang/be.po | 34 +++++++++++++++++++++++---- files/lang/bg.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/cs.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/de.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/dk.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/es.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/fr.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/hu.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/it.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/lt.po | 38 ++++++++++++++++++++++++++---- files/lang/nb.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/nl.po | 34 +++++++++++++++++++++++---- files/lang/pl.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/pt.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/ro.po | 34 +++++++++++++++++++++++---- files/lang/ru.po | 52 ++++++++++++++++++++++++++++++++++++------ files/lang/sk.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/sv.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/tr.po | 34 +++++++++++++++++++++++---- files/lang/uk.po | 48 ++++++++++++++++++++++++++++++++------ files/lang/vi.po | 48 ++++++++++++++++++++++++++++++++------ 24 files changed, 812 insertions(+), 140 deletions(-) diff --git a/docs/json/lang_cs.json b/docs/json/lang_cs.json index 1ada26fdf03..2142b6c32eb 100644 --- a/docs/json/lang_cs.json +++ b/docs/json/lang_cs.json @@ -1 +1 @@ -{"schemaVersion":1,"label":"Czech","message":"100%","color":"green"} +{"schemaVersion":1,"label":"Czech","message":"99%","color":"green"} diff --git a/docs/json/lang_hu.json b/docs/json/lang_hu.json index 2f1e48e51de..ab7ba1517a6 100644 --- a/docs/json/lang_hu.json +++ b/docs/json/lang_hu.json @@ -1 +1 @@ -{"schemaVersion":1,"label":"Hungarian","message":"100%","color":"green"} +{"schemaVersion":1,"label":"Hungarian","message":"99%","color":"green"} diff --git a/docs/json/lang_sk.json b/docs/json/lang_sk.json index 2ed4381bee4..71bf1abf6a7 100644 --- a/docs/json/lang_sk.json +++ b/docs/json/lang_sk.json @@ -1 +1 @@ -{"schemaVersion":1,"label":"Slovak","message":"83%","color":"yellow"} +{"schemaVersion":1,"label":"Slovak","message":"82%","color":"yellow"} diff --git a/files/lang/be.po b/files/lang/be.po index 63226fbcb6a..798ba9d87e5 100644 --- a/files/lang/be.po +++ b/files/lang/be.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2022-09-23 16:01+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -9020,6 +9020,9 @@ msgstr "" msgid "% damage from Elemental spells" msgstr "" +msgid "% damage from %{spell} spell" +msgstr "" + msgid "% chance to Dispel beneficial spells" msgstr "" @@ -9086,21 +9089,42 @@ msgstr "" msgid "No Morale" msgstr "" -msgid "200% damage from Fire spells" +msgid "Earth creature" msgstr "" -msgid "200% damage from Cold spells" +msgid "Air creature" msgstr "" -msgid "% damage from %{spell} spell" +msgid "Fire creature" msgstr "" -msgid "% immunity to " +msgid "Water creature" +msgstr "" + +msgid "Double damage from Fire spells" +msgstr "" + +msgid "Double damage from Cold spells" +msgstr "" + +msgid "Double damage from Earth creatures" +msgstr "" + +msgid "Double damage from Air creatures" +msgstr "" + +msgid "Double damage from Fire creatures" +msgstr "" + +msgid "Double damage from Water creatures" msgstr "" msgid "Lightning" msgstr "" +msgid "% immunity to " +msgstr "" + msgid "% damage from " msgstr "" diff --git a/files/lang/bg.po b/files/lang/bg.po index 56a64657330..47e67e09394 100644 --- a/files/lang/bg.po +++ b/files/lang/bg.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2022-11-08 16:44+0000\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -9954,6 +9954,9 @@ msgstr "% имунитет към %{spell} магия" msgid "% damage from Elemental spells" msgstr "% щети от магии на елементал" +msgid "% damage from %{spell} spell" +msgstr "% щети от %{spell} заклинание" + msgid "% chance to Dispel beneficial spells" msgstr "% шанс за отблъскване на полезни заклинания" @@ -10020,21 +10023,52 @@ msgstr "Елементал" msgid "No Morale" msgstr "Без морал" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Вербувай същества" + +#, fuzzy +msgid "Air creature" +msgstr "Вербувай същества" + +#, fuzzy +msgid "Fire creature" +msgstr "Вербувай същества" + +#, fuzzy +msgid "Water creature" +msgstr "Водно езеро" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% щети от огнени магии" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% щети от студени магии" -msgid "% damage from %{spell} spell" -msgstr "% щети от %{spell} заклинание" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Премахва всички магически заклинания от всички същества." -msgid "% immunity to " -msgstr "% имунитет към " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Двойни щети на Немъртвеца" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% щети от огнени магии" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Премахва всички магически заклинания от всички същества." msgid "Lightning" msgstr "Светкавица" +msgid "% immunity to " +msgstr "% имунитет към " + msgid "% damage from " msgstr "% щети от " diff --git a/files/lang/cs.po b/files/lang/cs.po index 0cc0ef43aa0..da1f8edfa99 100644 --- a/files/lang/cs.po +++ b/files/lang/cs.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-04 04:06+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-11-26 19:28+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: Czech \n" @@ -9636,6 +9636,9 @@ msgstr "% imunita vůči kouzlu %{spell}" msgid "% damage from Elemental spells" msgstr "% poškození z elementálských kouzel" +msgid "% damage from %{spell} spell" +msgstr "% poškození z kouzla %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% šance na zrušení prospěšných kouzel" @@ -9702,21 +9705,52 @@ msgstr "Elementál" msgid "No Morale" msgstr "Žádná morálka" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Najmout jednotky" + +#, fuzzy +msgid "Air creature" +msgstr "Najmout jednotky" + +#, fuzzy +msgid "Fire creature" +msgstr "Najmout jednotky" + +#, fuzzy +msgid "Water creature" +msgstr "Jezero" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% poškození z ohnivých kouzel" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% poškození z ledových kouzel" -msgid "% damage from %{spell} spell" -msgstr "% poškození z kouzla %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Odstraní všechna kouzla ze všech jednotek." -msgid "% immunity to " -msgstr "% imunita vůči " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Dvojnásobné zranění nemrtvých" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% poškození z ohnivých kouzel" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Odstraní všechna kouzla ze všech jednotek." msgid "Lightning" msgstr "Blesk" +msgid "% immunity to " +msgstr "% imunita vůči " + msgid "% damage from " msgstr "% poškození z " diff --git a/files/lang/de.po b/files/lang/de.po index e427d1f2033..83a374398cf 100644 --- a/files/lang/de.po +++ b/files/lang/de.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-11-07 12:48+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -10081,6 +10081,9 @@ msgstr "% immunität gegen %{spell}" msgid "% damage from Elemental spells" msgstr "% Schaden von Elementarzaubern" +msgid "% damage from %{spell} spell" +msgstr "% Schaden durch %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% Chance, nützliche Zauber zu bannen" @@ -10147,21 +10150,52 @@ msgstr "Element" msgid "No Morale" msgstr "Keine Moral" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Kreaturen rekrutieren" + +#, fuzzy +msgid "Air creature" +msgstr "Kreaturen rekrutieren" + +#, fuzzy +msgid "Fire creature" +msgstr "Kreaturen rekrutieren" + +#, fuzzy +msgid "Water creature" +msgstr "See" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% Schaden von Feuerzaubern" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% Schaden von Kälterzaubern" -msgid "% damage from %{spell} spell" -msgstr "% Schaden durch %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Entfernt alle Zaubersprüche von allen Kreaturen." -msgid "% immunity to " -msgstr "% immunität gegen " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Doppelter Schaden an Untoten" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% Schaden von Feuerzaubern" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Entfernt alle Zaubersprüche von allen Kreaturen." msgid "Lightning" msgstr "Blitz" +msgid "% immunity to " +msgstr "% immunität gegen " + msgid "% damage from " msgstr "% Schaden von " diff --git a/files/lang/dk.po b/files/lang/dk.po index 31b52b9ef56..70b1f35f156 100644 --- a/files/lang/dk.po +++ b/files/lang/dk.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-05-21 12:45+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Danish (Denmark)\n" @@ -9823,6 +9823,9 @@ msgstr "% immunitet overfor %{spell} besværgelse" msgid "% damage from Elemental spells" msgstr "% skade af elementalbesværgelser" +msgid "% damage from %{spell} spell" +msgstr "% skade af %{spell} besværgelsen" + msgid "% chance to Dispel beneficial spells" msgstr "% chance, for at fordrive gavnlige besværgelser" @@ -9889,21 +9892,52 @@ msgstr "Elementalt" msgid "No Morale" msgstr "Ingen moral" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Rekruttér væsener" + +#, fuzzy +msgid "Air creature" +msgstr "Rekruttér væsener" + +#, fuzzy +msgid "Fire creature" +msgstr "Rekruttér væsener" + +#, fuzzy +msgid "Water creature" +msgstr "Indsø" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% skade af ildbesværgelser" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% skade af koldbesværgelser" -msgid "% damage from %{spell} spell" -msgstr "% skade af %{spell} besværgelsen" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Fjerner alle magiske besværgelser fra samtlige tropper." -msgid "% immunity to " -msgstr "% immunitet overfor " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Dobbelt skade til udøde" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% skade af ildbesværgelser" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Fjerner alle magiske besværgelser fra samtlige tropper." msgid "Lightning" msgstr "Lyn" +msgid "% immunity to " +msgstr "% immunitet overfor " + msgid "% damage from " msgstr "% skade af " diff --git a/files/lang/es.po b/files/lang/es.po index e2a08bf28a5..3c4cfbe0b7d 100644 --- a/files/lang/es.po +++ b/files/lang/es.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-10-07 15:36+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Spanish \n" @@ -9800,6 +9800,9 @@ msgstr "% de inmunidad al hechizo %{spell}" msgid "% damage from Elemental spells" msgstr "% de daño de los hechizos elementales" +msgid "% damage from %{spell} spell" +msgstr "% de daño del hechizo %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% de probabilidad de disipar hechizos beneficiosos" @@ -9866,21 +9869,52 @@ msgstr "Elemental" msgid "No Morale" msgstr "Sin moral" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Reclutar Criaturas" + +#, fuzzy +msgid "Air creature" +msgstr "Reclutar Criaturas" + +#, fuzzy +msgid "Fire creature" +msgstr "Reclutar Criaturas" + +#, fuzzy +msgid "Water creature" +msgstr "Lago" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% de daño de los hechizos de fuego" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% de daño de los hechizos de frío" -msgid "% damage from %{spell} spell" -msgstr "% de daño del hechizo %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Elimina todos los hechizos mágicos que afectan a todas las criaturas." -msgid "% immunity to " -msgstr "% de inmunidad al " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Doble daño a nomuertos" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% de daño de los hechizos de fuego" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Elimina todos los hechizos mágicos que afectan a todas las criaturas." msgid "Lightning" msgstr "Rayo" +msgid "% immunity to " +msgstr "% de inmunidad al " + msgid "% damage from " msgstr "% de daño de " diff --git a/files/lang/fr.po b/files/lang/fr.po index b6992fff452..557994a1d46 100644 --- a/files/lang/fr.po +++ b/files/lang/fr.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-10-07 15:36+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: French \n" @@ -9960,6 +9960,9 @@ msgstr "% d'immunité contre le sort %{spell}" msgid "% damage from Elemental spells" msgstr "% de dégâts des sorts élémentaires" +msgid "% damage from %{spell} spell" +msgstr "% de dégâts du sort %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% de chances de dissiper les sorts bénéfiques" @@ -10026,21 +10029,52 @@ msgstr "Élémentaire" msgid "No Morale" msgstr "Pas de moral" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Recruter des créatures" + +#, fuzzy +msgid "Air creature" +msgstr "Recruter des créatures" + +#, fuzzy +msgid "Fire creature" +msgstr "Recruter des créatures" + +#, fuzzy +msgid "Water creature" +msgstr "Lac" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% des dégâts des sorts du feu" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% des dégâts des sorts du froid" -msgid "% damage from %{spell} spell" -msgstr "% de dégâts du sort %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Lève tous les sortilèges de toutes les créatures." -msgid "% immunity to " -msgstr "% d'immunité contre " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Double dégâts aux morts-vivants" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% des dégâts des sorts du feu" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Lève tous les sortilèges de toutes les créatures." msgid "Lightning" msgstr "Éclair foudroyant" +msgid "% immunity to " +msgstr "% d'immunité contre " + msgid "% damage from " msgstr "% de dégâts de " diff --git a/files/lang/hu.po b/files/lang/hu.po index 0d31b69e364..3a8880681d8 100644 --- a/files/lang/hu.po +++ b/files/lang/hu.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-08 01:56+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2023-09-04 21:55+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Hungarian \n" @@ -9685,6 +9685,9 @@ msgstr "% immunitás a(z) %{spell} varázslattól" msgid "% damage from Elemental spells" msgstr "% sebzés elemi varázslatoktól" +msgid "% damage from %{spell} spell" +msgstr "% sebzés %{spell} varázslattól" + msgid "% chance to Dispel beneficial spells" msgstr "% esély jótékony varázslatok eloszlatására" @@ -9751,21 +9754,52 @@ msgstr "Elemi lény" msgid "No Morale" msgstr "Nincs morál" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Lények toborzása" + +#, fuzzy +msgid "Air creature" +msgstr "Lények toborzása" + +#, fuzzy +msgid "Fire creature" +msgstr "Lények toborzása" + +#, fuzzy +msgid "Water creature" +msgstr "Tó" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% sebzés tűzvarázslatoktól" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% sebzés fagyvarázslatoktól" -msgid "% damage from %{spell} spell" -msgstr "% sebzés %{spell} varázslattól" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Eloszlat minden varászlatot minden lényről." -msgid "% immunity to " -msgstr "% immunitás ettől: " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Dupla sebzés élőhalottakon" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% sebzés tűzvarázslatoktól" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Eloszlat minden varászlatot minden lényről." msgid "Lightning" msgstr "Villámlás" +msgid "% immunity to " +msgstr "% immunitás ettől: " + msgid "% damage from " msgstr "% sebzés ettől: " diff --git a/files/lang/it.po b/files/lang/it.po index f4f8ad73630..0f82e62f068 100644 --- a/files/lang/it.po +++ b/files/lang/it.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2022-12-03 04:36+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -9239,6 +9239,9 @@ msgstr "% di immunità alla magia %{spell}" msgid "% damage from Elemental spells" msgstr "% di danni dalle magie elementali" +msgid "% damage from %{spell} spell" +msgstr "% di danni dalla magia %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% di probabiltà di annullare magie positive" @@ -9305,21 +9308,52 @@ msgstr "Elementale" msgid "No Morale" msgstr "Nessun morale" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Recluta Eroe" + +#, fuzzy +msgid "Air creature" +msgstr "Recluta Eroe" + +#, fuzzy +msgid "Fire creature" +msgstr "Recluta Eroe" + +#, fuzzy +msgid "Water creature" +msgstr "Lago" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% di danni dalle magie del fuoco" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% di danni dalle magie del gelo" -msgid "% damage from %{spell} spell" -msgstr "% di danni dalla magia %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Rimuove tutte le magie da tutte le creature nel campo di battaglia." -msgid "% immunity to " -msgstr "% di immunità a " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Doppio danno contro non-morti" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% di danni dalle magie del fuoco" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Rimuove tutte le magie da tutte le creature nel campo di battaglia." msgid "Lightning" msgstr "Fulmine" +msgid "% immunity to " +msgstr "% di immunità a " + msgid "% damage from " msgstr "% di danni da " diff --git a/files/lang/lt.po b/files/lang/lt.po index f4bef63c855..04cf8077e38 100644 --- a/files/lang/lt.po +++ b/files/lang/lt.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2022-09-23 16:14+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Lithuanian \n" @@ -9070,6 +9070,9 @@ msgstr "" msgid "% damage from Elemental spells" msgstr "" +msgid "% damage from %{spell} spell" +msgstr "" + msgid "% chance to Dispel beneficial spells" msgstr "" @@ -9138,21 +9141,46 @@ msgstr "" msgid "No Morale" msgstr "" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Pasamdyti %{name}" + +#, fuzzy +msgid "Air creature" +msgstr "Pasamdyti %{name}" + +#, fuzzy +msgid "Fire creature" +msgstr "Pasamdyti %{name}" + +#, fuzzy +msgid "Water creature" +msgstr "Pasamdyti %{name}" + +msgid "Double damage from Fire spells" +msgstr "" + +msgid "Double damage from Cold spells" msgstr "" -msgid "200% damage from Cold spells" +msgid "Double damage from Earth creatures" msgstr "" -msgid "% damage from %{spell} spell" +msgid "Double damage from Air creatures" msgstr "" -msgid "% immunity to " +msgid "Double damage from Fire creatures" +msgstr "" + +msgid "Double damage from Water creatures" msgstr "" msgid "Lightning" msgstr "" +msgid "% immunity to " +msgstr "" + msgid "% damage from " msgstr "" diff --git a/files/lang/nb.po b/files/lang/nb.po index 576628d42ab..7af44c88c35 100644 --- a/files/lang/nb.po +++ b/files/lang/nb.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2023-09-03 18:42+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -10091,6 +10091,9 @@ msgstr "% immunitet mot trolldommen %{spell}" msgid "% damage from Elemental spells" msgstr "% skade fra elementbasert magi" +msgid "% damage from %{spell} spell" +msgstr "% skade fra trolldommen %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% sjanse til å oppheve all fordelaktig trolldom" @@ -10157,21 +10160,52 @@ msgstr "Elementær" msgid "No Morale" msgstr "Ingen kampånd" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Verv krigere" + +#, fuzzy +msgid "Air creature" +msgstr "Verv krigere" + +#, fuzzy +msgid "Fire creature" +msgstr "Verv krigere" + +#, fuzzy +msgid "Water creature" +msgstr "Innsjø" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% skade fra ildmagi" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% skade fra kuldemagi" -msgid "% damage from %{spell} spell" -msgstr "% skade fra trolldommen %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Fjerner all trolldom fra alle krigere, både vennlige og fiendtlige." -msgid "% immunity to " -msgstr "% immunitet mot trolldommen %{spell} " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Dobbel skade mot vandøde" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% skade fra ildmagi" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Fjerner all trolldom fra alle krigere, både vennlige og fiendtlige." msgid "Lightning" msgstr "Lynnedslag" +msgid "% immunity to " +msgstr "% immunitet mot trolldommen %{spell} " + msgid "% damage from " msgstr "% skade fra " diff --git a/files/lang/nl.po b/files/lang/nl.po index cc0e4965227..a52af3ae0d2 100644 --- a/files/lang/nl.po +++ b/files/lang/nl.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2022-10-25 08:52+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Dutch \n" @@ -9035,6 +9035,9 @@ msgstr "" msgid "% damage from Elemental spells" msgstr "" +msgid "% damage from %{spell} spell" +msgstr "" + msgid "% chance to Dispel beneficial spells" msgstr "" @@ -9101,21 +9104,42 @@ msgstr "" msgid "No Morale" msgstr "" -msgid "200% damage from Fire spells" +msgid "Earth creature" msgstr "" -msgid "200% damage from Cold spells" +msgid "Air creature" msgstr "" -msgid "% damage from %{spell} spell" +msgid "Fire creature" msgstr "" -msgid "% immunity to " +msgid "Water creature" +msgstr "" + +msgid "Double damage from Fire spells" +msgstr "" + +msgid "Double damage from Cold spells" +msgstr "" + +msgid "Double damage from Earth creatures" +msgstr "" + +msgid "Double damage from Air creatures" +msgstr "" + +msgid "Double damage from Fire creatures" +msgstr "" + +msgid "Double damage from Water creatures" msgstr "" msgid "Lightning" msgstr "" +msgid "% immunity to " +msgstr "" + msgid "% damage from " msgstr "" diff --git a/files/lang/pl.po b/files/lang/pl.po index 1d6aae8f326..2b60b72e2b8 100644 --- a/files/lang/pl.po +++ b/files/lang/pl.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-07-20 23:19+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: https://discord.com/" @@ -9938,6 +9938,9 @@ msgstr "% odporności na czar %{spell}" msgid "% damage from Elemental spells" msgstr "% odporności na czary żywiołów" +msgid "% damage from %{spell} spell" +msgstr "% obrażeń od czaru %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% szans na Rozproszenie pozytywnych czarów" @@ -10004,21 +10007,52 @@ msgstr "Żywiołak" msgid "No Morale" msgstr "Neutralne morale" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Rekrutuj stworzenia" + +#, fuzzy +msgid "Air creature" +msgstr "Rekrutuj stworzenia" + +#, fuzzy +msgid "Fire creature" +msgstr "Rekrutuj stworzenia" + +#, fuzzy +msgid "Water creature" +msgstr "Jezioro" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% obrażeń od czarów ognia" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% obrażeń od czarów zimna" -msgid "% damage from %{spell} spell" -msgstr "% obrażeń od czaru %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Przerywa działanie wszelkich zaklęć rzuconych na wszystkie jednostki." -msgid "% immunity to " -msgstr "% odporności na " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Podwójne obrażenia nieumarłym" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% obrażeń od czarów ognia" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Przerywa działanie wszelkich zaklęć rzuconych na wszystkie jednostki." msgid "Lightning" msgstr "błyskawic" +msgid "% immunity to " +msgstr "% odporności na " + msgid "% damage from " msgstr "% obrażeń od " diff --git a/files/lang/pt.po b/files/lang/pt.po index 232f77e0b68..e48eff13d48 100644 --- a/files/lang/pt.po +++ b/files/lang/pt.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-10-14 16:46-0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: Brazilian Portuguese \n" @@ -9779,6 +9779,9 @@ msgstr "% imunidade à feitiços de %{spell}" msgid "% damage from Elemental spells" msgstr "% de dano para feitiços Elementais" +msgid "% damage from %{spell} spell" +msgstr "% de dano do feitiço %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% de chance de Dissipar feitiços benéficos" @@ -9845,21 +9848,52 @@ msgstr "Elemental" msgid "No Morale" msgstr "Sem Moral" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Recrutar Criaturas" + +#, fuzzy +msgid "Air creature" +msgstr "Recrutar Criaturas" + +#, fuzzy +msgid "Fire creature" +msgstr "Recrutar Criaturas" + +#, fuzzy +msgid "Water creature" +msgstr "Lago" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% de danos causados por feitiços Fogo" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% de danos causados por feitiços Frios" -msgid "% damage from %{spell} spell" -msgstr "% de dano do feitiço %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Remove todos os feitiços mágicos de todas as criaturas." -msgid "% immunity to " -msgstr "% imunidade à " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Dobro de dano para Mortos-Vivos" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% de danos causados por feitiços Fogo" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Remove todos os feitiços mágicos de todas as criaturas." msgid "Lightning" msgstr "Raio" +msgid "% immunity to " +msgstr "% imunidade à " + msgid "% damage from " msgstr "% de dano de " diff --git a/files/lang/ro.po b/files/lang/ro.po index e855a02e3bb..8260acd7567 100644 --- a/files/lang/ro.po +++ b/files/lang/ro.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2022-10-25 08:56+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -8981,6 +8981,9 @@ msgstr "" msgid "% damage from Elemental spells" msgstr "" +msgid "% damage from %{spell} spell" +msgstr "" + msgid "% chance to Dispel beneficial spells" msgstr "" @@ -9047,21 +9050,42 @@ msgstr "" msgid "No Morale" msgstr "" -msgid "200% damage from Fire spells" +msgid "Earth creature" msgstr "" -msgid "200% damage from Cold spells" +msgid "Air creature" msgstr "" -msgid "% damage from %{spell} spell" +msgid "Fire creature" msgstr "" -msgid "% immunity to " +msgid "Water creature" +msgstr "" + +msgid "Double damage from Fire spells" +msgstr "" + +msgid "Double damage from Cold spells" +msgstr "" + +msgid "Double damage from Earth creatures" +msgstr "" + +msgid "Double damage from Air creatures" +msgstr "" + +msgid "Double damage from Fire creatures" +msgstr "" + +msgid "Double damage from Water creatures" msgstr "" msgid "Lightning" msgstr "" +msgid "% immunity to " +msgstr "" + msgid "% damage from " msgstr "" diff --git a/files/lang/ru.po b/files/lang/ru.po index 1f969f4eef8..c43f91f1831 100644 --- a/files/lang/ru.po +++ b/files/lang/ru.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-10-04 15:32+0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: <>\n" @@ -9723,6 +9723,9 @@ msgstr "% устойчивости к заклинанию %{spell}" msgid "% damage from Elemental spells" msgstr "% урона от заклинаний" +msgid "% damage from %{spell} spell" +msgstr "% урона от заклинания %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% шанс развеять полезные заклинания" @@ -9789,21 +9792,56 @@ msgstr "Элементаль" msgid "No Morale" msgstr "Нет морали" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Нанять существ" + +#, fuzzy +msgid "Air creature" +msgstr "Нанять существ" + +#, fuzzy +msgid "Fire creature" +msgstr "Нанять существ" + +#, fuzzy +msgid "Water creature" +msgstr "Озеро" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% урона от заклинаний огня" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% урона от заклинаний холода" -msgid "% damage from %{spell} spell" -msgstr "% урона от заклинания %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "" +"Снимает действия всех заклинаний, как полезных, так и вредных, со всех " +"присутствующих на поле боя отрядов." -msgid "% immunity to " -msgstr "% невосприимчивости к " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Двойной урон по нежити" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% урона от заклинаний огня" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "" +"Снимает действия всех заклинаний, как полезных, так и вредных, со всех " +"присутствующих на поле боя отрядов." msgid "Lightning" msgstr "Молния" +msgid "% immunity to " +msgstr "% невосприимчивости к " + msgid "% damage from " msgstr "% урона от " diff --git a/files/lang/sk.po b/files/lang/sk.po index f5d59dcdb71..fc9f77000b2 100644 --- a/files/lang/sk.po +++ b/files/lang/sk.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2023-09-11 20:22+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -9882,6 +9882,9 @@ msgstr "% imunity voči kúzlu %{spell}" msgid "% damage from Elemental spells" msgstr "% poškodenia od elementálnych kúziel" +msgid "% damage from %{spell} spell" +msgstr "% poškodenia od kúzla %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% šanca na rozptýlenie prospešných kúziel" @@ -9948,21 +9951,52 @@ msgstr "Elementál" msgid "No Morale" msgstr "Žiadna morálka" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Najať bojovníkov" + +#, fuzzy +msgid "Air creature" +msgstr "Najať bojovníkov" + +#, fuzzy +msgid "Fire creature" +msgstr "Najať bojovníkov" + +#, fuzzy +msgid "Water creature" +msgstr "Jazero s vodou" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "Dvojité poškodenie od kúziel ohňa" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "Dvojité poškodenie od kúziel chladu" -msgid "% damage from %{spell} spell" -msgstr "% poškodenia od kúzla %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Odstráni všetky kúzla zo všetkých tvorov." -msgid "% immunity to " -msgstr "% imunity voči: " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Dvojité poškodenie nemŕtvym" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "Dvojité poškodenie od kúziel ohňa" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Odstráni všetky kúzla zo všetkých tvorov." msgid "Lightning" msgstr "Blesk" +msgid "% immunity to " +msgstr "% imunity voči: " + msgid "% damage from " msgstr "% poškodenie od: " diff --git a/files/lang/sv.po b/files/lang/sv.po index 901fe64590d..87f74ed5847 100644 --- a/files/lang/sv.po +++ b/files/lang/sv.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-11-11 20:10+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: Swedish \n" @@ -9691,6 +9691,9 @@ msgstr "% resistens mot %{spell}besvärjelse" msgid "% damage from Elemental spells" msgstr "% skada från Elementbesvärjelser" +msgid "% damage from %{spell} spell" +msgstr "% skada från %{spell}besvärjelse" + msgid "% chance to Dispel beneficial spells" msgstr "% chans att Skingra godartade besvärjelser" @@ -9757,21 +9760,52 @@ msgstr "Elementande" msgid "No Morale" msgstr "Ingen Stridsmoral" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Rekrytera Varelser" + +#, fuzzy +msgid "Air creature" +msgstr "Rekrytera Varelser" + +#, fuzzy +msgid "Fire creature" +msgstr "Rekrytera Varelser" + +#, fuzzy +msgid "Water creature" +msgstr "Sjö" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% skada från Eldbesvärjelser" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% skada från Köldbesvärjelser" -msgid "% damage from %{spell} spell" -msgstr "% skada från %{spell}besvärjelse" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Tar bort alla magiska besvärjelser från alla varelser." -msgid "% immunity to " -msgstr "% resistens mot " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Dubbel skada till Odöda" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% skada från Eldbesvärjelser" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Tar bort alla magiska besvärjelser från alla varelser." msgid "Lightning" msgstr "Blixt" +msgid "% immunity to " +msgstr "% resistens mot " + msgid "% damage from " msgstr "% skada från " diff --git a/files/lang/tr.po b/files/lang/tr.po index 37d33e2644a..450281be7ec 100644 --- a/files/lang/tr.po +++ b/files/lang/tr.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-12-01 16:37+0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: Turkish \n" @@ -8969,6 +8969,9 @@ msgstr "" msgid "% damage from Elemental spells" msgstr "" +msgid "% damage from %{spell} spell" +msgstr "" + msgid "% chance to Dispel beneficial spells" msgstr "" @@ -9035,21 +9038,42 @@ msgstr "" msgid "No Morale" msgstr "" -msgid "200% damage from Fire spells" +msgid "Earth creature" msgstr "" -msgid "200% damage from Cold spells" +msgid "Air creature" msgstr "" -msgid "% damage from %{spell} spell" +msgid "Fire creature" msgstr "" -msgid "% immunity to " +msgid "Water creature" +msgstr "" + +msgid "Double damage from Fire spells" +msgstr "" + +msgid "Double damage from Cold spells" +msgstr "" + +msgid "Double damage from Earth creatures" +msgstr "" + +msgid "Double damage from Air creatures" +msgstr "" + +msgid "Double damage from Fire creatures" +msgstr "" + +msgid "Double damage from Water creatures" msgstr "" msgid "Lightning" msgstr "" +msgid "% immunity to " +msgstr "" + msgid "% damage from " msgstr "" diff --git a/files/lang/uk.po b/files/lang/uk.po index 541cc9a0538..3668c04f2f6 100644 --- a/files/lang/uk.po +++ b/files/lang/uk.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-08-23 09:35+0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -9732,6 +9732,9 @@ msgstr "% імунітету до %{spell} заклинань" msgid "% damage from Elemental spells" msgstr "% шкоди від Елементальних заклинань" +msgid "% damage from %{spell} spell" +msgstr "% шкоди від %{spell} заклинання" + msgid "% chance to Dispel beneficial spells" msgstr "% шансу зняти позитивні чари" @@ -9798,21 +9801,52 @@ msgstr "Елементаль" msgid "No Morale" msgstr "Мораль відсутня" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Найняти створінь" + +#, fuzzy +msgid "Air creature" +msgstr "Найняти створінь" + +#, fuzzy +msgid "Fire creature" +msgstr "Найняти створінь" + +#, fuzzy +msgid "Water creature" +msgstr "Озеро" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% шкоди від Вогняних заклинань" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% шкоди від Холодових заклинань" -msgid "% damage from %{spell} spell" -msgstr "% шкоди від %{spell} заклинання" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Знімає всі закляття з усіх істот." -msgid "% immunity to " -msgstr "% імунітету до " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Подвійна шкода немертвим" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% шкоди від Вогняних заклинань" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Знімає всі закляття з усіх істот." msgid "Lightning" msgstr "Блискавка" +msgid "% immunity to " +msgstr "% імунітету до " + msgid "% damage from " msgstr "% шкоди від " diff --git a/files/lang/vi.po b/files/lang/vi.po index 71b229855fd..589c8af6d79 100644 --- a/files/lang/vi.po +++ b/files/lang/vi.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-28 15:55+0000\n" +"POT-Creation-Date: 2024-12-10 01:42+0000\n" "PO-Revision-Date: 2024-02-20 08:49+0700\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" @@ -9926,6 +9926,9 @@ msgstr "% kháng phép %{spell}" msgid "% damage from Elemental spells" msgstr "% sát thương với phép Nguyên tố" +msgid "% damage from %{spell} spell" +msgstr "% sát thương từ phép %{spell}" + msgid "% chance to Dispel beneficial spells" msgstr "% cơ hội Xua tan các phép thuật có lợi" @@ -9992,21 +9995,52 @@ msgstr "Nguyên tố" msgid "No Morale" msgstr "Mất Tinh thần" -msgid "200% damage from Fire spells" +#, fuzzy +msgid "Earth creature" +msgstr "Chiêu mộ Quái" + +#, fuzzy +msgid "Air creature" +msgstr "Chiêu mộ Quái" + +#, fuzzy +msgid "Fire creature" +msgstr "Chiêu mộ Quái" + +#, fuzzy +msgid "Water creature" +msgstr "Hồ Nước" + +#, fuzzy +msgid "Double damage from Fire spells" msgstr "200% sát thương từ phép Lửa" -msgid "200% damage from Cold spells" +#, fuzzy +msgid "Double damage from Cold spells" msgstr "200% sát thương từ phép Lạnh" -msgid "% damage from %{spell} spell" -msgstr "% sát thương từ phép %{spell}" +#, fuzzy +msgid "Double damage from Earth creatures" +msgstr "Loại bỏ tất cả các phép thuật từ tất cả các sinh vật." -msgid "% immunity to " -msgstr "% kháng lại " +#, fuzzy +msgid "Double damage from Air creatures" +msgstr "Gấp đôi sát thương cho Bất tử" + +#, fuzzy +msgid "Double damage from Fire creatures" +msgstr "200% sát thương từ phép Lửa" + +#, fuzzy +msgid "Double damage from Water creatures" +msgstr "Loại bỏ tất cả các phép thuật từ tất cả các sinh vật." msgid "Lightning" msgstr "Sét" +msgid "% immunity to " +msgstr "% kháng lại " + msgid "% damage from " msgstr "% sát thương từ " From 80d8371ebae6ddae271bf3c7c89d0517cee562d9 Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Wed, 11 Dec 2024 14:43:48 +0300 Subject: [PATCH 27/30] Limit the use of VPATH for GNU Make (#9332) --- src/dist/engine/Makefile | 10 +++------- src/dist/fheroes2/Makefile | 12 ++++++------ src/dist/thirdparty/libsmacker/Makefile | 7 +------ src/dist/tools/Makefile | 14 +++----------- 4 files changed, 13 insertions(+), 30 deletions(-) diff --git a/src/dist/engine/Makefile b/src/dist/engine/Makefile index 2914164f2ba..5e696bd9513 100644 --- a/src/dist/engine/Makefile +++ b/src/dist/engine/Makefile @@ -22,20 +22,16 @@ ifndef FHEROES2_WITH_SYSTEM_SMACKER CCFLAGS := $(CCFLAGS) -I../../thirdparty/libsmacker endif -SOURCEROOT := ../../engine -SOURCEDIRS := $(SOURCEROOT) -SOURCES := $(wildcard $(SOURCEDIRS)/*.cpp) +SOURCEDIR := ../../engine .PHONY: all clean all: libengine.a -libengine.a: $(notdir $(patsubst %.cpp, %.o, $(SOURCES))) +libengine.a: $(notdir $(patsubst %.cpp, %.o, $(wildcard $(SOURCEDIR)/*.cpp))) $(AR) crvs $@ $^ -VPATH := $(SOURCEDIRS) - -%.o: %.cpp +%.o: $(SOURCEDIR)/%.cpp $(CXX) -c -MD $< $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) include $(wildcard *.d) diff --git a/src/dist/fheroes2/Makefile b/src/dist/fheroes2/Makefile index 19082a83023..8e6ce3d151a 100644 --- a/src/dist/fheroes2/Makefile +++ b/src/dist/fheroes2/Makefile @@ -18,11 +18,11 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### -LIBENGINE := ../engine/libengine.a +DEPLIBS := ../engine/libengine.a CCFLAGS := $(CCFLAGS) -I../../engine ifndef FHEROES2_WITH_SYSTEM_SMACKER -LIBENGINE := $(LIBENGINE) ../thirdparty/libsmacker/libsmacker.a +DEPLIBS := $(DEPLIBS) ../thirdparty/libsmacker/libsmacker.a CCFLAGS := $(CCFLAGS) -I../../thirdparty/libsmacker endif @@ -30,23 +30,23 @@ SOURCEROOT := ../../fheroes2 SOURCEDIRS := $(filter %/,$(wildcard $(SOURCEROOT)/*/)) SOURCES := $(wildcard $(SOURCEROOT)/*/*.cpp) +VPATH := $(SOURCEDIRS) + .PHONY: all pot clean all: fheroes2 pot pot: fheroes2.pot -fheroes2: $(notdir $(patsubst %.cpp, %.o, $(SOURCES))) $(LIBENGINE) +fheroes2: $(notdir $(patsubst %.cpp, %.o, $(SOURCES))) $(DEPLIBS) $(CXX) -o $@ $^ $(LIBS) $(LDFLAGS) fheroes2.pot: $(SOURCES) xgettext -d fheroes2 -C -F -k_ -k_n:1,2 -o fheroes2.pot $(sort $(SOURCES)) sed -i~ -e 's/, c-format//' fheroes2.pot -VPATH := $(SOURCEDIRS) - %.o: %.cpp - $(CXX) -c -MD $(addprefix -I, $(SOURCEDIRS)) $< $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) + $(CXX) -c -MD $< $(addprefix -I, $(SOURCEDIRS)) $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) include $(wildcard *.d) diff --git a/src/dist/thirdparty/libsmacker/Makefile b/src/dist/thirdparty/libsmacker/Makefile index 3a5786b254f..7ab8f0503a2 100644 --- a/src/dist/thirdparty/libsmacker/Makefile +++ b/src/dist/thirdparty/libsmacker/Makefile @@ -18,9 +18,6 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### -SOURCEROOT := ../../../thirdparty/libsmacker -SOURCEDIRS := $(SOURCEROOT) - .PHONY: all clean all: libsmacker.a @@ -28,9 +25,7 @@ all: libsmacker.a libsmacker.a: smacker.o $(AR) crvs $@ $^ -VPATH := $(SOURCEDIRS) - -smacker.o: smacker.c +smacker.o: ../../../thirdparty/libsmacker/smacker.c $(CC) -c -MD $< $(CCFLAGS) $(CFLAGS) $(CPPFLAGS) include $(wildcard *.d) diff --git a/src/dist/tools/Makefile b/src/dist/tools/Makefile index 403aced02c5..1179485f57a 100644 --- a/src/dist/tools/Makefile +++ b/src/dist/tools/Makefile @@ -20,23 +20,15 @@ TARGETS := 82m2wav bin2txt extractor h2dmgr icn2img pal2img til2img xmi2midi -LIBENGINE := ../engine/libengine.a -CCFLAGS := $(CCFLAGS) -I../../engine - -SOURCEROOT := ../../tools -SOURCEDIRS := $(SOURCEROOT) - .PHONY: all clean all: $(TARGETS) -$(TARGETS): %: %.o $(LIBENGINE) +$(TARGETS): %: %.o ../engine/libengine.a $(CXX) -o $@ $^ $(LIBS) $(LDFLAGS) -VPATH := $(SOURCEDIRS) - -%.o: %.cpp - $(CXX) -c -MD $< $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) +%.o: ../../tools/%.cpp + $(CXX) -c -MD $< -I../../engine $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) include $(wildcard *.d) From bf9b9af9097be2c889271dd9e41f6f5cab797f0f Mon Sep 17 00:00:00 2001 From: "Sergei Ivanov (Districh)" <113276641+Districh-ru@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:46:45 +0300 Subject: [PATCH 28/30] Fix a bug when picking up an object whose type does not match its sprite on the map (#9326) --- src/fheroes2/heroes/heroes_action.cpp | 44 +++++++++++++++++-------- src/fheroes2/maps/maps_tiles_helper.cpp | 19 ++++++++++- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/fheroes2/heroes/heroes_action.cpp b/src/fheroes2/heroes/heroes_action.cpp index 9ce1eb52778..0388cbffc13 100644 --- a/src/fheroes2/heroes/heroes_action.cpp +++ b/src/fheroes2/heroes/heroes_action.cpp @@ -221,18 +221,29 @@ namespace I.setRedraw( Interface::REDRAW_RADAR ); } - void runActionObjectFadeOutAnumation( const Maps::Tile & tile, const MP2::MapObjectType objectType ) + void runActionObjectFadeOutAnimation( const Maps::Tile & tile, const MP2::MapObjectType objectType ) { uint32_t objectUID = 0; - if ( Maps::getObjectTypeByIcn( tile.getMainObjectPart().icnType, tile.getMainObjectPart().icnIndex ) == objectType ) { + // There are original hacked maps (or made with exploitation of the original Editor's bugs) that have wrong object type in relation to its image sprite. + // So, we also search for object ID by checking if the object is an action type because one tile can have only one action object. + MP2::MapObjectType actualObjectType = MP2::OBJ_NONE; + const bool isActionObject = MP2::isInGameActionObject( objectType ); + + if ( const MP2::MapObjectType mainObjectType = Maps::getObjectTypeByIcn( tile.getMainObjectPart().icnType, tile.getMainObjectPart().icnIndex ); + mainObjectType == objectType || ( isActionObject && MP2::isInGameActionObject( mainObjectType ) ) ) { objectUID = tile.getMainObjectPart()._uid; + + actualObjectType = mainObjectType; } else { // In maps made by the original map editor the action object can be in the ground layer. for ( auto iter = tile.getGroundObjectParts().rbegin(); iter != tile.getGroundObjectParts().rend(); ++iter ) { - if ( Maps::getObjectTypeByIcn( iter->icnType, iter->icnIndex ) == objectType ) { + if ( const MP2::MapObjectType partObjectType = Maps::getObjectTypeByIcn( iter->icnType, iter->icnIndex ); + partObjectType == objectType || ( isActionObject && MP2::isInGameActionObject( partObjectType ) ) ) { objectUID = iter->_uid; + + actualObjectType = partObjectType; break; } } @@ -243,6 +254,12 @@ namespace Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.getGameArea().runSingleObjectAnimation( std::make_shared( objectUID, tile.GetIndex(), objectType ) ); + // If there is a map bug the Object Animation destructor is not able to properly remove this object from the map. + if ( actualObjectType != objectType && actualObjectType != MP2::OBJ_NONE ) { + // Remove an object by its actual sprite type. + removeObjectFromTileByType( tile, actualObjectType ); + } + // Update radar in the place of the removed object. I.getRadar().SetRenderArea( { Maps::GetPoint( tile.GetIndex() ), { 1, 1 } } ); I.setRedraw( Interface::REDRAW_RADAR ); @@ -259,7 +276,7 @@ namespace if ( remove && recruit == troop.GetCount() ) { Game::PlayPickupSound(); - runActionObjectFadeOutAnumation( tile, tile.getMainObjectType() ); + runActionObjectFadeOutAnimation( tile, tile.getMainObjectType() ); resetObjectMetadata( tile ); } @@ -453,7 +470,7 @@ namespace assert( tile.getMainObjectType() == MP2::OBJ_MONSTER ); - runActionObjectFadeOutAnumation( tile, MP2::OBJ_MONSTER ); + runActionObjectFadeOutAnimation( tile, MP2::OBJ_MONSTER ); resetObjectMetadata( tile ); } @@ -723,8 +740,6 @@ namespace Maps::Tile & tile = world.getTile( dst_index ); - Interface::AdventureMap & I = Interface::AdventureMap::Get(); - if ( objectType == MP2::OBJ_BOTTLE ) { const MapSign * sign = dynamic_cast( world.GetMapObject( dst_index ) ); fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), ( sign ? sign->message : "No message provided" ), Dialog::OK ); @@ -741,6 +756,7 @@ namespace else { const auto resource = funds.getFirstValidResource(); + Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.getStatusPanel().SetResource( resource.first, resource.second ); I.setRedraw( Interface::REDRAW_STATUS ); } @@ -750,7 +766,7 @@ namespace Game::PlayPickupSound(); - runActionObjectFadeOutAnumation( tile, objectType ); + runActionObjectFadeOutAnimation( tile, objectType ); resetObjectMetadata( tile ); } @@ -950,7 +966,7 @@ namespace Game::PlayPickupSound(); - runActionObjectFadeOutAnumation( tile, objectType ); + runActionObjectFadeOutAnimation( tile, objectType ); resetObjectMetadata( tile ); } @@ -1571,7 +1587,7 @@ namespace Game::PlayPickupSound(); - runActionObjectFadeOutAnumation( tile, objectType ); + runActionObjectFadeOutAnimation( tile, objectType ); resetObjectMetadata( tile ); } @@ -1754,7 +1770,7 @@ namespace assert( tile.getMainObjectType() == MP2::OBJ_ARTIFACT ); - runActionObjectFadeOutAnumation( tile, MP2::OBJ_ARTIFACT ); + runActionObjectFadeOutAnimation( tile, MP2::OBJ_ARTIFACT ); resetObjectMetadata( tile ); } @@ -1858,7 +1874,7 @@ namespace Game::PlayPickupSound(); - runActionObjectFadeOutAnumation( tile, objectType ); + runActionObjectFadeOutAnimation( tile, objectType ); resetObjectMetadata( tile ); } @@ -3308,7 +3324,7 @@ namespace _( "In a dazzling display of daring, you break into the local jail and free the hero imprisoned there, who, in return, pledges loyalty to your cause." ), Dialog::OK ); - runActionObjectFadeOutAnumation( tile, objectType ); + runActionObjectFadeOutAnimation( tile, objectType ); // TODO: add hero fading in animation together with jail animation. Heroes * prisoner = world.FromJailHeroes( dst_index ); @@ -3563,7 +3579,7 @@ namespace AudioManager::PlaySound( M82::KILLFADE ); - runActionObjectFadeOutAnumation( tile, objectType ); + runActionObjectFadeOutAnimation( tile, objectType ); } else { fheroes2::showStandardTextMessage( diff --git a/src/fheroes2/maps/maps_tiles_helper.cpp b/src/fheroes2/maps/maps_tiles_helper.cpp index 820980faf97..3680f1b862b 100644 --- a/src/fheroes2/maps/maps_tiles_helper.cpp +++ b/src/fheroes2/maps/maps_tiles_helper.cpp @@ -2260,7 +2260,24 @@ namespace Maps assert( isFirstLoad ); if ( tile.isWater() ) { - tile.setMainObjectType( MP2::OBJ_SEA_CHEST ); + // On original map "Alteris 2" there is a treasure chest placed on the water and there might be other maps with such bug. + // If there is a bug then remove of the MP2::OBJ_TREASURE_CHEST will return 'true' and we can replace it with a Sea Chest object. + if ( removeObjectFromTileByType( tile, MP2::OBJ_TREASURE_CHEST ) ) { + const auto & objects = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_WATER ); + + for ( size_t i = 0; i < objects.size(); ++i ) { + if ( objects[i].objectType == MP2::OBJ_SEA_CHEST ) { + const auto & objectInfo = Maps::getObjectInfo( Maps::ObjectGroup::ADVENTURE_WATER, static_cast( i ) ); + setObjectOnTile( tile, objectInfo, true ); + + break; + } + } + } + else { + tile.setMainObjectType( MP2::OBJ_SEA_CHEST ); + } + updateObjectInfoTile( tile, isFirstLoad ); return; } From 5c4979a5cf2269908223ed152b3587b04d784c22 Mon Sep 17 00:00:00 2001 From: Oleg Derevenetz Date: Wed, 11 Dec 2024 17:55:01 +0300 Subject: [PATCH 29/30] Reset the damage info popup when spellcast has been cancelled (#9340) --- src/fheroes2/battle/battle_interface.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fheroes2/battle/battle_interface.cpp b/src/fheroes2/battle/battle_interface.cpp index 07cf28a4758..c96ec7f6f08 100644 --- a/src/fheroes2/battle/battle_interface.cpp +++ b/src/fheroes2/battle/battle_interface.cpp @@ -2775,6 +2775,7 @@ void Battle::Interface::HumanTurn( const Unit & unit, Actions & actions ) } popup.reset(); + _currentUnit = nullptr; } @@ -3071,6 +3072,8 @@ void Battle::Interface::HumanCastSpellTurn( const Unit & /* unused */, Actions & // Cancel the spellcast if ( le.isMouseRightButtonPressed() || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { + popup.reset(); + humanturn_spell = Spell::NONE; _teleportSpellSrcIdx = -1; From 23aedf78c08395f5e942859524e1388962d72491 Mon Sep 17 00:00:00 2001 From: Ihar Hubchyk Date: Wed, 11 Dec 2024 22:55:52 +0800 Subject: [PATCH 30/30] Do not rebuild interface while changing settings (#9317) --- src/fheroes2/dialog/dialog_system_options.cpp | 13 ++++++++++--- src/fheroes2/editor/editor_options.cpp | 15 ++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/fheroes2/dialog/dialog_system_options.cpp b/src/fheroes2/dialog/dialog_system_options.cpp index b793a85add6..ce0d77511a8 100644 --- a/src/fheroes2/dialog/dialog_system_options.cpp +++ b/src/fheroes2/dialog/dialog_system_options.cpp @@ -416,13 +416,18 @@ namespace fheroes2 auto redrawAdventureMap = []() { Interface::AdventureMap & adventureMap = Interface::AdventureMap::Get(); - adventureMap.reset(); // Since radar interface has a restorer we must redraw it first to avoid the restorer do some nasty work. adventureMap.redraw( Interface::REDRAW_RADAR ); adventureMap.redraw( Interface::REDRAW_ALL & ( ~Interface::REDRAW_RADAR ) ); }; + auto rebildAdventureMap = [&redrawAdventureMap]() { + Interface::AdventureMap::Get().reset(); + + redrawAdventureMap(); + }; + DialogAction action = DialogAction::Configuration; while ( action != DialogAction::Close ) { @@ -445,13 +450,15 @@ namespace fheroes2 Dialog::OK ); } + // We can redraw only status window as it is the only place that has text but to be safe let's redraw everything. redrawAdventureMap(); + saveConfiguration = true; action = DialogAction::Configuration; break; } case DialogAction::Graphics: - saveConfiguration |= fheroes2::openGraphicsSettingsDialog( redrawAdventureMap ); + saveConfiguration |= fheroes2::openGraphicsSettingsDialog( rebildAdventureMap ); action = DialogAction::Configuration; break; @@ -466,7 +473,7 @@ namespace fheroes2 action = DialogAction::Configuration; break; case DialogAction::InterfaceSettings: - saveConfiguration |= fheroes2::openInterfaceSettingsDialog( redrawAdventureMap ); + saveConfiguration |= fheroes2::openInterfaceSettingsDialog( rebildAdventureMap ); action = DialogAction::Configuration; break; diff --git a/src/fheroes2/editor/editor_options.cpp b/src/fheroes2/editor/editor_options.cpp index 82ae55f9979..ff5d7e98b01 100644 --- a/src/fheroes2/editor/editor_options.cpp +++ b/src/fheroes2/editor/editor_options.cpp @@ -236,10 +236,9 @@ namespace Editor bool saveConfiguration = false; Settings & conf = Settings::Get(); - auto redrawEditorMap = [&conf]() { + auto redrawEditor = [&conf]() { Interface::EditorInterface & editorInterface = Interface::EditorInterface::Get(); - editorInterface.reset(); // Since the radar interface has a restorer we must redraw it first to avoid the restorer doing something nasty. editorInterface.redraw( Interface::REDRAW_RADAR ); @@ -251,6 +250,12 @@ namespace Editor editorInterface.redraw( redrawOptions & ( ~Interface::REDRAW_RADAR ) ); }; + auto rebuildEditor = [&redrawEditor]() { + Interface::EditorInterface::Get().reset(); + + redrawEditor(); + }; + DialogAction action = DialogAction::Configuration; while ( action != DialogAction::Close ) { @@ -273,13 +278,13 @@ namespace Editor Dialog::OK ); } - redrawEditorMap(); + redrawEditor(); saveConfiguration = true; action = DialogAction::Configuration; break; } case DialogAction::Graphics: - saveConfiguration |= fheroes2::openGraphicsSettingsDialog( redrawEditorMap ); + saveConfiguration |= fheroes2::openGraphicsSettingsDialog( rebuildEditor ); action = DialogAction::Configuration; break; @@ -310,7 +315,7 @@ namespace Editor conf.setEditorPassability( !conf.isEditorPassabilityEnabled() ); saveConfiguration = true; - redrawEditorMap(); + redrawEditor(); action = DialogAction::Configuration; break;