1 /*
2  *  This file is part of Dune Legacy.
3  *
4  *  Dune Legacy is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  Dune Legacy is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with Dune Legacy.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <Menu/MapChoice.h>
19 
20 #include <globals.h>
21 
22 #include <FileClasses/FileManager.h>
23 #include <FileClasses/GFXManager.h>
24 #include <FileClasses/TextManager.h>
25 #include <FileClasses/INIFile.h>
26 #include <FileClasses/music/MusicPlayer.h>
27 
28 #include <misc/string_util.h>
29 #include <misc/exceptions.h>
30 #include <misc/format.h>
31 
32 #include <sand.h>
33 
MapChoice(int newHouse,unsigned int lastMission,Uint32 oldAlreadyPlayedRegions)34 MapChoice::MapChoice(int newHouse, unsigned int lastMission, Uint32 oldAlreadyPlayedRegions) : MenuBase() {
35     disableQuiting(true);
36     selectedRegion = -1;
37 
38     bFastBlending = false;
39     curHouse2Blit = 0;
40     curRegion2Blit = 0;
41     curBlendBlitter = nullptr;
42     lastScenario = (lastMission + 1)/3 + 1;
43     alreadyPlayedRegions = oldAlreadyPlayedRegions;
44     house = newHouse;
45 
46     // set up window
47     SDL_Texture *pBackground = pGFXManager->getUIGraphic(UI_MapChoiceScreen, house);
48     setBackground(pBackground, false);
49     resize(getTextureSize(pBackground));
50 
51     centerAreaRect.x = getRendererWidth()/2 - 320;
52     centerAreaRect.y = getRendererHeight()/2 - 200;
53     centerAreaRect.w = 640;
54     centerAreaRect.h = 400;
55 
56     msgticker.resize(640,30);
57 
58     // load all data from ini
59     loadINI();
60 
61     int numSelectableRegions = 0;
62     int numRegions = 0;
63     for(int i = 0; i < 4; i++) {
64         int regionNum = group[lastScenario].attackRegion[i].regionNum;
65         if(regionNum > 0) {
66             numRegions++;
67             if((alreadyPlayedRegions & (1 << regionNum)) == 0) {
68                 numSelectableRegions++;
69             }
70         }
71     }
72 
73     if(numSelectableRegions < numRegions) {
74         // we already were on this screen
75         mapSurface = nullptr;
76         mapTexture = nullptr;
77         mapChoiceState = MAPCHOICESTATE_ARROWS;
78         createMapSurfaceWithPieces(lastScenario+1);
79     } else {
80 
81         if(lastScenario == 1) {
82             // first time we're on the map choice screen
83 
84             // create black rectangle
85             mapSurface = convertSurfaceToDisplayFormat(copySurface(pGFXManager->getUIGraphicSurface(UI_MapChoicePlanet)), true);
86             SDL_Rect dest = { 16, 48, 608, 240 };
87             SDL_FillRect(mapSurface, &dest, COLOR_BLACK);
88             mapTexture = SDL_CreateTexture(renderer, SCREEN_FORMAT, SDL_TEXTUREACCESS_STREAMING, mapSurface->w, mapSurface->h);
89             SDL_SetTextureBlendMode(mapTexture, SDL_BLENDMODE_BLEND);
90 
91             mapChoiceState = MAPCHOICESTATE_FADEINPLANET;
92 
93             msgticker.addMessage(_("@DUNE.ENG|283#Three Houses have come to Dune..."));
94             msgticker.addMessage(_("@DUNE.ENG|284#...to take control of the land..."));
95             msgticker.addMessage(_("@DUNE.ENG|285#...that has become divided."));
96         } else {
97             mapSurface = nullptr;
98             mapTexture = nullptr;
99             mapChoiceState = MAPCHOICESTATE_BLENDING;
100             createMapSurfaceWithPieces(lastScenario);
101         }
102     }
103 
104     if(numSelectableRegions == 0) {
105         // reset all selectable regions
106         for(int i = 0; i < 4; i++) {
107             int regionNum = group[lastScenario].attackRegion[i].regionNum;
108             if(regionNum > 0) {
109                 alreadyPlayedRegions &= ~(1 << regionNum);
110             }
111         }
112     }
113 }
114 
~MapChoice()115 MapChoice::~MapChoice() {
116     delete curBlendBlitter;
117     curBlendBlitter = nullptr;
118 
119     SDL_FreeSurface(mapSurface);
120     mapSurface = nullptr;
121 
122     SDL_DestroyTexture(mapTexture);
123     mapTexture = nullptr;
124 }
125 
showMenu()126 int MapChoice::showMenu()
127 {
128     musicPlayer->changeMusic(MUSIC_MAPCHOICE);
129 
130     return MenuBase::showMenu();
131 }
132 
drawSpecificStuff()133 void MapChoice::drawSpecificStuff() {
134     SDL_UpdateTexture(mapTexture, nullptr, mapSurface->pixels, mapSurface->pitch);
135     SDL_RenderCopy(renderer, mapTexture, nullptr, &centerAreaRect);
136 
137     switch(mapChoiceState) {
138 
139         case MAPCHOICESTATE_FADEINPLANET: {
140             if(curBlendBlitter == nullptr) {
141                 SDL_Surface* pSurface = convertSurfaceToDisplayFormat(pGFXManager->getUIGraphicSurface(UI_MapChoicePlanet), false);
142                 SDL_Rect dest = { 0, 0, getWidth(pSurface), getHeight(pSurface) };
143                 curBlendBlitter = new BlendBlitter(pSurface, true, mapSurface, dest);
144             }
145 
146             if(curBlendBlitter != nullptr) {
147                 int numSteps = bFastBlending ? 8 : 1;
148 
149                 for(int i = 0; i < numSteps; i++) {
150                     if(curBlendBlitter->nextStep() == 0) {
151                         delete curBlendBlitter;
152                         curBlendBlitter = nullptr;
153 
154                         stateSwitchTime = SDL_GetTicks();
155                         mapChoiceState = MAPCHOICESTATE_SHOWPLANET;
156                         break;
157                     }
158                 }
159             }
160         } break;
161 
162         case MAPCHOICESTATE_SHOWPLANET: {
163             if(SDL_GetTicks() - stateSwitchTime > (bFastBlending ? 500U : 4000U)) {
164                 mapChoiceState = MAPCHOICESTATE_BLENDPLANET;
165             }
166         } break;
167 
168         case MAPCHOICESTATE_BLENDPLANET: {
169             if(curBlendBlitter == nullptr) {
170                 SDL_Surface* pSurface = convertSurfaceToDisplayFormat(pGFXManager->getUIGraphicSurface(UI_MapChoiceMapOnly), false);
171                 SDL_Rect dest = { 0, 0, getWidth(pSurface), getHeight(pSurface) };
172                 curBlendBlitter = new BlendBlitter(pSurface, true, mapSurface, dest);
173             }
174 
175             if(curBlendBlitter != nullptr) {
176                 int numSteps = bFastBlending ? 8 : 1;
177 
178                 for(int i = 0; i < numSteps; i++) {
179                     if(curBlendBlitter->nextStep() == 0) {
180                         delete curBlendBlitter;
181                         curBlendBlitter = nullptr;
182 
183                         stateSwitchTime = SDL_GetTicks();
184                         mapChoiceState = MAPCHOICESTATE_SHOWMAPONLY;
185                         break;
186                     }
187                 }
188             }
189         } break;
190 
191         case MAPCHOICESTATE_SHOWMAPONLY: {
192             if(SDL_GetTicks() - stateSwitchTime > (bFastBlending ? 500U : 4000U)) {
193                 mapChoiceState = MAPCHOICESTATE_BLENDMAP;
194             }
195         } break;
196 
197         case MAPCHOICESTATE_BLENDMAP: {
198             if(curBlendBlitter == nullptr) {
199                 SDL_Surface* pSurface = convertSurfaceToDisplayFormat(pGFXManager->getUIGraphicSurface(UI_MapChoiceMap), false);
200                 SDL_Rect dest = { 0, 0, getWidth(pSurface), getHeight(pSurface) };
201                 curBlendBlitter = new BlendBlitter(pSurface, true, mapSurface, dest);
202             }
203 
204             if(curBlendBlitter != nullptr) {
205                 int numSteps = bFastBlending ? 8 : 1;
206 
207                 for(int i = 0; i < numSteps; i++) {
208                     if(curBlendBlitter->nextStep() == 0) {
209                         delete curBlendBlitter;
210                         curBlendBlitter = nullptr;
211 
212                         createMapSurfaceWithPieces(lastScenario);
213                         mapChoiceState = MAPCHOICESTATE_BLENDING;
214                         break;
215                     }
216                 }
217             }
218         } break;
219 
220         case MAPCHOICESTATE_BLENDING: {
221             if(curBlendBlitter == nullptr) {
222                 while(  (curHouse2Blit < NUM_HOUSES) &&
223                         (curRegion2Blit >= group[lastScenario].newRegion[(curHouse2Blit + house) % NUM_HOUSES].size())) {
224                         curRegion2Blit = 0;
225                         curHouse2Blit++;
226                 }
227 
228                 if((curHouse2Blit < NUM_HOUSES)&&(curRegion2Blit < group[lastScenario].newRegion[(curHouse2Blit + house) % NUM_HOUSES].size())) {
229                     // there is still some region to blend in
230                     int pieceNum = (group[lastScenario].newRegion[(curHouse2Blit + house) % NUM_HOUSES])[curRegion2Blit];
231                     SDL_Surface* pPieceSurface = convertSurfaceToDisplayFormat(pGFXManager->getMapChoicePieceSurface(pieceNum,(curHouse2Blit + house) % NUM_HOUSES), false);
232                     SDL_Rect dest = calcDrawingRect(pPieceSurface, piecePosition[pieceNum].x, piecePosition[pieceNum].y);
233                     curBlendBlitter = new BlendBlitter(pPieceSurface, true, mapSurface, dest);
234                     curRegion2Blit++;
235 
236                     // have to show some text?
237                     for(const TGroup::TText& ttext : group[lastScenario].text) {
238                         if(ttext.region == pieceNum) {
239                             msgticker.addMessage(ttext.message);
240                         }
241                     }
242 
243                 } else {
244                     msgticker.addMessage(_("@DUNE.ENG|286#Select next region"));
245                     mapChoiceState = MAPCHOICESTATE_ARROWS;
246                 }
247             }
248 
249             if(curBlendBlitter != nullptr) {
250                 int numSteps = bFastBlending ? 8 : 1;
251 
252                 for(int i = 0; i < numSteps; i++) {
253                     if(curBlendBlitter->nextStep() == 0) {
254                         delete curBlendBlitter;
255                         curBlendBlitter = nullptr;
256                         break;
257                     }
258                 }
259             }
260         } break;
261 
262         case MAPCHOICESTATE_ARROWS:
263         {
264             // Draw arrows
265             for(int i = 0; i < 4; i++) {
266                 int regionNum = group[lastScenario].attackRegion[i].regionNum;
267                 if(regionNum == 0) {
268                     continue;
269                 }
270 
271                 if(alreadyPlayedRegions & (1 << regionNum)) {
272                     continue;
273                 }
274 
275                 int arrowNum = std::max(0, std::min(8, group[lastScenario].attackRegion[i].arrowNum));
276                 SDL_Texture* arrow = pGFXManager->getUIGraphic(UI_MapChoiceArrow_None + arrowNum, house);
277                 int arrowFrame = (SDL_GetTicks() / 128) % 4;
278                 SDL_Rect src = calcSpriteSourceRect(arrow, arrowFrame, 4);
279                 SDL_Rect dest = calcSpriteDrawingRect(  arrow,
280                                                         group[lastScenario].attackRegion[i].arrowPosition.x + centerAreaRect.x,
281                                                         group[lastScenario].attackRegion[i].arrowPosition.y + centerAreaRect.y,
282                                                         4, 1);
283 
284                 SDL_RenderCopy(renderer, arrow, &src, &dest);
285             }
286         } break;
287 
288         case MAPCHOICESTATE_BLINKING:
289         {
290             if(((SDL_GetTicks() - selectionTime) % 900) < 450) {
291                 SDL_Texture* pieceTexture = pGFXManager->getMapChoicePiece(selectedRegion,house);
292                 SDL_Rect dest = calcDrawingRect(pieceTexture, piecePosition[selectedRegion].x + centerAreaRect.x, piecePosition[selectedRegion].y + centerAreaRect.y);
293                 SDL_RenderCopy(renderer, pieceTexture, nullptr, &dest);
294             }
295 
296             for(int i = 0; i < 4; i++) {
297                 if(group[lastScenario].attackRegion[i].regionNum != selectedRegion) {
298                     continue;
299                 }
300 
301                 int arrowNum = std::max(0, std::min(8, group[lastScenario].attackRegion[i].arrowNum));
302                 SDL_Texture* arrow = pGFXManager->getUIGraphic(UI_MapChoiceArrow_None + arrowNum, house);
303                 int arrowFrame = (SDL_GetTicks() / 128) % 4;
304                 SDL_Rect src = calcSpriteSourceRect(arrow, arrowFrame, 4);
305                 SDL_Rect dest = calcSpriteDrawingRect(  arrow,
306                                                         group[lastScenario].attackRegion[i].arrowPosition.x + centerAreaRect.x,
307                                                         group[lastScenario].attackRegion[i].arrowPosition.y + centerAreaRect.y,
308                                                         4, 1);
309 
310                 SDL_RenderCopy(renderer, arrow, &src, &dest);
311             }
312 
313             if((SDL_GetTicks() - selectionTime) > 2000) {
314                 quit();
315             }
316         } break;
317 
318     }
319 
320     msgticker.draw(Point(centerAreaRect.x + 110, centerAreaRect.y + 320));
321 }
322 
doInput(SDL_Event & event)323 bool MapChoice::doInput(SDL_Event &event) {
324     if((event.type == SDL_MOUSEBUTTONUP) && (event.button.button == SDL_BUTTON_LEFT)) {
325         if(mapChoiceState == MAPCHOICESTATE_ARROWS) {
326             int x = event.button.x-centerAreaRect.x;
327             int y = event.button.y-centerAreaRect.y;
328 
329             if((x > 0) && (x < centerAreaRect.w) && (y > 0) && (y < centerAreaRect.h)) {
330                 SDL_Surface* clickmap = pGFXManager->getUIGraphicSurface(UI_MapChoiceClickMap);
331 
332                 if(SDL_LockSurface(clickmap) != 0) {
333                     THROW(std::runtime_error, "Cannot lock image!");
334                 }
335 
336                 int regionNum = ((Uint8*)clickmap->pixels)[y * clickmap->pitch + x];
337 
338                 SDL_UnlockSurface(clickmap);
339 
340                 if((regionNum != 0) && ((alreadyPlayedRegions & (1 << regionNum)) == 0)) {
341                     for(int i = 0; i < 4; i++) {
342                         if(group[lastScenario].attackRegion[i].regionNum == regionNum) {
343                             mapChoiceState = MAPCHOICESTATE_BLINKING;
344                             selectedRegion = regionNum;
345                             alreadyPlayedRegions |= (1 << selectedRegion);
346                             selectionTime = SDL_GetTicks();
347                             break;
348                         }
349                     }
350                 }
351             }
352         } else {
353             bFastBlending = true;
354         }
355     }
356     return MenuBase::doInput(event);
357 }
358 
createMapSurfaceWithPieces(unsigned int scenario)359 void MapChoice::createMapSurfaceWithPieces(unsigned int scenario) {
360     if(mapSurface != nullptr) {
361         SDL_FreeSurface(mapSurface);
362     }
363     if(mapTexture != nullptr) {
364         SDL_DestroyTexture(mapTexture);
365     }
366 
367     // Load map surface
368     mapSurface = convertSurfaceToDisplayFormat(copySurface(pGFXManager->getUIGraphicSurface(UI_MapChoiceMap)), true);
369     mapTexture = SDL_CreateTexture(renderer, SCREEN_FORMAT, SDL_TEXTUREACCESS_STREAMING, mapSurface->w, mapSurface->h);
370     SDL_SetTextureBlendMode(mapTexture, SDL_BLENDMODE_BLEND);
371 
372     for(unsigned int s = 1; s < scenario; s++) {
373         for(unsigned int h = 0; h < NUM_HOUSES; h++) {
374             for(unsigned int p = 0; p < group[s].newRegion[h].size(); p++) {
375                 int pieceNum = (group[s].newRegion[h])[p];
376                 SDL_Surface* pieceSurface = pGFXManager->getMapChoicePieceSurface(pieceNum,h);
377                 SDL_Rect dest = calcDrawingRect(pieceSurface, piecePosition[pieceNum].x, piecePosition[pieceNum].y);
378                 SDL_BlitSurface(pieceSurface,nullptr,mapSurface,&dest);
379             }
380         }
381     }
382 }
383 
loadINI()384 void MapChoice::loadINI() {
385     std::string filename = fmt::sprintf("REGION%c.INI", houseChar[house]);
386 
387     SDL_RWops* file = pFileManager->openFile(filename);
388     INIFile RegionINI(file);
389     SDL_RWclose(file);
390 
391 
392     piecePosition[0].x = 0;
393     piecePosition[0].y = 0;
394 
395     // read [PIECES]
396     for(int i=1; i < 28; i++) {
397         char tmp[3];
398         sprintf(tmp,"%d",i);
399         std::string entry = RegionINI.getStringValue("PIECES",tmp);
400 
401         std::string strXPos;
402         std::string strYPos;
403 
404         if(splitString(entry,2,&strXPos,&strYPos) == false) {
405             THROW(std::runtime_error, "File '%s' contains invalid value for key '%s'", filename, tmp);
406         }
407 
408         piecePosition[i].x = atol(strXPos.c_str());
409         piecePosition[i].y = atol(strYPos.c_str());
410         piecePosition[i].x += 8;
411         piecePosition[i].y += 24;
412         piecePosition[i].x *= 2;
413         piecePosition[i].y *= 2;
414     }
415 
416     for(int i=1; i<=8; i++) {
417         char strSection[8];
418         sprintf(strSection,"GROUP%d",i);
419 
420         // read new regions
421         for(int h = 0; h < NUM_HOUSES; h++) {
422             std::string key;
423             switch(h) {
424                 case HOUSE_HARKONNEN:   key = "HAR"; break;
425                 case HOUSE_ATREIDES:    key = "ATR"; break;
426                 case HOUSE_ORDOS:       key = "ORD"; break;
427                 case HOUSE_FREMEN:      key = "FRE"; break;
428                 case HOUSE_SARDAUKAR:   key = "SAR"; break;
429                 case HOUSE_MERCENARY:   key = "MER"; break;
430             }
431 
432             std::string strValue = RegionINI.getStringValue(strSection,key);
433             if(strValue != "") {
434                 std::vector<std::string> strRegions = splitString(strValue);
435 
436                 for(unsigned int r = 0; r < strRegions.size(); r++) {
437                     group[i].newRegion[h].push_back(atol(strRegions[r].c_str()));
438                 }
439             }
440         }
441 
442         // read attackRegion (REG1, REG2, REG3)
443         for(int a = 0; a < 4; a++) {
444             char strKey[5];
445             sprintf(strKey,"REG%d",a+1);
446 
447             std::string tmp = RegionINI.getStringValue(strSection,strKey);
448             if(tmp == "") {
449                 group[i].attackRegion[a].regionNum = 0;
450                 group[i].attackRegion[a].arrowNum = 0;
451                 group[i].attackRegion[a].arrowPosition.x = 0;
452                 group[i].attackRegion[a].arrowPosition.y = 0;
453             } else {
454                 std::vector<std::string> strAttackRegion = splitString(tmp);
455 
456                 if(strAttackRegion.size() < 4) {
457                     THROW(std::runtime_error, "File '%s' contains invalid value for key [%s]/%s; it has to consist of 4 numbers!", filename, strSection, strKey);
458                 }
459 
460                 group[i].attackRegion[a].regionNum = atol(strAttackRegion[0].c_str());
461                 group[i].attackRegion[a].arrowNum = atol(strAttackRegion[1].c_str());
462                 group[i].attackRegion[a].arrowPosition.x = atol(strAttackRegion[2].c_str());
463                 group[i].attackRegion[a].arrowPosition.y = atol(strAttackRegion[3].c_str());
464                 group[i].attackRegion[a].arrowPosition.x *= 2;
465                 group[i].attackRegion[a].arrowPosition.y *= 2;
466             }
467         }
468 
469         // read text
470         for(int j = 1; j < 28; j++) {
471             char key[10];
472             sprintf(key,"%sTXT%d",_("LanguageFileExtension").c_str(),j);
473 
474             std::string str = convertCP850ToISO8859_1(RegionINI.getStringValue(strSection,key));
475             if(str != "") {
476                 TGroup::TText tmp;
477                 tmp.message = str;
478                 tmp.region = j;
479                 group[i].text.push_back(tmp);
480             } else {
481                 // try TXT? without leading language
482                 sprintf(key,"TXT%d",j);
483 
484                 std::string str = convertCP850ToISO8859_1(RegionINI.getStringValue(strSection,key));
485                 if(str != "") {
486                     TGroup::TText tmp;
487                     tmp.message = str;
488                     tmp.region = j;
489                     group[i].text.push_back(tmp);
490                 }
491             }
492         }
493     }
494 }
495