Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add swap army and swap artifacts to the heroes meeting screen #9387

Merged
merged 19 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading