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