Skip to content

Commit

Permalink
Add swap army and swap artifacts to the heroes meeting screen (#9387)
Browse files Browse the repository at this point in the history
  • Loading branch information
zenseii authored Dec 30, 2024
1 parent f69408e commit 16f988b
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 29 deletions.
119 changes: 119 additions & 0 deletions src/fheroes2/agg/agg_image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand Down
1 change: 1 addition & 0 deletions src/fheroes2/agg/icn.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
48 changes: 30 additions & 18 deletions src/fheroes2/army/army.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,44 +237,42 @@ 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;
}
}

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 ) );
}
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 9 additions & 5 deletions src/fheroes2/army/army.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ class Troops : protected std::vector<Troop *>

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
Expand Down Expand Up @@ -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 )
{
Expand Down
2 changes: 2 additions & 0 deletions src/fheroes2/game/game_hotkeys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions src/fheroes2/game/game_hotkeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
66 changes: 60 additions & 6 deletions src/fheroes2/heroes/heroes_meeting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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() ) ) {
Expand All @@ -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() )
Expand Down Expand Up @@ -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() );

Expand Down Expand Up @@ -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 );
Expand Down

0 comments on commit 16f988b

Please sign in to comment.