diff --git a/src/fheroes2/agg/agg_image.cpp b/src/fheroes2/agg/agg_image.cpp index 04ca18ba258..2a1b64fed6d 100644 --- a/src/fheroes2/agg/agg_image.cpp +++ b/src/fheroes2/agg/agg_image.cpp @@ -3232,6 +3232,125 @@ namespace return true; } + case ICN::SWAP_ARROWS_CIRCULAR: { + const fheroes2::Sprite & original = fheroes2::AGG::GetICN( ICN::SWAP_ARROW_LEFT_TO_RIGHT, 0 ); + + const int32_t width = 47; + const int32_t height = 42; + fheroes2::Image out; + out.resize( width, height ); + out.reset(); + + // Rotate arrow heads. + const int32_t arrowTipToShaftLength = 18; + const int32_t arrowHeadWidth = 20; + + fheroes2::Image rotatedArrow; + rotatedArrow.resize( arrowHeadWidth, arrowTipToShaftLength ); + rotatedArrow.reset(); + for ( int x = 0; x < arrowTipToShaftLength; ++x ) { + for ( int y = 0; y < arrowHeadWidth; ++y ) { + Copy( original, x + ( original.width() - arrowTipToShaftLength ), y, rotatedArrow, arrowHeadWidth - y - 1, arrowTipToShaftLength - x - 1, 1, 1 ); + } + } + + fheroes2::Copy( Flip( rotatedArrow, true, true ), 0, 0, out, 0, 5, arrowHeadWidth, arrowTipToShaftLength ); + fheroes2::Copy( Flip( rotatedArrow, true, false ), 0, 0, out, 27, 19, arrowHeadWidth, arrowTipToShaftLength ); + + // Rotate arrow ends. + const int32_t arrowEndHeight = 11; + const int32_t arrowEndWidth = 9; + + rotatedArrow.resize( arrowEndWidth, arrowEndHeight ); + rotatedArrow.reset(); + + for ( int x = 0; x < arrowEndHeight; ++x ) { + for ( int y = 0; y < arrowEndWidth; ++y ) { + Copy( original, x + 2, y + 5, rotatedArrow, arrowEndWidth - y - 1, arrowEndHeight - x - 1, 1, 1 ); + } + } + + // Clean black corner. + fheroes2::Copy( original, 0, 0, rotatedArrow, 0, rotatedArrow.height() - 1, 1, 1 ); + + fheroes2::Copy( rotatedArrow, 0, 0, out, 32, 6, arrowEndWidth, arrowEndHeight ); + fheroes2::Copy( Flip( rotatedArrow, false, true ), 0, 0, out, 5, 25, arrowEndWidth, arrowEndHeight ); + + // Add straight shafts. + Copy( original, 5, 5, out, 13, 0, 21, 10 ); + Copy( original, 5, 5, out, 12, 32, 22, 10 ); + + // Lower arrow. + // Fix overlaps. + fheroes2::SetPixel( out, out.width() - 9, out.height() - 5, 119 ); + fheroes2::DrawLine( out, { out.width() - 13, out.height() - 6 }, { out.width() - 12, out.height() - 6 }, 109 ); + fheroes2::SetPixel( out, 12, out.height() - 10, 119 ); + fheroes2::SetPixel( out, out.width() - 14, out.height() - 10, 119 ); + + // Lower right corner. + Copy( original, 5, 10, out, out.width() - 13, out.height() - 5, 4, 5 ); + fheroes2::DrawLine( out, { out.width() - 6, out.height() - 4 }, { out.width() - 9, out.height() - 1 }, 59 ); + fheroes2::DrawLine( out, { out.width() - 6, out.height() - 5 }, { out.width() - 9, out.height() - 2 }, 59 ); + fheroes2::DrawLine( out, { out.width() - 7, out.height() - 5 }, { out.width() - 9, out.height() - 3 }, 129 ); + fheroes2::DrawLine( out, { out.width() - 8, out.height() - 5 }, { out.width() - 9, out.height() - 4 }, 123 ); + fheroes2::SetPixel( out, out.width() - 9, out.height() - 5, 119 ); + fheroes2::SetPixel( out, out.width() - 11, out.height() - 6, 112 ); + + // Lower left corner. + Copy( original, 5, 9, out, 9, out.height() - 6, 3, 6 ); + Copy( rotatedArrow, 0, 0, out, 5, out.height() - 7, 4, 2 ); + fheroes2::DrawLine( out, { 5, out.height() - 5 }, { 8, out.height() - 2 }, 129 ); + fheroes2::DrawLine( out, { 6, out.height() - 5 }, { 8, out.height() - 3 }, 123 ); + fheroes2::DrawLine( out, { 7, out.height() - 5 }, { 8, out.height() - 4 }, 119 ); + fheroes2::SetPixel( out, 8, out.height() - 5, 116 ); + fheroes2::SetPixel( out, 9, out.height() - 6, 112 ); + + // Fix shading. + fheroes2::DrawLine( out, { 14, out.height() - 15 }, { 14, out.height() - 11 }, 59 ); + + // Upper arrow. + // Upper left corner. + fheroes2::Copy( out, 15, 0, out, 9, 0, 4, 5 ); + fheroes2::Copy( out, 21, 5, out, 11, 5, 3, 2 ); + fheroes2::DrawLine( out, { 8, 1 }, { 5, 4 }, 129 ); + fheroes2::DrawLine( out, { 8, 2 }, { 6, 4 }, 119 ); + fheroes2::DrawLine( out, { 8, 3 }, { 7, 4 }, 114 ); + fheroes2::SetPixel( out, 8, 4, 112 ); + fheroes2::SetPixel( out, 9, 4, 112 ); + + // Upper right corner. + fheroes2::Copy( out, 21, 0, out, 33, 0, 3, 6 ); + fheroes2::Copy( out, 36, 7, out, 36, 5, 5, 1 ); + fheroes2::Copy( out, 37, 6, out, 37, 4, 3, 1 ); + fheroes2::Copy( out, 37, 6, out, 37, 4, 3, 1 ); + fheroes2::Copy( out, 34, 1, out, 36, 1, 1, 3 ); + fheroes2::DrawLine( out, { 36, 0 }, { 40, 4 }, 129 ); + fheroes2::DrawLine( out, { 37, 2 }, { 38, 3 }, 119 ); + fheroes2::DrawLine( out, { 36, 4 }, { 37, 3 }, 113 ); + + // Fix overlap. + fheroes2::DrawLine( out, { 33, 8 }, { 33, 9 }, 123 ); + fheroes2::SetPixel( out, 32, 9, 129 ); + fheroes2::SetPixel( out, 13, 9, 129 ); + + // Fix shading. + fheroes2::DrawLine( out, { 10, 22 }, { 18, 14 }, 59 ); + fheroes2::DrawLine( out, { 11, 22 }, { 19, 14 }, 59 ); + fheroes2::DrawLine( out, { 34, 17 }, { 40, 17 }, 59 ); + fheroes2::DrawLine( out, { 41, 5 }, { 41, 16 }, 59 ); + fheroes2::SetPixel( out, 40, 16, 59 ); + fheroes2::Copy( original, 0, 0, out, 1, 12, 4, 1 ); + fheroes2::Copy( original, 0, 0, out, 15, 12, 5, 1 ); + + // Make pressed state. + _icnVsSprite[id].resize( 2 ); + _icnVsSprite[id][0] = out; + _icnVsSprite[id][1] = _icnVsSprite[id][0]; + _icnVsSprite[id][1].setPosition( -1, 1 ); + ApplyPalette( _icnVsSprite[id][1], 4 ); + + return true; + } case ICN::EDITBTNS: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 35 ) { diff --git a/src/fheroes2/agg/icn.h b/src/fheroes2/agg/icn.h index a32cc6ff04c..b6356852afe 100644 --- a/src/fheroes2/agg/icn.h +++ b/src/fheroes2/agg/icn.h @@ -944,6 +944,7 @@ namespace ICN SILVER_GRADIENT_LARGE_FONT, SWAP_ARROW_LEFT_TO_RIGHT, SWAP_ARROW_RIGHT_TO_LEFT, + SWAP_ARROWS_CIRCULAR, COLOR_CURSOR_ADVENTURE_MAP, MONO_CURSOR_ADVENTURE_MAP, diff --git a/src/fheroes2/army/army.cpp b/src/fheroes2/army/army.cpp index aaca58f727c..479016311d1 100644 --- a/src/fheroes2/army/army.cpp +++ b/src/fheroes2/army/army.cpp @@ -237,19 +237,16 @@ Troops::~Troops() } ); } -void Troops::Assign( const Troop * itbeg, const Troop * itend ) +void Troops::Assign( const Troop * troopsBegin, const Troop * troopsEnd ) { Clean(); - iterator it = begin(); + for ( iterator iter = begin(); iter != end() && troopsBegin != troopsEnd; ++iter, ++troopsBegin ) { + assert( *iter != nullptr && troopsBegin != nullptr ); - while ( it != end() && itbeg != itend ) { - if ( itbeg->isValid() ) { - ( *it )->Set( *itbeg ); + if ( troopsBegin->isValid() ) { + ( *iter )->Set( *troopsBegin ); } - - ++it; - ++itbeg; } } @@ -257,24 +254,25 @@ void Troops::Assign( const Troops & troops ) { Clean(); - iterator it1 = begin(); - const_iterator it2 = troops.begin(); + for ( auto [thisIter, troopsIter] = std::make_pair( begin(), troops.begin() ); thisIter != end() && troopsIter != troops.end(); ++thisIter, ++troopsIter ) { + assert( *thisIter != nullptr && *troopsIter != nullptr ); - while ( it1 != end() && it2 != troops.end() ) { - if ( ( *it2 )->isValid() ) - ( *it1 )->Set( **it2 ); - ++it2; - ++it1; + if ( ( *troopsIter )->isValid() ) { + ( *thisIter )->Set( **troopsIter ); + } } } void Troops::Insert( const Troops & troops ) { - for ( const_iterator it = troops.begin(); it != troops.end(); ++it ) - push_back( new Troop( **it ) ); + for ( const Troop * troop : troops ) { + assert( troop != nullptr ); + + push_back( new Troop( *troop ) ); + } } -void Troops::PushBack( const Monster & mons, uint32_t count ) +void Troops::PushBack( const Monster & mons, const uint32_t count ) { push_back( new Troop( mons, count ) ); } @@ -1509,6 +1507,20 @@ void Army::MoveTroops( Army & from, const int monsterIdToKeep ) moveTroops( false ); } +void Army::SwapTroops( Army & from ) +{ + assert( this != &from ); + + // Heroes need to have at least one occupied slot in their army, so both armies should have at least one + // occupied slot, even if the exchange of armies takes place between a castle garrison and a hero. + assert( from.isValid() && isValid() ); + + const Troops temp = this->getTroops(); + + Assign( from ); + from.Assign( temp ); +} + uint32_t Army::ActionToSirens() const { uint32_t experience = 0; diff --git a/src/fheroes2/army/army.h b/src/fheroes2/army/army.h index 868c5ba920f..4fb36b03e3f 100644 --- a/src/fheroes2/army/army.h +++ b/src/fheroes2/army/army.h @@ -57,10 +57,10 @@ class Troops : protected std::vector Troops & operator=( const Troops & ) = delete; - void Assign( const Troop * itbeg, const Troop * itend ); - void Assign( const Troops & ); - void Insert( const Troops & ); - void PushBack( const Monster &, uint32_t ); + void Assign( const Troop * troopsBegin, const Troop * troopsEnd ); + void Assign( const Troops & troops ); + void Insert( const Troops & troops ); + void PushBack( const Monster & mons, const uint32_t count ); void PopBack(); size_t Size() const @@ -228,8 +228,12 @@ class Army final : public Troops, public Control void JoinStrongestFromArmy( Army & giver ); - // Implements the necessary logic to move unit stacks from army to army in the hero's meeting dialog and in the castle dialog + // Implements the necessary logic to move unit stacks from army to army in the heroes meeting dialog and in the castle dialog void MoveTroops( Army & from, const int monsterIdToKeep ); + // Implements the necessary logic to swap all unit stacks from an army to another army in the heroes meeting dialog and in the + // castle dialog - provided that there is at least one occupied slot in the castle garrison. It's the caller's responsibility + // to ensure that this is indeed the case. + void SwapTroops( Army & from ); void SetSpreadFormation( const bool spread ) { diff --git a/src/fheroes2/game/game_hotkeys.cpp b/src/fheroes2/game/game_hotkeys.cpp index 27af9193e54..be067217449 100644 --- a/src/fheroes2/game/game_hotkeys.cpp +++ b/src/fheroes2/game/game_hotkeys.cpp @@ -316,6 +316,8 @@ namespace = { Game::HotKeyCategory::ARMY, gettext_noop( "hotkey|upgrade troop" ), fheroes2::Key::KEY_U }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::ARMY_DISMISS )] = { Game::HotKeyCategory::ARMY, gettext_noop( "hotkey|dismiss hero or troop" ), fheroes2::Key::KEY_D }; + hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::ARMY_SWAP )] + = { Game::HotKeyCategory::ARMY, gettext_noop( "hotkey|exchange all troops" ), fheroes2::Key::KEY_S }; } std::string getHotKeyFileContent() diff --git a/src/fheroes2/game/game_hotkeys.h b/src/fheroes2/game/game_hotkeys.h index a128eb90fca..d0c58d702d8 100644 --- a/src/fheroes2/game/game_hotkeys.h +++ b/src/fheroes2/game/game_hotkeys.h @@ -163,6 +163,7 @@ namespace Game ARMY_JOIN_STACKS, ARMY_UPGRADE_TROOP, ARMY_DISMISS, + ARMY_SWAP, // WARNING! Put all new event only above this line. No adding in between. NO_EVENT, diff --git a/src/fheroes2/heroes/heroes_meeting.cpp b/src/fheroes2/heroes/heroes_meeting.cpp index c43e3c2ad7c..d5f36f7a621 100644 --- a/src/fheroes2/heroes/heroes_meeting.cpp +++ b/src/fheroes2/heroes/heroes_meeting.cpp @@ -346,11 +346,12 @@ void Heroes::MeetingDialog( Heroes & otherHero ) fheroes2::Blit( moveButtonBackground, 292, 270, display, cur_pt.x + 292, cur_pt.y + 270, 48, 44 ); // The original resources do not have such animated buttons so we have to create those. - fheroes2::ButtonSprite moveArmyToHero2 = createMoveButton( ICN::SWAP_ARROW_LEFT_TO_RIGHT, cur_pt.x + 298, cur_pt.y + 267, display ); - fheroes2::ButtonSprite moveArmyToHero1 = createMoveButton( ICN::SWAP_ARROW_RIGHT_TO_LEFT, cur_pt.x + 298, cur_pt.y + 290, display ); + fheroes2::ButtonSprite moveArmyToHero2 = createMoveButton( ICN::SWAP_ARROW_LEFT_TO_RIGHT, cur_pt.x + 126, cur_pt.y + 319, display ); + fheroes2::ButtonSprite moveArmyToHero1 = createMoveButton( ICN::SWAP_ARROW_RIGHT_TO_LEFT, cur_pt.x + 472, cur_pt.y + 319, display ); + fheroes2::ButtonSprite swapArmies = createMoveButton( ICN::SWAP_ARROWS_CIRCULAR, cur_pt.x + 297, cur_pt.y + 268, display ); - fheroes2::ImageRestorer armyCountBackgroundRestorerLeft( display, cur_pt.x + 36, cur_pt.y + 310, 223, 20 ); - fheroes2::ImageRestorer armyCountBackgroundRestorerRight( display, cur_pt.x + 381, cur_pt.y + 310, 223, 20 ); + fheroes2::ImageRestorer armyCountBackgroundRestorerLeft( display, cur_pt.x + 36, cur_pt.y + 311, 223, 8 ); + fheroes2::ImageRestorer armyCountBackgroundRestorerRight( display, cur_pt.x + 381, cur_pt.y + 311, 223, 8 ); // army dst_pt.x = cur_pt.x + 36; @@ -393,8 +394,9 @@ void Heroes::MeetingDialog( Heroes & otherHero ) selectArtifacts2.Redraw( display ); fheroes2::Blit( moveButtonBackground, 292, 363, display, cur_pt.x + 292, cur_pt.y + 363, 48, 44 ); - fheroes2::ButtonSprite moveArtifactsToHero2 = createMoveButton( ICN::SWAP_ARROW_LEFT_TO_RIGHT, cur_pt.x + 298, cur_pt.y + 361, display ); - fheroes2::ButtonSprite moveArtifactsToHero1 = createMoveButton( ICN::SWAP_ARROW_RIGHT_TO_LEFT, cur_pt.x + 298, cur_pt.y + 384, display ); + fheroes2::ButtonSprite moveArtifactsToHero2 = createMoveButton( ICN::SWAP_ARROW_LEFT_TO_RIGHT, cur_pt.x + 126, cur_pt.y + 426, display ); + fheroes2::ButtonSprite moveArtifactsToHero1 = createMoveButton( ICN::SWAP_ARROW_RIGHT_TO_LEFT, cur_pt.x + 472, cur_pt.y + 426, display ); + fheroes2::ButtonSprite swapArtifacts = createMoveButton( ICN::SWAP_ARROWS_CIRCULAR, cur_pt.x + 297, cur_pt.y + 361, display ); // button exit dst_pt.x = cur_pt.x + 280; @@ -403,8 +405,10 @@ void Heroes::MeetingDialog( Heroes & otherHero ) moveArmyToHero2.draw(); moveArmyToHero1.draw(); + swapArmies.draw(); moveArtifactsToHero2.draw(); moveArtifactsToHero1.draw(); + swapArtifacts.draw(); buttonExit.draw(); // Fade-in heroes meeting dialog. Use half fade if game resolution is not 640x480. @@ -428,9 +432,13 @@ void Heroes::MeetingDialog( Heroes & otherHero ) moveArmyToHero1.drawOnPress(); moveArmyToHero2.drawOnRelease(); } + else if ( le.isMouseLeftButtonPressedInArea( swapArmies.area() ) || HotKeyHoldEvent( Game::HotKeyEvent::ARMY_SWAP ) ) { + swapArmies.drawOnPress(); + } else { moveArmyToHero1.drawOnRelease(); moveArmyToHero2.drawOnRelease(); + swapArmies.drawOnRelease(); } if ( le.isMouseLeftButtonPressedInArea( moveArtifactsToHero2.area() ) ) { @@ -441,9 +449,13 @@ void Heroes::MeetingDialog( Heroes & otherHero ) moveArtifactsToHero1.drawOnPress(); moveArtifactsToHero2.drawOnRelease(); } + else if ( le.isMouseLeftButtonPressedInArea( swapArtifacts.area() ) ) { + swapArtifacts.drawOnPress(); + } else { moveArtifactsToHero1.drawOnRelease(); moveArtifactsToHero2.drawOnRelease(); + swapArtifacts.drawOnRelease(); } if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) @@ -616,6 +628,23 @@ void Heroes::MeetingDialog( Heroes & otherHero ) display.render(); } + else if ( le.MouseClickLeft( swapArmies.area() ) || HotKeyPressEvent( Game::HotKeyEvent::ARMY_SWAP ) ) { + GetArmy().SwapTroops( otherHero.GetArmy() ); + + armyCountBackgroundRestorerLeft.restore(); + armyCountBackgroundRestorerRight.restore(); + + selectArmy1.ResetSelected(); + selectArmy2.ResetSelected(); + + selectArmy1.Redraw( display ); + selectArmy2.Redraw( display ); + + moraleIndicator1.Redraw(); + moraleIndicator2.Redraw(); + + display.render(); + } else if ( le.MouseClickLeft( moveArtifactsToHero2.area() ) ) { moveArtifacts( GetBagArtifacts(), otherHero.GetBagArtifacts() ); @@ -650,6 +679,31 @@ void Heroes::MeetingDialog( Heroes & otherHero ) display.render(); } + else if ( le.MouseClickLeft( swapArtifacts.area() ) ) { + BagArtifacts temp; + + moveArtifacts( GetBagArtifacts(), temp ); + moveArtifacts( otherHero.GetBagArtifacts(), GetBagArtifacts() ); + moveArtifacts( temp, otherHero.GetBagArtifacts() ); + + selectArtifacts1.ResetSelected(); + selectArtifacts2.ResetSelected(); + + selectArtifacts1.Redraw( display ); + selectArtifacts2.Redraw( display ); + + backPrimary.restore(); + + fheroes2::RedrawPrimarySkillInfo( cur_pt, &primskill_bar1, &primskill_bar2 ); + + moraleIndicator1.Redraw(); + moraleIndicator2.Redraw(); + + luckIndicator1.Redraw(); + luckIndicator2.Redraw(); + + display.render(); + } if ( le.isMouseRightButtonPressedInArea( hero1Area ) ) { Dialog::QuickInfo( *this );