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