1 /***************************************************************************
2  *   Copyright (C) 2009 by Andrey Afletdinov <fheroes2@gmail.com>          *
3  *                                                                         *
4  *   Part of the Free Heroes2 Engine:                                      *
5  *   http://sourceforge.net/projects/fheroes2                              *
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU General Public License as published by  *
9  *   the Free Software Foundation; either version 2 of the License, or     *
10  *   (at your option) any later version.                                   *
11  *                                                                         *
12  *   This program is distributed in the hope that it will be useful,       *
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
15  *   GNU General Public License for more details.                          *
16  *                                                                         *
17  *   You should have received a copy of the GNU General Public License     *
18  *   along with this program; if not, write to the                         *
19  *   Free Software Foundation, Inc.,                                       *
20  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
21  ***************************************************************************/
22 
23 #include <string>
24 
25 #include "agg_image.h"
26 #include "army_troop.h"
27 #include "battle_cell.h"
28 #include "castle.h"
29 #include "cursor.h"
30 #include "dialog.h"
31 #include "game.h"
32 #include "game_delays.h"
33 #include "icn.h"
34 #include "kingdom.h"
35 #include "monster_anim.h"
36 #include "resource.h"
37 #include "speed.h"
38 #include "text.h"
39 #include "translations.h"
40 #include "ui_text.h"
41 
42 namespace
43 {
HowManyRecruitMonster(const Castle & castle,Troops & tempArmy,const uint32_t dw,const Funds & add,Funds & res)44     uint32_t HowManyRecruitMonster( const Castle & castle, Troops & tempArmy, const uint32_t dw, const Funds & add, Funds & res )
45     {
46         const Monster ms( castle.GetRace(), castle.GetActualDwelling( dw ) );
47         if ( !tempArmy.CanJoinTroop( ms ) )
48             return 0;
49 
50         uint32_t count = castle.getMonstersInDwelling( dw );
51         payment_t payment;
52 
53         const Kingdom & kingdom = castle.GetKingdom();
54 
55         while ( count ) {
56             payment = ms.GetCost() * count;
57             res = payment;
58             payment += add;
59             if ( kingdom.AllowPayment( payment ) )
60                 break;
61             --count;
62         }
63 
64         if ( count > 0 ) {
65             tempArmy.JoinTroop( ms, count );
66         }
67 
68         return count;
69     }
70 
getPressedBuildingHotkey()71     building_t getPressedBuildingHotkey()
72     {
73         if ( HotKeyPressEvent( Game::EVENT_TOWN_DWELLING_LEVEL_1 ) ) {
74             return DWELLING_MONSTER1;
75         }
76         if ( HotKeyPressEvent( Game::EVENT_TOWN_DWELLING_LEVEL_2 ) ) {
77             return DWELLING_MONSTER2;
78         }
79         if ( HotKeyPressEvent( Game::EVENT_TOWN_DWELLING_LEVEL_3 ) ) {
80             return DWELLING_MONSTER3;
81         }
82         if ( HotKeyPressEvent( Game::EVENT_TOWN_DWELLING_LEVEL_4 ) ) {
83             return DWELLING_MONSTER4;
84         }
85         if ( HotKeyPressEvent( Game::EVENT_TOWN_DWELLING_LEVEL_5 ) ) {
86             return DWELLING_MONSTER5;
87         }
88         if ( HotKeyPressEvent( Game::EVENT_TOWN_DWELLING_LEVEL_6 ) ) {
89             return DWELLING_MONSTER6;
90         }
91 
92         return BUILD_NOTHING;
93     }
94 }
95 
OpenWell(void)96 void Castle::OpenWell( void )
97 {
98     fheroes2::Display & display = fheroes2::Display::instance();
99 
100     // setup cursor
101     const CursorRestorer cursorRestorer( true, Cursor::POINTER );
102 
103     const fheroes2::ImageRestorer restorer( display, ( display.width() - fheroes2::Display::DEFAULT_WIDTH ) / 2,
104                                             ( display.height() - fheroes2::Display::DEFAULT_HEIGHT ) / 2, fheroes2::Display::DEFAULT_WIDTH,
105                                             fheroes2::Display::DEFAULT_HEIGHT );
106 
107     const fheroes2::Point cur_pt( restorer.x(), restorer.y() );
108     fheroes2::Point dst_pt( cur_pt.x, cur_pt.y );
109 
110     // button exit
111     dst_pt.x = cur_pt.x + 578;
112     dst_pt.y = cur_pt.y + 461;
113     fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, ICN::WELLXTRA, 0, 1 );
114 
115     dst_pt.x = cur_pt.x;
116     dst_pt.y = cur_pt.y + 461;
117     fheroes2::Button buttonMax( dst_pt.x, dst_pt.y, ICN::BUYMAX, 0, 1 );
118 
119     const fheroes2::Rect rectMonster1( cur_pt.x + 20, cur_pt.y + 18, 288, 124 );
120     const fheroes2::Rect rectMonster2( cur_pt.x + 20, cur_pt.y + 168, 288, 124 );
121     const fheroes2::Rect rectMonster3( cur_pt.x + 20, cur_pt.y + 318, 288, 124 );
122     const fheroes2::Rect rectMonster4( cur_pt.x + 334, cur_pt.y + 18, 288, 124 );
123     const fheroes2::Rect rectMonster5( cur_pt.x + 334, cur_pt.y + 168, 288, 124 );
124     const fheroes2::Rect rectMonster6( cur_pt.x + 334, cur_pt.y + 318, 288, 124 );
125 
126     buttonExit.draw();
127 
128     std::vector<fheroes2::RandomMonsterAnimation> monsterAnimInfo;
129     monsterAnimInfo.emplace_back( Monster( race, DWELLING_MONSTER1 ) );
130     monsterAnimInfo.emplace_back( Monster( race, GetActualDwelling( DWELLING_MONSTER2 ) ) );
131     monsterAnimInfo.emplace_back( Monster( race, GetActualDwelling( DWELLING_MONSTER3 ) ) );
132     monsterAnimInfo.emplace_back( Monster( race, GetActualDwelling( DWELLING_MONSTER4 ) ) );
133     monsterAnimInfo.emplace_back( Monster( race, GetActualDwelling( DWELLING_MONSTER5 ) ) );
134     monsterAnimInfo.emplace_back( Monster( race, GetActualDwelling( DWELLING_MONSTER6 ) ) );
135 
136     WellRedrawInfoArea( cur_pt, monsterAnimInfo );
137 
138     buttonMax.draw();
139 
140     std::vector<u32> alldwellings;
141     alldwellings.reserve( 6 );
142     alldwellings.push_back( DWELLING_MONSTER6 );
143     alldwellings.push_back( DWELLING_MONSTER5 );
144     alldwellings.push_back( DWELLING_MONSTER4 );
145     alldwellings.push_back( DWELLING_MONSTER3 );
146     alldwellings.push_back( DWELLING_MONSTER2 );
147     alldwellings.push_back( DWELLING_MONSTER1 );
148 
149     display.render();
150 
151     LocalEvent & le = LocalEvent::Get();
152     while ( le.HandleEvents() ) {
153         le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease();
154 
155         le.MousePressLeft( buttonMax.area() ) ? buttonMax.drawOnPress() : buttonMax.drawOnRelease();
156         const building_t pressedHotkeyBuildingID = getPressedBuildingHotkey();
157 
158         if ( le.MouseClickLeft( buttonExit.area() ) || HotKeyCloseWindow ) {
159             break;
160         }
161         if ( le.MouseClickLeft( buttonMax.area() ) || HotKeyPressEvent( Game::EVENT_WELL_BUY_ALL_CREATURES ) ) {
162             std::vector<Troop> results;
163             Funds cur;
164             Funds total;
165             std::string str;
166 
167             const Troops & currentArmy = GetArmy();
168             Troops tempArmy( currentArmy );
169 
170             for ( const uint32_t dwellingType : alldwellings ) {
171                 const uint32_t canRecruit = HowManyRecruitMonster( *this, tempArmy, dwellingType, total, cur );
172                 if ( canRecruit != 0 ) {
173                     const Monster ms( race, GetActualDwelling( dwellingType ) );
174                     results.emplace_back( ms, canRecruit );
175                     total += cur;
176                     str.append( ms.GetPluralName( canRecruit ) );
177                     str.append( " - " );
178                     str.append( std::to_string( canRecruit ) );
179                     str += '\n';
180                 }
181             }
182 
183             if ( str.empty() ) {
184                 bool isCreaturePresent = false;
185                 for ( int i = 0; i < CASTLEMAXMONSTER; ++i ) {
186                     if ( dwelling[i] > 0 ) {
187                         isCreaturePresent = true;
188                         break;
189                     }
190                 }
191                 if ( isCreaturePresent ) {
192                     Dialog::Message( "", _( "Not enough resources to buy monsters." ), Font::BIG, Dialog::OK );
193                 }
194                 else {
195                     Dialog::Message( "", _( "No monsters available for purchase." ), Font::BIG, Dialog::OK );
196                 }
197             }
198             else if ( Dialog::YES == Dialog::ResourceInfo( _( "Buy Monsters" ), str, total, Dialog::YES | Dialog::NO ) ) {
199                 for ( const Troop & troop : results ) {
200                     RecruitMonster( troop, false );
201                 }
202             }
203         }
204         else if ( ( building & DWELLING_MONSTER1 ) && ( le.MouseClickLeft( rectMonster1 ) || pressedHotkeyBuildingID == DWELLING_MONSTER1 ) )
205             RecruitMonster( Dialog::RecruitMonster( Monster( race, DWELLING_MONSTER1 ), dwelling[0], true, 0 ) );
206         else if ( ( building & DWELLING_MONSTER2 ) && ( le.MouseClickLeft( rectMonster2 ) || pressedHotkeyBuildingID == DWELLING_MONSTER2 ) )
207             RecruitMonster( Dialog::RecruitMonster( Monster( race, GetActualDwelling( DWELLING_MONSTER2 ) ), dwelling[1], true, 0 ) );
208         else if ( ( building & DWELLING_MONSTER3 ) && ( le.MouseClickLeft( rectMonster3 ) || pressedHotkeyBuildingID == DWELLING_MONSTER3 ) )
209             RecruitMonster( Dialog::RecruitMonster( Monster( race, GetActualDwelling( DWELLING_MONSTER3 ) ), dwelling[2], true, 0 ) );
210         else if ( ( building & DWELLING_MONSTER4 ) && ( le.MouseClickLeft( rectMonster4 ) || pressedHotkeyBuildingID == DWELLING_MONSTER4 ) )
211             RecruitMonster( Dialog::RecruitMonster( Monster( race, GetActualDwelling( DWELLING_MONSTER4 ) ), dwelling[3], true, 0 ) );
212         else if ( ( building & DWELLING_MONSTER5 ) && ( le.MouseClickLeft( rectMonster5 ) || pressedHotkeyBuildingID == DWELLING_MONSTER5 ) )
213             RecruitMonster( Dialog::RecruitMonster( Monster( race, GetActualDwelling( DWELLING_MONSTER5 ) ), dwelling[4], true, 0 ) );
214         else if ( ( building & DWELLING_MONSTER6 ) && ( le.MouseClickLeft( rectMonster6 ) || pressedHotkeyBuildingID == DWELLING_MONSTER6 ) )
215             RecruitMonster( Dialog::RecruitMonster( Monster( race, GetActualDwelling( DWELLING_MONSTER6 ) ), dwelling[5], true, 0 ) );
216 
217         if ( Game::validateAnimationDelay( Game::CASTLE_UNIT_DELAY ) ) {
218             WellRedrawInfoArea( cur_pt, monsterAnimInfo );
219 
220             for ( size_t i = 0; i < monsterAnimInfo.size(); ++i )
221                 monsterAnimInfo[i].increment();
222 
223             buttonMax.draw();
224             display.render();
225         }
226     }
227 }
228 
WellRedrawInfoArea(const fheroes2::Point & cur_pt,const std::vector<fheroes2::RandomMonsterAnimation> & monsterAnimInfo) const229 void Castle::WellRedrawInfoArea( const fheroes2::Point & cur_pt, const std::vector<fheroes2::RandomMonsterAnimation> & monsterAnimInfo ) const
230 {
231     fheroes2::Display & display = fheroes2::Display::instance();
232     fheroes2::Blit( fheroes2::AGG::GetICN( ICN::WELLBKG, 0 ), display, cur_pt.x, cur_pt.y );
233 
234     fheroes2::Text text;
235     fheroes2::Point dst_pt;
236     fheroes2::Point pt;
237 
238     const fheroes2::FontType statsFontType{ fheroes2::FontSize::SMALL, fheroes2::FontColor::WHITE };
239 
240     const fheroes2::Sprite & button = fheroes2::AGG::GetICN( ICN::BUYMAX, 0 );
241     const fheroes2::Rect src_rt( 0, 461, button.width(), 19 );
242     fheroes2::Blit( fheroes2::AGG::GetICN( ICN::WELLBKG, 0 ), src_rt.x, src_rt.y, display, cur_pt.x + button.width() + 1, cur_pt.y + 461, src_rt.width, src_rt.height );
243     fheroes2::Fill( display, cur_pt.x + button.width(), cur_pt.y + 461, 1, src_rt.height, 0 );
244 
245     text.set( _( "Town Population Information and Statistics" ), fheroes2::FontType() );
246     dst_pt.x = cur_pt.x + 315 - text.width() / 2;
247     dst_pt.y = cur_pt.y + 464;
248     text.draw( dst_pt.x, dst_pt.y, display );
249 
250     u32 dw = DWELLING_MONSTER1;
251     size_t monsterId = 0u;
252 
253     while ( dw <= DWELLING_MONSTER6 ) {
254         bool present = false;
255         u32 dw_orig = DWELLING_MONSTER1;
256         u32 icnindex = 0;
257         u32 available = 0;
258 
259         switch ( dw ) {
260         case DWELLING_MONSTER1:
261             pt.x = cur_pt.x;
262             pt.y = cur_pt.y + 1;
263             present = ( DWELLING_MONSTER1 & building ) != 0;
264             icnindex = 19;
265             available = dwelling[0];
266             break;
267         case DWELLING_MONSTER2:
268             pt.x = cur_pt.x;
269             pt.y = cur_pt.y + 151;
270             present = ( DWELLING_MONSTER2 & building ) != 0;
271             dw_orig = GetActualDwelling( DWELLING_MONSTER2 );
272             icnindex = DWELLING_UPGRADE2 & building ? 25 : 20;
273             available = dwelling[1];
274             break;
275         case DWELLING_MONSTER3:
276             pt.x = cur_pt.x;
277             pt.y = cur_pt.y + 301;
278             present = ( DWELLING_MONSTER3 & building ) != 0;
279             dw_orig = GetActualDwelling( DWELLING_MONSTER3 );
280             icnindex = DWELLING_UPGRADE3 & building ? 26 : 21;
281             available = dwelling[2];
282             break;
283         case DWELLING_MONSTER4:
284             pt.x = cur_pt.x + 314;
285             pt.y = cur_pt.y + 1;
286             present = ( DWELLING_MONSTER4 & building ) != 0;
287             dw_orig = GetActualDwelling( DWELLING_MONSTER4 );
288             icnindex = DWELLING_UPGRADE4 & building ? 27 : 22;
289             available = dwelling[3];
290             break;
291         case DWELLING_MONSTER5:
292             pt.x = cur_pt.x + 314;
293             pt.y = cur_pt.y + 151;
294             present = ( DWELLING_MONSTER5 & building ) != 0;
295             dw_orig = GetActualDwelling( DWELLING_MONSTER5 );
296             icnindex = DWELLING_UPGRADE5 & building ? 28 : 23;
297             available = dwelling[4];
298             break;
299         case DWELLING_MONSTER6:
300             pt.x = cur_pt.x + 314;
301             pt.y = cur_pt.y + 301;
302             present = ( DWELLING_MONSTER6 & building ) != 0;
303             dw_orig = GetActualDwelling( DWELLING_MONSTER6 );
304             icnindex = DWELLING_UPGRADE7 & building ? 30 : ( DWELLING_UPGRADE6 & building ? 29 : 24 );
305             available = dwelling[5];
306             break;
307         default:
308             break;
309         }
310 
311         const Monster monster( race, dw_orig );
312 
313         // sprite
314         dst_pt.x = pt.x + 21;
315         dst_pt.y = pt.y + 35;
316         fheroes2::Blit( fheroes2::AGG::GetICN( ICN::Get4Building( race ), icnindex ), display, dst_pt.x, dst_pt.y );
317 
318         // monster dwelling name
319         text.set( GetStringBuilding( dw_orig, race ), statsFontType );
320         dst_pt.x = pt.x + 86 - text.width() / 2;
321         dst_pt.y = pt.y + 104;
322         text.draw( dst_pt.x, dst_pt.y, display );
323 
324         // creature name
325         text.set( monster.GetMultiName(), statsFontType );
326         dst_pt.x = pt.x + 122 - text.width() / 2;
327         dst_pt.y = pt.y + 19;
328         text.draw( dst_pt.x, dst_pt.y, display );
329 
330         // attack
331         std::string str;
332         str = _( "Attack" );
333         str += ": ";
334         str += std::to_string( monster.GetAttack() );
335 
336         text.set( str, statsFontType );
337 
338         const int32_t statsOffsetX = 269;
339         const int32_t statsInitialOffsetY = 22;
340         int32_t statsOffsetY = statsInitialOffsetY;
341 
342         dst_pt.x = pt.x + statsOffsetX - text.width() / 2;
343         dst_pt.y = pt.y + statsOffsetY;
344         text.draw( dst_pt.x, dst_pt.y, display );
345         statsOffsetY += text.height( text.width() );
346 
347         // defense
348         str = _( "Defense" );
349         str += ": ";
350         str += std::to_string( monster.GetDefense() );
351 
352         text.set( str, statsFontType );
353         dst_pt.x = pt.x + statsOffsetX - text.width() / 2;
354         dst_pt.y = pt.y + statsOffsetY;
355         text.draw( dst_pt.x, dst_pt.y, display );
356         statsOffsetY += text.height( text.width() );
357 
358         // damage
359         str = _( "Damg" );
360         str += ": ";
361 
362         const uint32_t monsterMinDamage = monster.GetDamageMin();
363         const uint32_t monsterMaxDamage = monster.GetDamageMax();
364 
365         str += std::to_string( monsterMinDamage );
366 
367         if ( monsterMinDamage != monsterMaxDamage ) {
368             str += '-';
369             str += std::to_string( monsterMaxDamage );
370         }
371 
372         text.set( str, statsFontType );
373         dst_pt.x = pt.x + statsOffsetX - text.width() / 2;
374         dst_pt.y = pt.y + statsOffsetY;
375         text.draw( dst_pt.x, dst_pt.y, display );
376         statsOffsetY += text.height( text.width() );
377 
378         // hp
379         str = _( "HP" );
380         str += ": ";
381         str += std::to_string( monster.GetHitPoints() );
382 
383         text.set( str, statsFontType );
384         dst_pt.x = pt.x + statsOffsetX - text.width() / 2;
385         dst_pt.y = pt.y + statsOffsetY;
386         text.draw( dst_pt.x, dst_pt.y, display );
387         statsOffsetY += 2 * ( text.height( text.width() ) ); // skip a line
388 
389         // speed
390         str = _( "Speed" );
391         str += ':';
392 
393         text.set( str, statsFontType );
394         dst_pt.x = pt.x + statsOffsetX - text.width() / 2;
395         dst_pt.y = pt.y + statsOffsetY;
396         text.draw( dst_pt.x, dst_pt.y, display );
397         statsOffsetY += text.height( text.width() );
398 
399         text.set( Speed::String( monster.GetSpeed() ), statsFontType );
400         dst_pt.x = pt.x + statsOffsetX - text.width() / 2;
401         dst_pt.y = pt.y + statsOffsetY;
402         text.draw( dst_pt.x, dst_pt.y, display );
403         statsOffsetY += 2 * ( text.height( text.width() ) ); // skip a line
404 
405         // growth and number available
406         if ( present ) {
407             uint32_t monsterGrown = monster.GetGrown();
408             monsterGrown += building & BUILD_WELL ? GetGrownWell() : 0;
409 
410             if ( DWELLING_MONSTER1 & dw ) {
411                 monsterGrown += building & BUILD_WEL2 ? GetGrownWel2() : 0;
412             }
413 
414             text.set( _( "Growth" ), statsFontType );
415             dst_pt.x = pt.x + statsOffsetX - text.width() / 2;
416             dst_pt.y = pt.y + statsOffsetY;
417             text.draw( dst_pt.x, dst_pt.y, display );
418             statsOffsetY += text.height( text.width() );
419 
420             str = "+ ";
421             str += std::to_string( monsterGrown );
422             str += " / ";
423             str += _( "week" );
424 
425             text.set( str, statsFontType );
426             dst_pt.x = pt.x + statsOffsetX - text.width() / 2;
427             dst_pt.y = pt.y + statsOffsetY;
428             text.draw( dst_pt.x, dst_pt.y, display );
429 
430             str = _( "Available" );
431             str += ':';
432 
433             text.set( str, statsFontType );
434             dst_pt.x = pt.x + 44;
435             dst_pt.y = pt.y + 122;
436             text.draw( dst_pt.x, dst_pt.y, display );
437 
438             text.set( std::to_string( available ), { fheroes2::FontSize::NORMAL, fheroes2::FontColor::YELLOW } );
439             dst_pt.x = pt.x + 129 - text.width() / 2;
440             dst_pt.y = pt.y + 120;
441             text.draw( dst_pt.x, dst_pt.y, display );
442         }
443 
444         // monster
445         const bool flipMonsterSprite = ( dw >= DWELLING_MONSTER4 );
446 
447         const fheroes2::Sprite & smonster = fheroes2::AGG::GetICN( monsterAnimInfo[monsterId].icnFile(), monsterAnimInfo[monsterId].frameId() );
448         if ( flipMonsterSprite )
449             dst_pt.x = pt.x + 193 - ( smonster.x() + smonster.width() ) + ( monster.isWide() ? CELLW / 2 : 0 ) + monsterAnimInfo[monsterId].offset();
450         else
451             dst_pt.x = pt.x + 193 + smonster.x() - ( monster.isWide() ? CELLW / 2 : 0 ) - monsterAnimInfo[monsterId].offset();
452 
453         dst_pt.y = pt.y + 124 + smonster.y();
454 
455         fheroes2::Point inPos( 0, 0 );
456         fheroes2::Point outPos( dst_pt.x, dst_pt.y );
457         fheroes2::Size inSize( smonster.width(), smonster.height() );
458 
459         if ( fheroes2::FitToRoi( smonster, inPos, display, outPos, inSize,
460                                  fheroes2::Rect( cur_pt.x, cur_pt.y, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ) ) ) {
461             fheroes2::Blit( smonster, inPos, display, outPos, inSize, flipMonsterSprite );
462         }
463 
464         dw <<= 1;
465         ++monsterId;
466     }
467 }
468