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, ¢erAreaRect);
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