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 <algorithm>
24 #include <cassert>
25
26 #include "agg_image.h"
27 #include "castle.h"
28 #include "castle_building_info.h"
29 #include "castle_ui.h"
30 #include "game.h"
31 #include "game_delays.h"
32 #include "icn.h"
33 #include "race.h"
34 #include "settings.h"
35 #include "text.h"
36 #include "tools.h"
37
38 void CastleRedrawCurrentBuilding( const Castle & castle, const fheroes2::Point & dst_pt, const CastleDialog::CacheBuildings & orders,
39 const CastleDialog::FadeBuilding & alphaBuilding, const uint32_t animationIndex );
40 fheroes2::Rect CastleGetMaxArea( const Castle &, const fheroes2::Point & );
41
42 namespace
43 {
isBuildingConnectionNeeded(const Castle & castle,const uint32_t buildId,const bool constructionInProgress)44 bool isBuildingConnectionNeeded( const Castle & castle, const uint32_t buildId, const bool constructionInProgress )
45 {
46 const int race = castle.GetRace();
47
48 if ( race == Race::BARB ) {
49 if ( buildId & BUILD_MAGEGUILD ) {
50 const int mageGuildLevel = castle.GetLevelMageGuild();
51 if ( constructionInProgress ) {
52 return mageGuildLevel == 0 || buildId > ( BUILD_MAGEGUILD1 << ( mageGuildLevel - 1 ) );
53 }
54
55 assert( mageGuildLevel > 0 );
56 return buildId == ( BUILD_MAGEGUILD1 << ( mageGuildLevel - 1 ) );
57 }
58 else if ( buildId == BUILD_THIEVESGUILD ) {
59 return true;
60 }
61 }
62 else if ( race == Race::NECR ) {
63 if ( buildId == BUILD_CAPTAIN ) {
64 return true;
65 }
66 }
67
68 return false;
69 }
70
redrawBuildingConnection(const Castle & castle,const fheroes2::Point & position,const uint32_t buildId,const uint8_t alpha=255)71 void redrawBuildingConnection( const Castle & castle, const fheroes2::Point & position, const uint32_t buildId, const uint8_t alpha = 255 )
72 {
73 const fheroes2::Rect & roi = CastleGetMaxArea( castle, position );
74 const bool constructionInProgress = alpha < 255;
75
76 const int race = castle.GetRace();
77
78 if ( race == Race::BARB ) {
79 if ( buildId & BUILD_MAGEGUILD || buildId == BUILD_SPEC ) {
80 if ( buildId & BUILD_MAGEGUILD ) {
81 if ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( BUILD_SPEC ) ) )
82 return;
83 }
84 else if ( buildId == BUILD_SPEC ) {
85 if ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( BUILD_MAGEGUILD1 ) ) )
86 return;
87 }
88
89 const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::TWNBEXT2, 0 );
90 CastleDialog::RedrawBuildingSpriteToArea( sprite, position.x + sprite.x(), position.y + sprite.y(), roi, alpha );
91 }
92
93 if ( buildId == DWELLING_MONSTER3 || buildId == BUILD_THIEVESGUILD ) {
94 if ( buildId == DWELLING_MONSTER3 ) {
95 if ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( BUILD_THIEVESGUILD ) ) )
96 return;
97 }
98 else if ( buildId == BUILD_THIEVESGUILD ) {
99 if ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( DWELLING_MONSTER3 ) ) )
100 return;
101 }
102
103 const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::TWNBEXT3, 0 );
104 CastleDialog::RedrawBuildingSpriteToArea( sprite, position.x + sprite.x(), position.y + sprite.y(), roi, alpha );
105 }
106 }
107 else if ( race == Race::NECR ) {
108 if ( buildId == BUILD_CAPTAIN ) {
109 if ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( BUILD_CASTLE ) ) )
110 return;
111 }
112 else if ( buildId == BUILD_CASTLE ) {
113 if ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( BUILD_CAPTAIN ) ) )
114 return;
115 }
116
117 const fheroes2::Sprite & bridge = fheroes2::AGG::GetICN( ICN::NECROMANCER_CASTLE_CAPTAIN_QUARTERS_BRIDGE, 0 );
118 CastleDialog::RedrawBuildingSpriteToArea( bridge, position.x + bridge.x(), position.y + bridge.y(), roi, alpha );
119 }
120 }
121 }
122
RedrawBuildingSpriteToArea(const fheroes2::Sprite & sprite,s32 dst_x,s32 dst_y,const fheroes2::Rect & max,uint8_t alpha)123 void CastleDialog::RedrawBuildingSpriteToArea( const fheroes2::Sprite & sprite, s32 dst_x, s32 dst_y, const fheroes2::Rect & max, uint8_t alpha )
124 {
125 std::pair<fheroes2::Rect, fheroes2::Point> res = Fixed4Blit( fheroes2::Rect( dst_x, dst_y, sprite.width(), sprite.height() ), max );
126 fheroes2::AlphaBlit( sprite, res.first.x, res.first.y, fheroes2::Display::instance(), res.second.x, res.second.y, res.first.width, res.first.height, alpha );
127 }
128
CacheBuildings(const Castle & castle,const fheroes2::Point & top)129 CastleDialog::CacheBuildings::CacheBuildings( const Castle & castle, const fheroes2::Point & top )
130 {
131 const std::vector<building_t> ordersBuildings = fheroes2::getBuildingDrawingPriorities( castle.GetRace(), Settings::Get().CurrentFileInfo()._version );
132
133 for ( const building_t buildingId : ordersBuildings ) {
134 emplace_back( buildingId, fheroes2::getCastleBuildingArea( castle.GetRace(), buildingId ) + top );
135 }
136 }
137
StartFadeBuilding(const uint32_t build)138 void CastleDialog::FadeBuilding::StartFadeBuilding( const uint32_t build )
139 {
140 _alpha = 0;
141 _build = build;
142 }
143
UpdateFadeBuilding()144 bool CastleDialog::FadeBuilding::UpdateFadeBuilding()
145 {
146 if ( _alpha < 255 && Game::validateAnimationDelay( Game::CASTLE_BUILD_DELAY ) ) {
147 if ( _alpha < 255 - 15 ) {
148 _alpha += 15;
149 }
150 else {
151 _alpha = 255;
152 }
153 return true;
154 }
155 return false;
156 }
157
StopFadeBuilding()158 void CastleDialog::FadeBuilding::StopFadeBuilding()
159 {
160 if ( _build != BUILD_NOTHING ) {
161 _build = BUILD_NOTHING;
162 _alpha = 255;
163 }
164 }
165
RedrawAllBuilding(const Castle & castle,const fheroes2::Point & dst_pt,const CacheBuildings & orders,const CastleDialog::FadeBuilding & alphaBuilding,const uint32_t animationIndex)166 void CastleDialog::RedrawAllBuilding( const Castle & castle, const fheroes2::Point & dst_pt, const CacheBuildings & orders,
167 const CastleDialog::FadeBuilding & alphaBuilding, const uint32_t animationIndex )
168 {
169 CastleRedrawCurrentBuilding( castle, dst_pt, orders, alphaBuilding, animationIndex );
170 fheroes2::drawCastleName( castle, fheroes2::Display::instance(), dst_pt );
171 }
172
CastleRedrawCurrentBuilding(const Castle & castle,const fheroes2::Point & dst_pt,const CastleDialog::CacheBuildings & orders,const CastleDialog::FadeBuilding & fadeBuilding,const uint32_t animationIndex)173 void CastleRedrawCurrentBuilding( const Castle & castle, const fheroes2::Point & dst_pt, const CastleDialog::CacheBuildings & orders,
174 const CastleDialog::FadeBuilding & fadeBuilding, const uint32_t animationIndex )
175 {
176 fheroes2::Display & display = fheroes2::Display::instance();
177
178 int townIcnId = -1;
179
180 switch ( castle.GetRace() ) {
181 case Race::KNGT:
182 townIcnId = ICN::TOWNBKG0;
183 break;
184 case Race::BARB:
185 townIcnId = ICN::TOWNBKG1;
186 break;
187 case Race::SORC:
188 townIcnId = ICN::TOWNBKG2;
189 break;
190 case Race::WRLK:
191 townIcnId = ICN::TOWNBKG3;
192 break;
193 case Race::WZRD:
194 townIcnId = ICN::TOWNBKG4;
195 break;
196 case Race::NECR:
197 townIcnId = ICN::TOWNBKG5;
198 break;
199 default:
200 break;
201 }
202
203 const fheroes2::Rect max = CastleGetMaxArea( castle, dst_pt );
204
205 if ( townIcnId != -1 ) {
206 // We shouldn't Blit this image. This is a background image so a normal Copy is enough.
207 const fheroes2::Sprite & townbkg = fheroes2::AGG::GetICN( townIcnId, 0 );
208 fheroes2::Copy( townbkg, 0, 0, display, dst_pt.x, dst_pt.y, townbkg.width(), townbkg.height() );
209 }
210
211 if ( Race::BARB == castle.GetRace() ) {
212 const fheroes2::Sprite & sprite0 = fheroes2::AGG::GetICN( ICN::TWNBEXT1, 1 + animationIndex % 5 );
213 fheroes2::Blit( sprite0, display, dst_pt.x + sprite0.x(), dst_pt.y + sprite0.y() );
214 }
215
216 // Bay animation
217 if ( Race::WZRD == castle.GetRace() || ( !castle.isBuild( BUILD_SHIPYARD ) && castle.HaveNearlySea() ) ) {
218 int bayIcnId = 0;
219 const uint32_t bayExtraIndex = 1 + animationIndex % 5;
220
221 switch ( castle.GetRace() ) {
222 case Race::KNGT:
223 bayIcnId = ICN::TWNKEXT0;
224 break;
225 case Race::BARB:
226 bayIcnId = ICN::TWNBEXT0;
227 break;
228 case Race::SORC:
229 bayIcnId = ICN::TWNSEXT0;
230 break;
231 case Race::NECR:
232 bayIcnId = ICN::TWNNEXT0;
233 break;
234 case Race::WRLK:
235 bayIcnId = ICN::TWNWEXT0;
236 break;
237 case Race::WZRD:
238 bayIcnId = ICN::TWNZEXT0;
239 break;
240 default:
241 // Did you add a new race? Add the logic for it!
242 assert( 0 );
243 break;
244 }
245
246 const fheroes2::Sprite & bayBaseSprite = fheroes2::AGG::GetICN( bayIcnId, 0 );
247 const fheroes2::Sprite & bayExtraSprite = fheroes2::AGG::GetICN( bayIcnId, bayExtraIndex );
248
249 CastleDialog::RedrawBuildingSpriteToArea( bayBaseSprite, dst_pt.x + bayBaseSprite.x(), dst_pt.y + bayBaseSprite.y(), max );
250 CastleDialog::RedrawBuildingSpriteToArea( bayExtraSprite, dst_pt.x + bayExtraSprite.x(), dst_pt.y + bayExtraSprite.y(), max );
251 }
252
253 // redraw all builds
254 if ( BUILD_NOTHING == fadeBuilding.GetBuild() ) {
255 for ( auto it = orders.cbegin(); it != orders.cend(); ++it ) {
256 const uint32_t currentBuildId = it->id;
257 if ( castle.isBuild( currentBuildId ) ) {
258 CastleDialog::CastleRedrawBuilding( castle, dst_pt, currentBuildId, animationIndex );
259 CastleDialog::CastleRedrawBuildingExtended( castle, dst_pt, currentBuildId, animationIndex );
260 if ( isBuildingConnectionNeeded( castle, currentBuildId, false ) ) {
261 redrawBuildingConnection( castle, dst_pt, currentBuildId );
262 }
263 }
264 }
265 }
266 // redraw build with alpha
267 else if ( orders.cend() != std::find( orders.cbegin(), orders.cend(), fadeBuilding.GetBuild() ) ) {
268 for ( auto it = orders.cbegin(); it != orders.cend(); ++it ) {
269 const uint32_t currentBuildId = it->id;
270
271 if ( castle.isBuild( currentBuildId ) ) {
272 CastleDialog::CastleRedrawBuilding( castle, dst_pt, currentBuildId, animationIndex );
273 if ( currentBuildId == BUILD_SHIPYARD && fadeBuilding.GetBuild() == BUILD_SHIPYARD ) {
274 CastleDialog::CastleRedrawBuildingExtended( castle, dst_pt, currentBuildId, animationIndex, fadeBuilding.GetAlpha() );
275 }
276 else {
277 CastleDialog::CastleRedrawBuildingExtended( castle, dst_pt, currentBuildId, animationIndex );
278 }
279 if ( isBuildingConnectionNeeded( castle, currentBuildId, false ) ) {
280 redrawBuildingConnection( castle, dst_pt, fadeBuilding.GetBuild(), fadeBuilding.GetAlpha() );
281 redrawBuildingConnection( castle, dst_pt, currentBuildId );
282 }
283 }
284 else if ( currentBuildId == fadeBuilding.GetBuild() ) {
285 CastleDialog::CastleRedrawBuilding( castle, dst_pt, currentBuildId, animationIndex, fadeBuilding.GetAlpha() );
286 CastleDialog::CastleRedrawBuildingExtended( castle, dst_pt, currentBuildId, animationIndex, fadeBuilding.GetAlpha() );
287 if ( isBuildingConnectionNeeded( castle, currentBuildId, true ) ) {
288 redrawBuildingConnection( castle, dst_pt, currentBuildId, fadeBuilding.GetAlpha() );
289 }
290 }
291 }
292 }
293 }
294
CastleRedrawBuilding(const Castle & castle,const fheroes2::Point & dst_pt,u32 build,u32 frame,uint8_t alpha)295 void CastleDialog::CastleRedrawBuilding( const Castle & castle, const fheroes2::Point & dst_pt, u32 build, u32 frame, uint8_t alpha )
296 {
297 if ( build == BUILD_TENT ) // we don't need to draw a tent as it's on the background image
298 return;
299
300 const fheroes2::Rect max = CastleGetMaxArea( castle, dst_pt );
301
302 // correct build
303 switch ( build ) {
304 case DWELLING_MONSTER2:
305 case DWELLING_MONSTER3:
306 case DWELLING_MONSTER4:
307 case DWELLING_MONSTER5:
308 case DWELLING_MONSTER6:
309 build = castle.GetActualDwelling( build );
310 break;
311
312 default:
313 break;
314 }
315
316 const int race = castle.GetRace();
317 const int icn = Castle::GetICNBuilding( build, race );
318 u32 index = 0;
319
320 // correct index (mage guild)
321 switch ( build ) {
322 case BUILD_MAGEGUILD1:
323 if ( castle.GetLevelMageGuild() > 1 )
324 return;
325 index = 0;
326 break;
327 case BUILD_MAGEGUILD2:
328 if ( castle.GetLevelMageGuild() > 2 )
329 return;
330 index = Race::NECR == race ? 6 : 1;
331 break;
332 case BUILD_MAGEGUILD3:
333 if ( castle.GetLevelMageGuild() > 3 )
334 return;
335 index = Race::NECR == race ? 12 : 2;
336 break;
337 case BUILD_MAGEGUILD4:
338 if ( castle.GetLevelMageGuild() > 4 )
339 return;
340 index = Race::NECR == race ? 18 : 3;
341 break;
342 case BUILD_MAGEGUILD5:
343 index = Race::NECR == race ? 24 : 4;
344 break;
345 default:
346 break;
347 }
348
349 if ( icn != ICN::UNKNOWN ) {
350 // simple first sprite
351 const fheroes2::Sprite & sprite1 = fheroes2::AGG::GetICN( icn, index );
352
353 CastleDialog::RedrawBuildingSpriteToArea( sprite1, dst_pt.x + sprite1.x(), dst_pt.y + sprite1.y(), max, alpha );
354
355 // Special case: Knight castle's flags are overlapped by Right Turret so we need to draw flags after drawing the Turret.
356 const bool knightCastleCase = ( race == Race::KNGT && castle.isBuild( BUILD_RIGHTTURRET ) && castle.isBuild( BUILD_CASTLE ) );
357 if ( knightCastleCase && build == BUILD_CASTLE ) {
358 // Do not draw flags.
359 return;
360 }
361
362 // second anime sprite
363 if ( const u32 index2 = ICN::AnimationFrame( icn, index, frame ) ) {
364 const fheroes2::Sprite & sprite2 = fheroes2::AGG::GetICN( icn, index2 );
365
366 CastleDialog::RedrawBuildingSpriteToArea( sprite2, dst_pt.x + sprite2.x(), dst_pt.y + sprite2.y(), max, alpha );
367 }
368
369 if ( knightCastleCase && build == BUILD_RIGHTTURRET ) {
370 // Draw Castle's flags after the Turret.
371 const int castleIcn = Castle::GetICNBuilding( BUILD_CASTLE, race );
372 const uint32_t flagAnimFrame = ICN::AnimationFrame( castleIcn, index, frame );
373 if ( flagAnimFrame > 0 ) {
374 const fheroes2::Sprite & castleFlagSprite = fheroes2::AGG::GetICN( castleIcn, flagAnimFrame );
375
376 CastleDialog::RedrawBuildingSpriteToArea( castleFlagSprite, dst_pt.x + castleFlagSprite.x(), dst_pt.y + castleFlagSprite.y(), max, alpha );
377 }
378 }
379 }
380 }
381
CastleRedrawBuildingExtended(const Castle & castle,const fheroes2::Point & dst_pt,u32 build,u32 frame,uint8_t alpha)382 void CastleDialog::CastleRedrawBuildingExtended( const Castle & castle, const fheroes2::Point & dst_pt, u32 build, u32 frame, uint8_t alpha )
383 {
384 if ( build == BUILD_TENT ) // we don't need to draw a tent as it's on the background image
385 return;
386
387 const fheroes2::Rect max = CastleGetMaxArea( castle, dst_pt );
388 int icn = Castle::GetICNBuilding( build, castle.GetRace() );
389
390 // shipyard
391 if ( BUILD_SHIPYARD == build ) {
392 // boat
393 if ( castle.PresentBoat() ) {
394 const int icn2 = Castle::GetICNBoat( castle.GetRace() );
395
396 const fheroes2::Sprite & sprite40 = fheroes2::AGG::GetICN( icn2, 0 );
397 CastleDialog::RedrawBuildingSpriteToArea( sprite40, dst_pt.x + sprite40.x(), dst_pt.y + sprite40.y(), max, alpha );
398
399 if ( const u32 index2 = ICN::AnimationFrame( icn2, 0, frame ) ) {
400 const fheroes2::Sprite & sprite41 = fheroes2::AGG::GetICN( icn2, index2 );
401 CastleDialog::RedrawBuildingSpriteToArea( sprite41, dst_pt.x + sprite41.x(), dst_pt.y + sprite41.y(), max, alpha );
402 }
403 }
404 else {
405 if ( const u32 index2 = ICN::AnimationFrame( icn, 0, frame ) ) {
406 const fheroes2::Sprite & sprite3 = fheroes2::AGG::GetICN( icn, index2 );
407 CastleDialog::RedrawBuildingSpriteToArea( sprite3, dst_pt.x + sprite3.x(), dst_pt.y + sprite3.y(), max, alpha );
408 }
409 }
410 }
411 else if ( Race::SORC == castle.GetRace() && BUILD_WEL2 == build ) { // sorc and anime wel2 or statue
412 const int icn2 = castle.isBuild( BUILD_STATUE ) ? ICN::TWNSEXT1 : icn;
413
414 const fheroes2::Sprite & sprite20 = fheroes2::AGG::GetICN( icn2, 0 );
415 CastleDialog::RedrawBuildingSpriteToArea( sprite20, dst_pt.x + sprite20.x(), dst_pt.y + sprite20.y(), max, alpha );
416
417 if ( const u32 index2 = ICN::AnimationFrame( icn2, 0, frame ) ) {
418 const fheroes2::Sprite & sprite21 = fheroes2::AGG::GetICN( icn2, index2 );
419 CastleDialog::RedrawBuildingSpriteToArea( sprite21, dst_pt.x + sprite21.x(), dst_pt.y + sprite21.y(), max, alpha );
420 }
421 }
422 else if ( castle.GetRace() == Race::KNGT && BUILD_WEL2 == build && !castle.isBuild( BUILD_CASTLE ) ) {
423 const fheroes2::Sprite & rightFarm = fheroes2::AGG::GetICN( ICN::KNIGHT_CASTLE_RIGHT_FARM, 0 );
424 const fheroes2::Sprite & leftFarm = fheroes2::AGG::GetICN( ICN::KNIGHT_CASTLE_LEFT_FARM, 0 );
425 CastleDialog::RedrawBuildingSpriteToArea( leftFarm, dst_pt.x + rightFarm.x() - leftFarm.width(), dst_pt.y + rightFarm.y(), max, alpha );
426 }
427 else if ( castle.GetRace() == Race::BARB && BUILD_CAPTAIN == build && !castle.isBuild( BUILD_CASTLE ) ) {
428 const fheroes2::Sprite & rightCaptainQuarters = fheroes2::AGG::GetICN( ICN::TWNBCAPT, 0 );
429 const fheroes2::Sprite & leftCaptainQuarters = fheroes2::AGG::GetICN( ICN::BARBARIAN_CASTLE_CAPTAIN_QUARTERS_LEFT_SIDE, 0 );
430 CastleDialog::RedrawBuildingSpriteToArea( leftCaptainQuarters, dst_pt.x + rightCaptainQuarters.x() - leftCaptainQuarters.width() + leftCaptainQuarters.x(),
431 dst_pt.y + rightCaptainQuarters.y() + leftCaptainQuarters.y(), max, alpha );
432 }
433 }
434
CastleGetMaxArea(const Castle & castle,const fheroes2::Point & top)435 fheroes2::Rect CastleGetMaxArea( const Castle & castle, const fheroes2::Point & top )
436 {
437 fheroes2::Rect res( top.x, top.y, 0, 0 );
438
439 int townIcnId = -1;
440
441 switch ( castle.GetRace() ) {
442 case Race::KNGT:
443 townIcnId = ICN::TOWNBKG0;
444 break;
445 case Race::BARB:
446 townIcnId = ICN::TOWNBKG1;
447 break;
448 case Race::SORC:
449 townIcnId = ICN::TOWNBKG2;
450 break;
451 case Race::WRLK:
452 townIcnId = ICN::TOWNBKG3;
453 break;
454 case Race::WZRD:
455 townIcnId = ICN::TOWNBKG4;
456 break;
457 case Race::NECR:
458 townIcnId = ICN::TOWNBKG5;
459 break;
460 default:
461 return res;
462 }
463
464 const fheroes2::Sprite & townbkg = fheroes2::AGG::GetICN( townIcnId, 0 );
465 res.width = townbkg.width();
466 res.height = townbkg.height();
467
468 return res;
469 }
470