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 <Game.h>
19 
20 #include <globals.h>
21 #include <config.h>
22 #include <main.h>
23 
24 #include <FileClasses/FileManager.h>
25 #include <FileClasses/GFXManager.h>
26 #include <FileClasses/FontManager.h>
27 #include <FileClasses/TextManager.h>
28 #include <FileClasses/music/MusicPlayer.h>
29 #include <FileClasses/LoadSavePNG.h>
30 #include <SoundPlayer.h>
31 #include <misc/IFileStream.h>
32 #include <misc/OFileStream.h>
33 #include <misc/IMemoryStream.h>
34 #include <misc/FileSystem.h>
35 #include <misc/fnkdat.h>
36 #include <misc/draw_util.h>
37 #include <misc/md5.h>
38 #include <misc/exceptions.h>
39 #include <misc/format.h>
40 
41 #include <players/HumanPlayer.h>
42 
43 #include <Network/NetworkManager.h>
44 
45 #include <GUI/dune/InGameMenu.h>
46 #include <GUI/dune/WaitingForOtherPlayers.h>
47 #include <Menu/MentatHelp.h>
48 #include <Menu/BriefingMenu.h>
49 #include <Menu/MapChoice.h>
50 
51 #include <House.h>
52 #include <Map.h>
53 #include <Bullet.h>
54 #include <Explosion.h>
55 #include <GameInitSettings.h>
56 #include <ScreenBorder.h>
57 #include <sand.h>
58 
59 #include <structures/StructureBase.h>
60 #include <structures/ConstructionYard.h>
61 #include <units/UnitBase.h>
62 #include <structures/BuilderBase.h>
63 #include <structures/Palace.h>
64 #include <units/Harvester.h>
65 #include <units/InfantryBase.h>
66 
67 #include <algorithm>
68 #include <sstream>
69 #include <iomanip>
70 #include <SDL.h>
71 
Game()72 Game::Game() {
73     currentZoomlevel = settings.video.preferredZoomLevel;
74 
75     localPlayerName = settings.general.playerName;
76 
77     unitList.clear();       //holds all the units
78     structureList.clear();  //all the structures
79     bulletList.clear();
80 
81     house.resize(NUM_HOUSES);
82 
83     sideBarPos = calcAlignedDrawingRect(pGFXManager->getUIGraphic(UI_SideBar), HAlign::Right, VAlign::Top);
84     topBarPos = calcAlignedDrawingRect(pGFXManager->getUIGraphic(UI_TopBar), HAlign::Left, VAlign::Top);
85 
86     // set to true for now
87     debug = false;
88 
89     powerIndicatorPos.h = spiceIndicatorPos.h = settings.video.height - 146 - 2;
90 
91     musicPlayer->changeMusic(MUSIC_PEACE);
92     //////////////////////////////////////////////////////////////////////////
93     SDL_Rect gameBoardRect = { 0, topBarPos.h, sideBarPos.x, getRendererHeight() - topBarPos.h };
94     screenborder = new ScreenBorder(gameBoardRect);
95 }
96 
97 
98 /**
99     The destructor frees up all the used memory.
100 */
~Game()101 Game::~Game() {
102     if(pNetworkManager != nullptr) {
103         pNetworkManager->setOnReceiveChatMessage(std::function<void (const std::string&, const std::string&)>());
104         pNetworkManager->setOnReceiveCommandList(std::function<void (const std::string&, const CommandList&)>());
105         pNetworkManager->setOnReceiveSelectionList(std::function<void (const std::string&, const std::set<Uint32>&, int)>());
106         pNetworkManager->setOnPeerDisconnected(std::function<void (const std::string&, bool, int)>());
107     }
108 
109     delete pInGameMenu;
110     pInGameMenu = nullptr;
111 
112     delete pInterface;
113     pInterface = nullptr;
114 
115     delete pWaitingForOtherPlayers;
116     pWaitingForOtherPlayers = nullptr;
117 
118     for(StructureBase* pStructure : structureList) {
119         delete pStructure;
120     }
121     structureList.clear();
122 
123     for(UnitBase* pUnit : unitList) {
124         delete pUnit;
125     }
126     unitList.clear();
127 
128     for(Bullet* pBullet : bulletList) {
129         delete pBullet;
130     }
131     bulletList.clear();
132 
133     for(Explosion* pExplosion : explosionList) {
134         delete pExplosion;
135     }
136     explosionList.clear();
137 
138     for(int i=0;i<NUM_HOUSES;i++) {
139         delete house[i];
140         house[i] = nullptr;
141     }
142 
143     delete currentGameMap;
144     currentGameMap = nullptr;
145     delete screenborder;
146     screenborder = nullptr;
147 }
148 
149 
initGame(const GameInitSettings & newGameInitSettings)150 void Game::initGame(const GameInitSettings& newGameInitSettings) {
151     gameInitSettings = newGameInitSettings;
152 
153     switch(gameInitSettings.getGameType()) {
154         case GameType::LoadSavegame: {
155             if(loadSaveGame(gameInitSettings.getFilename()) == false) {
156                 THROW(std::runtime_error, "Loading save game failed!");
157             }
158         } break;
159 
160         case GameType::LoadMultiplayer: {
161             IMemoryStream memStream(gameInitSettings.getFiledata().c_str(), gameInitSettings.getFiledata().size());
162 
163             if(loadSaveGame(memStream) == false) {
164                 THROW(std::runtime_error, "Loading save game failed!");
165             }
166         } break;
167 
168         case GameType::Campaign:
169         case GameType::Skirmish:
170         case GameType::CustomGame:
171         case GameType::CustomMultiplayer: {
172             gameType = gameInitSettings.getGameType();
173             randomGen.setSeed(gameInitSettings.getRandomSeed());
174 
175             objectData.loadFromINIFile("ObjectData.ini");
176 
177             if(gameInitSettings.getMission() != 0) {
178                 techLevel = ((gameInitSettings.getMission() + 1)/3) + 1 ;
179             }
180 
181             INIMapLoader* pINIMapLoader = new INIMapLoader(this, gameInitSettings.getFilename(), gameInitSettings.getFiledata());
182             delete pINIMapLoader;
183 
184 
185             if(bReplay == false && gameInitSettings.getGameType() != GameType::CustomGame && gameInitSettings.getGameType() != GameType::CustomMultiplayer) {
186                 /* do briefing */
187                 SDL_Log("Briefing...");
188                 BriefingMenu* pBriefing = new BriefingMenu(gameInitSettings.getHouseID(), gameInitSettings.getMission(),BRIEFING);
189                 pBriefing->showMenu();
190                 delete pBriefing;
191             }
192         } break;
193 
194         default: {
195         } break;
196     }
197 }
198 
initReplay(const std::string & filename)199 void Game::initReplay(const std::string& filename) {
200     bReplay = true;
201 
202     IFileStream fs;
203 
204     if(fs.open(filename) == false) {
205         THROW(io_error, "Error while opening '%s'!", filename);
206     }
207 
208     // override local player name as it was when the replay was created
209     localPlayerName = fs.readString();
210 
211     // read GameInitInfo
212     GameInitSettings loadedGameInitSettings(fs);
213 
214     // load all commands
215     cmdManager.load(fs);
216 
217     initGame(loadedGameInitSettings);
218 }
219 
220 
processObjects()221 void Game::processObjects()
222 {
223     // update all tiles
224     for(int y = 0; y < currentGameMap->getSizeY(); y++) {
225         for(int x = 0; x < currentGameMap->getSizeX(); x++) {
226             currentGameMap->getTile(x,y)->update();
227         }
228     }
229 
230     for(StructureBase* pStructure : structureList) {
231         pStructure->update();
232     }
233 
234     if ((currentCursorMode == CursorMode_Placing) && selectedList.empty()) {
235         currentCursorMode = CursorMode_Normal;
236     }
237 
238     for(UnitBase* pUnit : unitList) {
239         pUnit->update();
240     }
241 
242     for(Bullet* pBullet : bulletList) {
243         pBullet->update();
244     }
245 
246     for(Explosion* pExplosion : explosionList) {
247         pExplosion->update();
248     }
249 }
250 
251 
drawScreen()252 void Game::drawScreen()
253 {
254     Coord TopLeftTile = screenborder->getTopLeftTile();
255     Coord BottomRightTile = screenborder->getBottomRightTile();
256 
257     // extend the view a little bit to avoid graphical glitches
258     TopLeftTile.x = std::max(0, TopLeftTile.x - 1);
259     TopLeftTile.y = std::max(0, TopLeftTile.y - 1);
260     BottomRightTile.x = std::min(currentGameMap->getSizeX()-1, BottomRightTile.x + 1);
261     BottomRightTile.y = std::min(currentGameMap->getSizeY()-1, BottomRightTile.y + 1);
262 
263     Coord currentTile;
264 
265     /* draw ground */
266     for(currentTile.y = TopLeftTile.y; currentTile.y <= BottomRightTile.y; currentTile.y++) {
267         for(currentTile.x = TopLeftTile.x; currentTile.x <= BottomRightTile.x; currentTile.x++) {
268 
269             if (currentGameMap->tileExists(currentTile))    {
270                 Tile* pTile = currentGameMap->getTile(currentTile);
271                 pTile->blitGround( screenborder->world2screenX(currentTile.x*TILESIZE),
272                                   screenborder->world2screenY(currentTile.y*TILESIZE));
273             }
274         }
275     }
276 
277     /* draw structures */
278     for(currentTile.y = TopLeftTile.y; currentTile.y <= BottomRightTile.y; currentTile.y++) {
279         for(currentTile.x = TopLeftTile.x; currentTile.x <= BottomRightTile.x; currentTile.x++) {
280 
281             if (currentGameMap->tileExists(currentTile))    {
282                 Tile* pTile = currentGameMap->getTile(currentTile);
283                 pTile->blitStructures( screenborder->world2screenX(currentTile.x*TILESIZE),
284                                       screenborder->world2screenY(currentTile.y*TILESIZE));
285             }
286         }
287     }
288 
289     /* draw underground units */
290     for(currentTile.y = TopLeftTile.y; currentTile.y <= BottomRightTile.y; currentTile.y++) {
291         for(currentTile.x = TopLeftTile.x; currentTile.x <= BottomRightTile.x; currentTile.x++) {
292 
293             if (currentGameMap->tileExists(currentTile))    {
294                 Tile* pTile = currentGameMap->getTile(currentTile);
295                 pTile->blitUndergroundUnits( screenborder->world2screenX(currentTile.x*TILESIZE),
296                                             screenborder->world2screenY(currentTile.y*TILESIZE));
297             }
298         }
299     }
300 
301     /* draw dead objects */
302     for(currentTile.y = TopLeftTile.y; currentTile.y <= BottomRightTile.y; currentTile.y++) {
303         for(currentTile.x = TopLeftTile.x; currentTile.x <= BottomRightTile.x; currentTile.x++) {
304 
305             if (currentGameMap->tileExists(currentTile))    {
306                 Tile* pTile = currentGameMap->getTile(currentTile);
307                 pTile->blitDeadUnits( screenborder->world2screenX(currentTile.x*TILESIZE),
308                                      screenborder->world2screenY(currentTile.y*TILESIZE));
309             }
310         }
311     }
312 
313     /* draw infantry */
314     for(currentTile.y = TopLeftTile.y; currentTile.y <= BottomRightTile.y; currentTile.y++) {
315         for(currentTile.x = TopLeftTile.x; currentTile.x <= BottomRightTile.x; currentTile.x++) {
316 
317             if (currentGameMap->tileExists(currentTile))    {
318                 Tile* pTile = currentGameMap->getTile(currentTile);
319                 pTile->blitInfantry( screenborder->world2screenX(currentTile.x*TILESIZE),
320                                     screenborder->world2screenY(currentTile.y*TILESIZE));
321             }
322         }
323     }
324 
325     /* draw non-infantry ground units */
326     for(currentTile.y = TopLeftTile.y; currentTile.y <= BottomRightTile.y; currentTile.y++) {
327         for(currentTile.x = TopLeftTile.x; currentTile.x <= BottomRightTile.x; currentTile.x++) {
328 
329             if (currentGameMap->tileExists(currentTile))    {
330                 Tile* pTile = currentGameMap->getTile(currentTile);
331                 pTile->blitNonInfantryGroundUnits( screenborder->world2screenX(currentTile.x*TILESIZE),
332                                                   screenborder->world2screenY(currentTile.y*TILESIZE));
333             }
334         }
335     }
336 
337     /* draw bullets */
338     for(const Bullet* pBullet : bulletList) {
339         pBullet->blitToScreen();
340     }
341 
342 
343     /* draw explosions */
344     for(const Explosion* pExplosion : explosionList) {
345         pExplosion->blitToScreen();
346     }
347 
348     /* draw air units */
349     for(currentTile.y = TopLeftTile.y; currentTile.y <= BottomRightTile.y; currentTile.y++) {
350         for(currentTile.x = TopLeftTile.x; currentTile.x <= BottomRightTile.x; currentTile.x++) {
351 
352             if (currentGameMap->tileExists(currentTile))    {
353                 Tile* pTile = currentGameMap->getTile(currentTile);
354                 pTile->blitAirUnits(   screenborder->world2screenX(currentTile.x*TILESIZE),
355                                       screenborder->world2screenY(currentTile.y*TILESIZE));
356             }
357         }
358     }
359 
360     // draw the gathering point line if a structure is selected
361     if(selectedList.size() == 1) {
362         StructureBase *pStructure = dynamic_cast<StructureBase*>(getObjectManager().getObject(*selectedList.begin()));
363         if(pStructure != nullptr) {
364             pStructure->drawGatheringPointLine();
365         }
366     }
367 
368     /* draw selection rectangles */
369     for(currentTile.y = TopLeftTile.y; currentTile.y <= BottomRightTile.y; currentTile.y++) {
370         for(currentTile.x = TopLeftTile.x; currentTile.x <= BottomRightTile.x; currentTile.x++) {
371 
372             if (currentGameMap->tileExists(currentTile))    {
373                 Tile* pTile = currentGameMap->getTile(currentTile);
374 
375                 if(debug || pTile->isExplored(pLocalHouse->getHouseID())) {
376                     pTile->blitSelectionRects(   screenborder->world2screenX(currentTile.x*TILESIZE),
377                                                 screenborder->world2screenY(currentTile.y*TILESIZE));
378                 }
379             }
380         }
381     }
382 
383 
384 //////////////////////////////draw unexplored/shade
385 
386     if(debug == false) {
387         int zoomedTileSize = world2zoomedWorld(TILESIZE);
388         for(int x = screenborder->getTopLeftTile().x - 1; x <= screenborder->getBottomRightTile().x + 1; x++) {
389             for (int y = screenborder->getTopLeftTile().y - 1; y <= screenborder->getBottomRightTile().y + 1; y++) {
390 
391                 if((x >= 0) && (x < currentGameMap->getSizeX()) && (y >= 0) && (y < currentGameMap->getSizeY())) {
392                     Tile* pTile = currentGameMap->getTile(x, y);
393 
394                     if(pTile->isExplored(pLocalHouse->getHouseID())) {
395                         int hideTile = pTile->getHideTile(pLocalHouse->getHouseID());
396 
397                         if(hideTile != 0) {
398                             SDL_Texture** hiddenTex = pGFXManager->getObjPic(ObjPic_Terrain_Hidden);
399 
400                             SDL_Rect source = { hideTile*zoomedTileSize, 0, zoomedTileSize, zoomedTileSize };
401                             SDL_Rect drawLocation = {   screenborder->world2screenX(x*TILESIZE), screenborder->world2screenY(y*TILESIZE),
402                                                         zoomedTileSize, zoomedTileSize };
403                             SDL_RenderCopy(renderer, hiddenTex[currentZoomlevel], &source, &drawLocation);
404                         }
405 
406                         if(gameInitSettings.getGameOptions().fogOfWar == true) {
407                             int fogTile = pTile->getFogTile(pLocalHouse->getHouseID());
408 
409                             if(pTile->isFogged(pLocalHouse->getHouseID()) == true) {
410                                 fogTile = Terrain_HiddenFull;
411                             }
412 
413                             if(fogTile != 0) {
414                                 SDL_Texture** hiddenFogTex = pGFXManager->getObjPic(ObjPic_Terrain_HiddenFog);
415 
416                                 SDL_Rect source = { fogTile*zoomedTileSize, 0,
417                                                     zoomedTileSize, zoomedTileSize };
418                                 SDL_Rect drawLocation = {   screenborder->world2screenX(x*TILESIZE), screenborder->world2screenY(y*TILESIZE),
419                                                             zoomedTileSize, zoomedTileSize };
420 
421                                 SDL_RenderCopy(renderer, hiddenFogTex[currentZoomlevel], &source, &drawLocation);
422                             }
423                         }
424                     } else {
425                         if(!debug) {
426                             SDL_Texture** hiddenTex = pGFXManager->getObjPic(ObjPic_Terrain_Hidden);
427                             SDL_Rect source = { zoomedTileSize*15, 0, zoomedTileSize, zoomedTileSize };
428                             SDL_Rect drawLocation = {   screenborder->world2screenX(x*TILESIZE), screenborder->world2screenY(y*TILESIZE),
429                                                         zoomedTileSize, zoomedTileSize };
430                             SDL_RenderCopy(renderer, hiddenTex[currentZoomlevel], &source, &drawLocation);
431                         }
432                     }
433                 } else {
434                     // we are outside the map => draw complete hidden
435                     SDL_Texture** hiddenTex = pGFXManager->getObjPic(ObjPic_Terrain_Hidden);
436                     SDL_Rect source = { zoomedTileSize*15, 0, zoomedTileSize, zoomedTileSize };
437                     SDL_Rect drawLocation = {   screenborder->world2screenX(x*TILESIZE), screenborder->world2screenY(y*TILESIZE),
438                                                 zoomedTileSize, zoomedTileSize };
439                     SDL_RenderCopy(renderer, hiddenTex[currentZoomlevel], &source, &drawLocation);
440                 }
441             }
442         }
443     }
444 
445 /////////////draw placement position
446 
447     if(currentCursorMode == CursorMode_Placing) {
448         //if user has selected to place a structure
449 
450         if(screenborder->isScreenCoordInsideMap(drawnMouseX, drawnMouseY)) {
451             //if mouse is not over game bar
452 
453             int xPos = screenborder->screen2MapX(drawnMouseX);
454             int yPos = screenborder->screen2MapY(drawnMouseY);
455 
456             BuilderBase* builder = nullptr;
457             if(selectedList.size() == 1) {
458                 builder = dynamic_cast<BuilderBase*>(objectManager.getObject(*selectedList.begin()));
459 
460                 int placeItem = builder->getCurrentProducedItem();
461                 Coord structuresize = getStructureSize(placeItem);
462 
463                 bool withinRange = false;
464                 for (int i = xPos; i < (xPos + structuresize.x); i++) {
465                     for (int j = yPos; j < (yPos + structuresize.y); j++) {
466                         if (currentGameMap->isWithinBuildRange(i, j, builder->getOwner())) {
467                             withinRange = true;         //find out if the structure is close enough to other buildings
468                         }
469                     }
470                 }
471 
472                 SDL_Texture* validPlace = nullptr;
473                 SDL_Texture* invalidPlace = nullptr;
474 
475                 switch(currentZoomlevel) {
476                     case 0: {
477                         validPlace = pGFXManager->getUIGraphic(UI_ValidPlace_Zoomlevel0);
478                         invalidPlace = pGFXManager->getUIGraphic(UI_InvalidPlace_Zoomlevel0);
479                     } break;
480 
481                     case 1: {
482                         validPlace = pGFXManager->getUIGraphic(UI_ValidPlace_Zoomlevel1);
483                         invalidPlace = pGFXManager->getUIGraphic(UI_InvalidPlace_Zoomlevel1);
484                     } break;
485 
486                     case 2:
487                     default: {
488                         validPlace = pGFXManager->getUIGraphic(UI_ValidPlace_Zoomlevel2);
489                         invalidPlace = pGFXManager->getUIGraphic(UI_InvalidPlace_Zoomlevel2);
490                     } break;
491 
492                 }
493 
494                 for(int i = xPos; i < (xPos + structuresize.x); i++) {
495                     for(int j = yPos; j < (yPos + structuresize.y); j++) {
496                         SDL_Texture* image;
497 
498                         if(!withinRange || !currentGameMap->tileExists(i,j) || !currentGameMap->getTile(i,j)->isRock()
499                             || currentGameMap->getTile(i,j)->isMountain() || currentGameMap->getTile(i,j)->hasAGroundObject()
500                             || (((placeItem == Structure_Slab1) || (placeItem == Structure_Slab4)) && currentGameMap->getTile(i,j)->isConcrete())) {
501                             image = invalidPlace;
502                         } else {
503                             image = validPlace;
504                         }
505 
506                         SDL_Rect drawLocation = calcDrawingRect(image, screenborder->world2screenX(i*TILESIZE), screenborder->world2screenY(j*TILESIZE));
507                         SDL_RenderCopy(renderer, image, nullptr, &drawLocation);
508                     }
509                 }
510             }
511         }
512     }
513 
514 ///////////draw game selection rectangle
515     if(selectionMode) {
516 
517         int finalMouseX = drawnMouseX;
518         int finalMouseY = drawnMouseY;
519         if(finalMouseX >= sideBarPos.x) {
520             //this keeps the box on the map, and not over game bar
521             finalMouseX = sideBarPos.x-1;
522         }
523 
524         if(finalMouseY < topBarPos.y+topBarPos.h) {
525             finalMouseY = topBarPos.x+topBarPos.h;
526         }
527 
528         // draw the mouse selection rectangle
529         renderDrawRect( renderer,
530                         screenborder->world2screenX(selectionRect.x),
531                         screenborder->world2screenY(selectionRect.y),
532                         finalMouseX,
533                         finalMouseY,
534                         COLOR_WHITE);
535     }
536 
537 
538 
539 ///////////draw action indicator
540 
541     if((indicatorFrame != NONE_ID) && (screenborder->isInsideScreen(indicatorPosition, Coord(TILESIZE,TILESIZE)) == true)) {
542         SDL_Texture* pUIIndicator = pGFXManager->getUIGraphic(UI_Indicator);
543         SDL_Rect source = calcSpriteSourceRect(pUIIndicator, indicatorFrame, 3);
544         SDL_Rect drawLocation = calcSpriteDrawingRect(  pUIIndicator,
545                                                         screenborder->world2screenX(indicatorPosition.x),
546                                                         screenborder->world2screenY(indicatorPosition.y),
547                                                         3, 1,
548                                                         HAlign::Center, VAlign::Center);
549         SDL_RenderCopy(renderer, pUIIndicator, &source, &drawLocation);
550     }
551 
552 
553 ///////////draw game bar
554     pInterface->draw(Point(0,0));
555     pInterface->drawOverlay(Point(0,0));
556 
557     // draw chat message currently typed
558     if(chatMode) {
559         SDL_Texture* pChatTexture = pFontManager->createTextureWithText("Chat: " + typingChatMessage + (((SDL_GetTicks() / 150) % 2 == 0) ? "_" : ""), COLOR_WHITE, FONT_STD12);
560         SDL_Rect drawLocation = calcDrawingRect(pChatTexture, 20, getRendererHeight() - 40);
561         SDL_RenderCopy(renderer, pChatTexture, nullptr, &drawLocation);
562         SDL_DestroyTexture(pChatTexture);
563     }
564 
565     if(bShowFPS) {
566         std::string strFPS = fmt::sprintf("fps: %.1f ", 1000.0f/averageFrameTime);
567 
568         SDL_Texture* pFPSTexture = pFontManager->createTextureWithText(strFPS, COLOR_WHITE, FONT_STD12);
569         SDL_Rect drawLocation = calcDrawingRect(pFPSTexture,sideBarPos.x - strFPS.length()*8, 60);
570         SDL_RenderCopy(renderer, pFPSTexture, nullptr, &drawLocation);
571         SDL_DestroyTexture(pFPSTexture);
572     }
573 
574     if(bShowTime) {
575         int seconds = getGameTime() / 1000;
576         std::string strTime = fmt::sprintf(" %.2d:%.2d:%.2d", seconds / 3600, (seconds % 3600)/60, (seconds % 60) );
577 
578         SDL_Texture* pTimeTexture = pFontManager->createTextureWithText(strTime, COLOR_WHITE, FONT_STD12);
579         SDL_Rect drawLocation = calcAlignedDrawingRect(pTimeTexture, HAlign::Left, VAlign::Bottom);
580         drawLocation.y++;
581         SDL_RenderCopy(renderer, pTimeTexture, nullptr, &drawLocation);
582         SDL_DestroyTexture(pTimeTexture);
583     }
584 
585     if(finished) {
586         std::string message;
587 
588         if(won) {
589             message = _("You Have Completed Your Mission.");
590         } else {
591             message = _("You Have Failed Your Mission.");
592         }
593 
594         SDL_Texture* pFinishMessageTexture = pFontManager->createTextureWithText(message.c_str(), COLOR_WHITE, FONT_STD24);
595         SDL_Rect drawLocation = calcDrawingRect(pFinishMessageTexture, sideBarPos.x/2, topBarPos.h + (getRendererHeight()-topBarPos.h)/2, HAlign::Center, VAlign::Center);
596         SDL_RenderCopy(renderer, pFinishMessageTexture, nullptr, &drawLocation);
597         SDL_DestroyTexture(pFinishMessageTexture);
598     }
599 
600     if(pWaitingForOtherPlayers != nullptr) {
601         pWaitingForOtherPlayers->draw();
602     }
603 
604     if(pInGameMenu != nullptr) {
605         pInGameMenu->draw();
606     } else if(pInGameMentat != nullptr) {
607         pInGameMentat->draw();
608     }
609 
610     drawCursor();
611 }
612 
613 
doInput()614 void Game::doInput()
615 {
616     SDL_Event event;
617     while(SDL_PollEvent(&event)) {
618         // check for a key press
619 
620         // first of all update mouse
621         if(event.type == SDL_MOUSEMOTION) {
622             SDL_MouseMotionEvent* mouse = &event.motion;
623             drawnMouseX = std::max(0, std::min(mouse->x, settings.video.width-1));
624             drawnMouseY = std::max(0, std::min(mouse->y, settings.video.height-1));
625         }
626 
627         if(pInGameMenu != nullptr) {
628             pInGameMenu->handleInput(event);
629 
630             if(bMenu == false) {
631                 delete pInGameMenu;
632                 pInGameMenu = nullptr;
633             }
634 
635         } else if(pInGameMentat != nullptr) {
636             pInGameMentat->doInput(event);
637 
638             if(bMenu == false) {
639                 delete pInGameMentat;
640                 pInGameMentat = nullptr;
641             }
642 
643         } else if(pWaitingForOtherPlayers != nullptr) {
644             pWaitingForOtherPlayers->handleInput(event);
645 
646             if(bMenu == false) {
647                 delete pWaitingForOtherPlayers;
648                 pWaitingForOtherPlayers = nullptr;
649             }
650 
651         } else {
652             /* Look for a keypress */
653             switch (event.type) {
654 
655                 case SDL_KEYDOWN: {
656                     if(chatMode) {
657                         handleChatInput(event.key);
658                     } else {
659                         handleKeyInput(event.key);
660                     }
661                 } break;
662 
663                 case SDL_TEXTINPUT: {
664                     if(chatMode) {
665                         std::string newText = convertUTF8ToISO8859_1(event.text.text);
666                         if(typingChatMessage.length() + newText.length() <= 60) {
667                             typingChatMessage += newText;
668                         }
669                     }
670                 } break;
671 
672                 case SDL_MOUSEWHEEL: {
673                     if (event.wheel.y != 0) {
674                         pInterface->handleMouseWheel(drawnMouseX,drawnMouseY,(event.wheel.y > 0));
675                     }
676                 } break;
677 
678                 case SDL_MOUSEBUTTONDOWN: {
679                     SDL_MouseButtonEvent* mouse = &event.button;
680 
681                     switch(mouse->button) {
682                         case SDL_BUTTON_LEFT: {
683                             pInterface->handleMouseLeft(mouse->x, mouse->y, true);
684                         } break;
685 
686                         case SDL_BUTTON_RIGHT: {
687                             pInterface->handleMouseRight(mouse->x, mouse->y, true);
688                         } break;
689                     }
690 
691                     switch(mouse->button) {
692 
693                         case SDL_BUTTON_LEFT: {
694 
695                             switch(currentCursorMode) {
696 
697                                 case CursorMode_Placing: {
698                                     if(screenborder->isScreenCoordInsideMap(mouse->x, mouse->y) == true) {
699                                         handlePlacementClick(screenborder->screen2MapX(mouse->x), screenborder->screen2MapY(mouse->y));
700                                     }
701                                 } break;
702 
703                                 case CursorMode_Attack: {
704 
705                                     if(screenborder->isScreenCoordInsideMap(mouse->x, mouse->y) == true) {
706                                         handleSelectedObjectsAttackClick(screenborder->screen2MapX(mouse->x), screenborder->screen2MapY(mouse->y));
707                                     }
708 
709                                 } break;
710 
711                                 case CursorMode_Move: {
712 
713                                     if(screenborder->isScreenCoordInsideMap(mouse->x, mouse->y) == true) {
714                                         handleSelectedObjectsMoveClick(screenborder->screen2MapX(mouse->x), screenborder->screen2MapY(mouse->y));
715                                     }
716 
717                                 } break;
718 
719                                 case CursorMode_CarryallDrop: {
720 
721                                     if(screenborder->isScreenCoordInsideMap(mouse->x, mouse->y) == true) {
722                                         handleSelectedObjectsRequestCarryallDropClick(screenborder->screen2MapX(mouse->x), screenborder->screen2MapY(mouse->y));
723                                     }
724 
725                                 } break;
726 
727                                 case CursorMode_Capture: {
728 
729                                     if(screenborder->isScreenCoordInsideMap(mouse->x, mouse->y) == true) {
730                                         handleSelectedObjectsCaptureClick(screenborder->screen2MapX(mouse->x), screenborder->screen2MapY(mouse->y));
731                                     }
732 
733                                 } break;
734 
735                                 case CursorMode_Normal:
736                                 default: {
737 
738                                     if (mouse->x < sideBarPos.x && mouse->y >= topBarPos.h) {
739                                         // it isn't on the gamebar
740 
741                                         if(!selectionMode) {
742                                             // if we have started the selection rectangle
743                                             // the starting point of the selection rectangele
744                                             selectionRect.x = screenborder->screen2worldX(mouse->x);
745                                             selectionRect.y = screenborder->screen2worldY(mouse->y);
746                                         }
747                                         selectionMode = true;
748 
749                                     }
750                                 } break;
751                             }
752                         } break;    //end of SDL_BUTTON_LEFT
753 
754                         case SDL_BUTTON_RIGHT: {
755                             //if the right mouse button is pressed
756 
757                             if(currentCursorMode != CursorMode_Normal) {
758                                 //cancel special cursor mode
759                                 currentCursorMode = CursorMode_Normal;
760                             } else if((!selectedList.empty()
761                                             && (((objectManager.getObject(*selectedList.begin()))->getOwner() == pLocalHouse))
762                                             && (((objectManager.getObject(*selectedList.begin()))->isRespondable())) ) )
763                             {
764                                 //if user has a controlable unit selected
765 
766                                 if(screenborder->isScreenCoordInsideMap(mouse->x, mouse->y) == true) {
767                                     if(handleSelectedObjectsActionClick(screenborder->screen2MapX(mouse->x), screenborder->screen2MapY(mouse->y))) {
768                                         indicatorFrame = 0;
769                                         indicatorPosition.x = screenborder->screen2worldX(mouse->x);
770                                         indicatorPosition.y = screenborder->screen2worldY(mouse->y);
771                                     }
772                                 }
773                             }
774                         } break;    //end of SDL_BUTTON_RIGHT
775                     }
776                 } break;
777 
778                 case SDL_MOUSEMOTION: {
779                     SDL_MouseMotionEvent* mouse = &event.motion;
780 
781                     pInterface->handleMouseMovement(mouse->x,mouse->y);
782                 } break;
783 
784                 case SDL_MOUSEBUTTONUP: {
785                     SDL_MouseButtonEvent* mouse = &event.button;
786 
787                     switch(mouse->button) {
788                         case SDL_BUTTON_LEFT: {
789                             pInterface->handleMouseLeft(mouse->x, mouse->y, false);
790                         } break;
791 
792                         case SDL_BUTTON_RIGHT: {
793                             pInterface->handleMouseRight(mouse->x, mouse->y, false);
794                         } break;
795                     }
796 
797                     if(selectionMode && (mouse->button == SDL_BUTTON_LEFT)) {
798                         //this keeps the box on the map, and not over game bar
799                         int finalMouseX = mouse->x;
800                         int finalMouseY = mouse->y;
801 
802                         if(finalMouseX >= sideBarPos.x) {
803                             finalMouseX = sideBarPos.x-1;
804                         }
805 
806                         if(finalMouseY < topBarPos.y+topBarPos.h) {
807                             finalMouseY = topBarPos.x+topBarPos.h;
808                         }
809 
810                         int rectFinishX = screenborder->screen2MapX(finalMouseX);
811                         if(rectFinishX > (currentGameMap->getSizeX()-1)) {
812                             rectFinishX = currentGameMap->getSizeX()-1;
813                         }
814 
815                         int rectFinishY = screenborder->screen2MapY(finalMouseY);
816 
817                         // convert start also to map coordinates
818                         int rectStartX = selectionRect.x/TILESIZE;
819                         int rectStartY = selectionRect.y/TILESIZE;
820 
821                         currentGameMap->selectObjects(  pLocalHouse->getHouseID(),
822                                                         rectStartX, rectStartY, rectFinishX, rectFinishY,
823                                                         screenborder->screen2worldX(finalMouseX),
824                                                         screenborder->screen2worldY(finalMouseY),
825                                                         SDL_GetModState() & KMOD_SHIFT);
826 
827                         if(selectedList.size() == 1) {
828                             ObjectBase* pObject = objectManager.getObject( *selectedList.begin());
829                             if(pObject != nullptr && pObject->getOwner() == pLocalHouse && pObject->getItemID() == Unit_Harvester) {
830                                 Harvester* pHarvester = static_cast<Harvester*>(pObject);
831 
832                                 std::string harvesterMessage = _("@DUNE.ENG|226#Harvester");
833 
834                                 int percent = lround(100 * pHarvester->getAmountOfSpice() / HARVESTERMAXSPICE);
835                                 if(percent > 0) {
836                                     if(pHarvester->isAwaitingPickup()) {
837                                         harvesterMessage += fmt::sprintf(_("@DUNE.ENG|124#full and awaiting pickup"), percent);
838                                     } else if(pHarvester->isReturning()) {
839                                         harvesterMessage += fmt::sprintf(_("@DUNE.ENG|123#full and returning"), percent);
840                                     } else if(pHarvester->isHarvesting()) {
841                                         harvesterMessage += fmt::sprintf(_("@DUNE.ENG|122#full and harvesting"), percent);
842                                     } else {
843                                         harvesterMessage += fmt::sprintf(_("@DUNE.ENG|121#full"), percent);
844                                     }
845 
846                                 } else {
847                                     if(pHarvester->isAwaitingPickup()) {
848                                         harvesterMessage += _("@DUNE.ENG|128#empty and awaiting pickup");
849                                     } else if(pHarvester->isReturning()) {
850                                         harvesterMessage += _("@DUNE.ENG|127#empty and returning");
851                                     } else if(pHarvester->isHarvesting()) {
852                                         harvesterMessage += _("@DUNE.ENG|126#empty and harvesting");
853                                     } else {
854                                         harvesterMessage += _("@DUNE.ENG|125#empty");
855                                     }
856                                 }
857 
858                                 if(!pInterface->newsTickerHasMessage()) {
859                                     pInterface->addToNewsTicker(harvesterMessage);
860                                 }
861                             }
862                         }
863                     }
864 
865                     selectionMode = false;
866 
867                 } break;
868 
869                 case (SDL_QUIT): {
870                     bQuitGame = true;
871                 } break;
872 
873                 default:
874                     break;
875             }
876         }
877     }
878 
879     if((pInGameMenu == nullptr) && (pInGameMentat == nullptr) && (pWaitingForOtherPlayers == nullptr) && (SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_FOCUS)) {
880 
881         const Uint8 *keystate = SDL_GetKeyboardState(nullptr);
882         scrollDownMode =  (drawnMouseY >= getRendererHeight()-1-SCROLLBORDER) || keystate[SDL_SCANCODE_DOWN];
883         scrollLeftMode = (drawnMouseX <= SCROLLBORDER) || keystate[SDL_SCANCODE_LEFT];
884         scrollRightMode = (drawnMouseX >= getRendererWidth()-1-SCROLLBORDER) || keystate[SDL_SCANCODE_RIGHT];
885         scrollUpMode = (drawnMouseY <= SCROLLBORDER) || keystate[SDL_SCANCODE_UP];
886 
887         if(scrollLeftMode && scrollRightMode) {
888             // do nothing
889         } else if(scrollLeftMode) {
890             scrollLeftMode = screenborder->scrollLeft();
891         } else if(scrollRightMode) {
892             scrollRightMode = screenborder->scrollRight();
893         }
894 
895         if(scrollDownMode && scrollUpMode) {
896             // do nothing
897         } else if(scrollDownMode) {
898             scrollDownMode = screenborder->scrollDown();
899         } else if(scrollUpMode) {
900             scrollUpMode = screenborder->scrollUp();
901         }
902     } else {
903         scrollDownMode = false;
904         scrollLeftMode = false;
905         scrollRightMode = false;
906         scrollUpMode = false;
907     }
908 }
909 
910 
drawCursor()911 void Game::drawCursor()
912 {
913     if(!(SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_FOCUS)) {
914         return;
915     }
916 
917     SDL_Texture* pCursor = nullptr;
918     SDL_Rect dest = { 0, 0, 0, 0};
919     if(scrollLeftMode || scrollRightMode || scrollUpMode || scrollDownMode) {
920         if(scrollLeftMode && !scrollRightMode) {
921             pCursor = pGFXManager->getUIGraphic(UI_CursorLeft);
922             dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY-5, HAlign::Left, VAlign::Top);
923         } else if(scrollRightMode && !scrollLeftMode) {
924             pCursor = pGFXManager->getUIGraphic(UI_CursorRight);
925             dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY-5, HAlign::Center, VAlign::Top);
926         }
927 
928         if(pCursor == nullptr) {
929             if(scrollUpMode && !scrollDownMode) {
930                 pCursor = pGFXManager->getUIGraphic(UI_CursorUp);
931                 dest = calcDrawingRect(pCursor, drawnMouseX-5, drawnMouseY, HAlign::Left, VAlign::Top);
932             } else if(scrollDownMode && !scrollUpMode) {
933                 pCursor = pGFXManager->getUIGraphic(UI_CursorDown);
934                 dest = calcDrawingRect(pCursor, drawnMouseX-5, drawnMouseY, HAlign::Left, VAlign::Center);
935             } else {
936                 pCursor = pGFXManager->getUIGraphic(UI_CursorNormal);
937                 dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY, HAlign::Left, VAlign::Top);
938             }
939         }
940     } else {
941         if( (pInGameMenu != nullptr) || (pInGameMentat != nullptr) || (pWaitingForOtherPlayers != nullptr) || (((drawnMouseX >= sideBarPos.x) || (drawnMouseY < topBarPos.h)) && (isOnRadarView(drawnMouseX, drawnMouseY) == false))) {
942             // Menu mode or Mentat Menu or Waiting for other players or outside of game screen but not inside minimap
943             pCursor = pGFXManager->getUIGraphic(UI_CursorNormal);
944             dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY, HAlign::Left, VAlign::Top);
945         } else {
946 
947             switch(currentCursorMode) {
948                 case CursorMode_Normal:
949                 case CursorMode_Placing: {
950                     pCursor = pGFXManager->getUIGraphic(UI_CursorNormal);
951                     dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY, HAlign::Left, VAlign::Top);
952                 } break;
953 
954                 case CursorMode_Move: {
955                     switch(currentZoomlevel) {
956                         case 0:     pCursor = pGFXManager->getUIGraphic(UI_CursorMove_Zoomlevel0); break;
957                         case 1:     pCursor = pGFXManager->getUIGraphic(UI_CursorMove_Zoomlevel1); break;
958                         case 2:
959                         default:    pCursor = pGFXManager->getUIGraphic(UI_CursorMove_Zoomlevel2); break;
960                     }
961 
962                     dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY, HAlign::Center, VAlign::Center);
963                 } break;
964 
965                 case CursorMode_Attack: {
966                     switch(currentZoomlevel) {
967                         case 0:     pCursor = pGFXManager->getUIGraphic(UI_CursorAttack_Zoomlevel0); break;
968                         case 1:     pCursor = pGFXManager->getUIGraphic(UI_CursorAttack_Zoomlevel1); break;
969                         case 2:
970                         default:    pCursor = pGFXManager->getUIGraphic(UI_CursorAttack_Zoomlevel2); break;
971                     }
972 
973                     dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY, HAlign::Center, VAlign::Center);
974                 } break;
975 
976                 case CursorMode_Capture: {
977                     switch(currentZoomlevel) {
978                         case 0:     pCursor = pGFXManager->getUIGraphic(UI_CursorCapture_Zoomlevel0); break;
979                         case 1:     pCursor = pGFXManager->getUIGraphic(UI_CursorCapture_Zoomlevel1); break;
980                         case 2:
981                         default:    pCursor = pGFXManager->getUIGraphic(UI_CursorCapture_Zoomlevel2); break;
982                     }
983 
984                     dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY, HAlign::Center, VAlign::Bottom);
985 
986                     int xPos = INVALID_POS;
987                     int yPos = INVALID_POS;
988 
989                     if(screenborder->isScreenCoordInsideMap(drawnMouseX, drawnMouseY) == true) {
990                         xPos = screenborder->screen2MapX(drawnMouseX);
991                         yPos = screenborder->screen2MapY(drawnMouseY);
992                     } else if(isOnRadarView(drawnMouseX, drawnMouseY)) {
993                         Coord position = pInterface->getRadarView().getWorldCoords(drawnMouseX - (sideBarPos.x + SIDEBAR_COLUMN_WIDTH), drawnMouseY - sideBarPos.y);
994 
995                         xPos = position.x / TILESIZE;
996                         yPos = position.y / TILESIZE;
997                     }
998 
999                     if((xPos != INVALID_POS) && (yPos != INVALID_POS)) {
1000 
1001                         Tile* pTile = currentGameMap->getTile(xPos, yPos);
1002 
1003                         if(pTile->isExplored(pLocalHouse->getHouseID())) {
1004 
1005                             StructureBase* pStructure = dynamic_cast<StructureBase*>(pTile->getGroundObject());
1006 
1007                             if((pStructure != nullptr) && (pStructure->canBeCaptured()) && (pStructure->getOwner()->getTeam() != pLocalHouse->getTeam())) {
1008                                 dest.y += ((getGameCycleCount() / 10) % 5);
1009                             }
1010                         }
1011                     }
1012 
1013                 } break;
1014 
1015                 case CursorMode_CarryallDrop: {
1016                     switch(currentZoomlevel) {
1017                         case 0:     pCursor = pGFXManager->getUIGraphic(UI_CursorCarryallDrop_Zoomlevel0); break;
1018                         case 1:     pCursor = pGFXManager->getUIGraphic(UI_CursorCarryallDrop_Zoomlevel1); break;
1019                         case 2:
1020                         default:    pCursor = pGFXManager->getUIGraphic(UI_CursorCarryallDrop_Zoomlevel2); break;
1021                     }
1022 
1023                     dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY, HAlign::Center, VAlign::Bottom);
1024                 } break;
1025 
1026 
1027                 default: {
1028                     THROW(std::runtime_error, "Game::drawCursor(): Unknown cursor mode");
1029                 };
1030             }
1031         }
1032     }
1033 
1034     SDL_RenderCopy(renderer, pCursor, nullptr, &dest);
1035 }
1036 
setupView()1037 void Game::setupView()
1038 {
1039     int i = 0;
1040     int j = 0;
1041     int count = 0;
1042 
1043     //setup start location/view
1044     i = j = count = 0;
1045 
1046     for(const UnitBase* pUnit : unitList) {
1047         if((pUnit->getOwner() == pLocalHouse) && (pUnit->getItemID() != Unit_Sandworm)) {
1048             i += pUnit->getX();
1049             j += pUnit->getY();
1050             count++;
1051         }
1052     }
1053 
1054     for(const StructureBase* pStructure : structureList) {
1055         if(pStructure->getOwner() == pLocalHouse) {
1056             i += pStructure->getX();
1057             j += pStructure->getY();
1058             count++;
1059         }
1060     }
1061 
1062     if(count == 0) {
1063         i = currentGameMap->getSizeX()*TILESIZE/2-1;
1064         j = currentGameMap->getSizeY()*TILESIZE/2-1;
1065     } else {
1066         i = i*TILESIZE/count;
1067         j = j*TILESIZE/count;
1068     }
1069 
1070     screenborder->setNewScreenCenter(Coord(i,j));
1071 }
1072 
1073 
runMainLoop()1074 void Game::runMainLoop() {
1075     SDL_Log("Starting game...");
1076 
1077     // add interface
1078     if(pInterface == nullptr) {
1079         pInterface = new GameInterface();
1080         if(gameState == GameState::Loading) {
1081             // when loading a save game we set radar directly
1082             pInterface->getRadarView().setRadarMode(pLocalHouse->hasRadarOn());
1083         } else if(pLocalHouse->hasRadarOn()) {
1084             // when starting a new game we switch the radar on with an animation if appropriate
1085             pInterface->getRadarView().switchRadarMode(true);
1086         }
1087     }
1088 
1089     gameState = GameState::Running;
1090 
1091     //setup endlevel conditions
1092     finishedLevel = false;
1093 
1094     bShowTime = winFlags & WINLOSEFLAGS_TIMEOUT;
1095 
1096     // Check if a player has lost
1097     for(int j = 0; j < NUM_HOUSES; j++) {
1098         if(house[j] != nullptr) {
1099             if(!house[j]->isAlive()) {
1100                 house[j]->lose(true);
1101             }
1102         }
1103     }
1104 
1105     if(bReplay) {
1106         cmdManager.setReadOnly(true);
1107     } else {
1108         char tmp[FILENAME_MAX];
1109         fnkdat("replay/auto.rpl", tmp, FILENAME_MAX, FNKDAT_USER | FNKDAT_CREAT);
1110         std::string replayname(tmp);
1111 
1112         OFileStream* pStream = new OFileStream();
1113         pStream->open(replayname);
1114 
1115         pStream->writeString(getLocalPlayerName());
1116 
1117         gameInitSettings.save(*pStream);
1118 
1119         // when this game was loaded we have to save the old commands to the replay file first
1120         cmdManager.save(*pStream);
1121 
1122         // now all new commands might be added
1123         cmdManager.setStream(pStream);
1124 
1125         // flush stream
1126         pStream->flush();
1127     }
1128 
1129     if(pNetworkManager != nullptr) {
1130         pNetworkManager->setOnReceiveChatMessage(std::bind(&ChatManager::addChatMessage, &(pInterface->getChatManager()), std::placeholders::_1, std::placeholders::_2));
1131         pNetworkManager->setOnReceiveCommandList(std::bind(&CommandManager::addCommandList, &cmdManager, std::placeholders::_1, std::placeholders::_2));
1132         pNetworkManager->setOnReceiveSelectionList(std::bind(&Game::onReceiveSelectionList, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
1133         pNetworkManager->setOnPeerDisconnected(std::bind(&Game::onPeerDisconnected, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
1134 
1135         cmdManager.setNetworkCycleBuffer( MILLI2CYCLES(pNetworkManager->getMaxPeerRoundTripTime()) + 5 );
1136     }
1137 
1138     // Change music to ingame music
1139     musicPlayer->changeMusic(MUSIC_PEACE);
1140 
1141 
1142     int     frameStart = SDL_GetTicks();
1143     int     frameTime = 0;
1144     int     numFrames = 0;
1145 
1146     //SDL_Log("Random Seed (GameCycle %d): 0x%0X", GameCycleCount, RandomGen.getSeed());
1147 
1148     //main game loop
1149     do {
1150         SDL_SetRenderTarget(renderer, screenTexture);
1151 
1152         // clear whole screen
1153         SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
1154         SDL_RenderClear(renderer);
1155 
1156         drawScreen();
1157 
1158         SDL_RenderPresent(renderer);
1159 
1160         SDL_SetRenderTarget(renderer, nullptr);
1161         SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
1162         SDL_RenderClear(renderer);
1163         SDL_RenderCopy(renderer, screenTexture, nullptr, nullptr);
1164         SDL_RenderPresent(renderer);
1165 
1166         int frameEnd = SDL_GetTicks();
1167 
1168         if(frameEnd == frameStart) {
1169             SDL_Delay(1);
1170         }
1171 
1172         frameTime += frameEnd - frameStart; // find difference to get frametime
1173         frameStart = SDL_GetTicks();
1174 
1175         numFrames++;
1176 
1177         if (bShowFPS) {
1178             averageFrameTime = 0.99f * averageFrameTime + 0.01f * frameTime;
1179         }
1180 
1181         if(settings.video.frameLimit == true) {
1182             if(frameTime < 32) {
1183                 SDL_Delay(32 - frameTime);
1184             }
1185         }
1186 
1187         if(finished) {
1188             // end timer for the ending message
1189             if(SDL_GetTicks() - finishedLevelTime > END_WAIT_TIME) {
1190                 finishedLevel = true;
1191             }
1192         }
1193 
1194         if(takePeriodicalScreenshots && ((gameCycleCount % (MILLI2CYCLES(10*1000))) == 0)) {
1195             takeScreenshot();
1196         }
1197 
1198 
1199         while( (frameTime > getGameSpeed()) || (!finished && (gameCycleCount < skipToGameCycle)) )  {
1200 
1201             bool bWaitForNetwork = false;
1202 
1203             if(pNetworkManager != nullptr) {
1204                 pNetworkManager->update();
1205 
1206                 // test if we need to wait for data to arrive
1207                 for(const std::string& playername : pNetworkManager->getConnectedPeers()) {
1208                     HumanPlayer* pPlayer = dynamic_cast<HumanPlayer*>(getPlayerByName(playername));
1209                     if(pPlayer != nullptr) {
1210                         if(pPlayer->nextExpectedCommandsCycle <= gameCycleCount) {
1211                             //SDL_Log("Cycle %d: Waiting for player '%s' to send data for cycle %d...", GameCycleCount, pPlayer->getPlayername().c_str(), pPlayer->nextExpectedCommandsCycle);
1212                             bWaitForNetwork = true;
1213                         }
1214                     }
1215                 }
1216 
1217                 if(bWaitForNetwork == true) {
1218                     if(startWaitingForOtherPlayersTime == 0) {
1219                         // we just started waiting
1220                         startWaitingForOtherPlayersTime = SDL_GetTicks();
1221                     } else {
1222                         if(SDL_GetTicks() - startWaitingForOtherPlayersTime > 1000) {
1223                             // we waited for more than one second
1224 
1225                             if(pWaitingForOtherPlayers == nullptr) {
1226                                 pWaitingForOtherPlayers = new WaitingForOtherPlayers();
1227                                 bMenu = true;
1228                             }
1229                         }
1230                     }
1231 
1232                     SDL_Delay(10);
1233                 } else {
1234                     startWaitingForOtherPlayersTime = 0;
1235                     delete pWaitingForOtherPlayers;
1236                     pWaitingForOtherPlayers = nullptr;
1237                 }
1238             }
1239 
1240             doInput();
1241             pInterface->updateObjectInterface();
1242 
1243             if(pNetworkManager != nullptr) {
1244                 if(bSelectionChanged) {
1245                     pNetworkManager->sendSelectedList(selectedList);
1246 
1247                     bSelectionChanged = false;
1248                 }
1249             }
1250 
1251             if(pInGameMentat != nullptr) {
1252                 pInGameMentat->update();
1253             }
1254 
1255             if(pWaitingForOtherPlayers != nullptr) {
1256                 pWaitingForOtherPlayers->update();
1257             }
1258 
1259             cmdManager.update();
1260 
1261             if(!bWaitForNetwork && !bPause) {
1262                 pInterface->getRadarView().update();
1263                 cmdManager.executeCommands(gameCycleCount);
1264 
1265 //              SDL_Log("cycle %d : %d", gameCycleCount, currentGame->randomGen.getSeed());
1266 
1267 #ifdef TEST_SYNC
1268                 // add every gamecycles one test sync command
1269                 if(bReplay == false) {
1270                     cmdManager.addCommand(Command(pLocalPlayer->getPlayerID(), CMD_TEST_SYNC, randomGen.getSeed()));
1271                 }
1272 #endif
1273 
1274                 for (int i = 0; i < NUM_HOUSES; i++) {
1275                     if (house[i] != nullptr) {
1276                         house[i]->update();
1277                     }
1278                 }
1279 
1280                 screenborder->update();
1281 
1282                 triggerManager.trigger(gameCycleCount);
1283 
1284                 processObjects();
1285 
1286                 if ((indicatorFrame != NONE_ID) && (--indicatorTimer <= 0)) {
1287                     indicatorTimer = indicatorTime;
1288 
1289                     if (++indicatorFrame > 2) {
1290                         indicatorFrame = NONE_ID;
1291                     }
1292                 }
1293 
1294                 gameCycleCount++;
1295             }
1296 
1297             if(gameCycleCount <= skipToGameCycle) {
1298                 frameTime = 0;
1299             } else {
1300                 frameTime -= getGameSpeed();
1301             }
1302         }
1303 
1304         musicPlayer->musicCheck();  //if song has finished, start playing next one
1305     } while (!bQuitGame && !finishedLevel);//not sure if we need this extra bool
1306 
1307 
1308 
1309     // Game is finished
1310 
1311     if(bReplay == false && currentGame->won == true) {
1312         // save replay
1313         char tmp[FILENAME_MAX];
1314 
1315         std::string mapnameBase = getBasename(gameInitSettings.getFilename(), true);
1316         fnkdat(std::string("replay/" + mapnameBase + ".rpl").c_str(), tmp, FILENAME_MAX, FNKDAT_USER | FNKDAT_CREAT);
1317         std::string replayname(tmp);
1318 
1319         OFileStream* pStream = new OFileStream();
1320         pStream->open(replayname);
1321         pStream->writeString(getLocalPlayerName());
1322         gameInitSettings.save(*pStream);
1323         cmdManager.save(*pStream);
1324         delete pStream;
1325     }
1326 
1327     if(pNetworkManager != nullptr) {
1328         pNetworkManager->disconnect();
1329     }
1330 
1331     gameState = GameState::Deinitialize;
1332     SDL_Log("Game finished!");
1333 }
1334 
1335 
resumeGame()1336 void Game::resumeGame()
1337 {
1338     bMenu = false;
1339     if(gameType != GameType::CustomMultiplayer) {
1340         bPause = false;
1341     }
1342 }
1343 
1344 
onOptions()1345 void Game::onOptions()
1346 {
1347     if(bReplay == true) {
1348         // don't show menu
1349         quitGame();
1350     } else {
1351         Uint32 color = SDL2RGB(palette[houseToPaletteIndex[pLocalHouse->getHouseID()] + 3]);
1352         pInGameMenu = new InGameMenu((gameType == GameType::CustomMultiplayer), color);
1353         bMenu = true;
1354         pauseGame();
1355     }
1356 }
1357 
1358 
onMentat()1359 void Game::onMentat()
1360 {
1361     pInGameMentat = new MentatHelp(pLocalHouse->getHouseID(), techLevel, gameInitSettings.getMission());
1362     bMenu = true;
1363     pauseGame();
1364 }
1365 
1366 
getNextGameInitSettings()1367 GameInitSettings Game::getNextGameInitSettings()
1368 {
1369     if(nextGameInitSettings.getGameType() != GameType::Invalid) {
1370         // return the prepared game init settings (load game or restart mission)
1371         return nextGameInitSettings;
1372     }
1373 
1374     switch(gameInitSettings.getGameType()) {
1375         case GameType::Campaign: {
1376             int currentMission = gameInitSettings.getMission();
1377             if(!won) {
1378                 currentMission -= (currentMission >= 22) ? 1 : 3;
1379             }
1380             int nextMission = gameInitSettings.getMission();
1381             Uint32 alreadyPlayedRegions = gameInitSettings.getAlreadyPlayedRegions();
1382             if(currentMission >= -1) {
1383                 // do map choice
1384                 SDL_Log("Map Choice...");
1385                 MapChoice* pMapChoice = new MapChoice(gameInitSettings.getHouseID(), currentMission, alreadyPlayedRegions);
1386                 pMapChoice->showMenu();
1387                 nextMission = pMapChoice->getSelectedMission();
1388                 alreadyPlayedRegions = pMapChoice->getAlreadyPlayedRegions();
1389                 delete pMapChoice;
1390             }
1391 
1392             Uint32 alreadyShownTutorialHints = won ? pLocalPlayer->getAlreadyShownTutorialHints() : gameInitSettings.getAlreadyShownTutorialHints();
1393             return GameInitSettings(gameInitSettings, nextMission, alreadyPlayedRegions, alreadyShownTutorialHints);
1394         } break;
1395 
1396         default: {
1397             SDL_Log("Game::getNextGameInitClass(): Wrong gameType for next Game.");
1398             return GameInitSettings();
1399         } break;
1400     }
1401 
1402     return GameInitSettings();
1403 }
1404 
1405 
whatNext()1406 int Game::whatNext()
1407 {
1408     if(whatNextParam != GAME_NOTHING) {
1409         int tmp = whatNextParam;
1410         whatNextParam = GAME_NOTHING;
1411         return tmp;
1412     }
1413 
1414     if(nextGameInitSettings.getGameType() != GameType::Invalid) {
1415         return GAME_LOAD;
1416     }
1417 
1418     switch(gameType) {
1419         case GameType::Campaign: {
1420             if(bQuitGame == true) {
1421                 return GAME_RETURN_TO_MENU;
1422             } else if(won == true) {
1423                 if(gameInitSettings.getMission() == 22) {
1424                     // there is no mission after this mission
1425                     whatNextParam = GAME_RETURN_TO_MENU;
1426                 } else {
1427                     // there is a mission after this mission
1428                     whatNextParam = GAME_NEXTMISSION;
1429                 }
1430                 return GAME_DEBRIEFING_WIN;
1431             } else {
1432                 // we need to play this mission again
1433                 whatNextParam = GAME_NEXTMISSION;
1434 
1435                 return GAME_DEBRIEFING_LOST;
1436             }
1437         } break;
1438 
1439         case GameType::Skirmish: {
1440             if(bQuitGame == true) {
1441                 return GAME_RETURN_TO_MENU;
1442             } else if(won == true) {
1443                 whatNextParam = GAME_RETURN_TO_MENU;
1444                 return GAME_DEBRIEFING_WIN;
1445             } else {
1446                 whatNextParam = GAME_RETURN_TO_MENU;
1447                 return GAME_DEBRIEFING_LOST;
1448             }
1449         } break;
1450 
1451         case GameType::CustomGame:
1452         case GameType::CustomMultiplayer: {
1453             if(bQuitGame == true) {
1454                 return GAME_RETURN_TO_MENU;
1455             } else {
1456                 whatNextParam = GAME_RETURN_TO_MENU;
1457                 return GAME_CUSTOM_GAME_STATS;
1458             }
1459         } break;
1460 
1461         default: {
1462             return GAME_RETURN_TO_MENU;
1463         } break;
1464     }
1465 }
1466 
1467 
loadSaveGame(const std::string & filename)1468 bool Game::loadSaveGame(const std::string& filename) {
1469     IFileStream fs;
1470 
1471     if(fs.open(filename) == false) {
1472         return false;
1473     }
1474 
1475     bool ret = loadSaveGame(fs);
1476 
1477     fs.close();
1478 
1479     return ret;
1480 }
1481 
loadSaveGame(InputStream & stream)1482 bool Game::loadSaveGame(InputStream& stream) {
1483     gameState = GameState::Loading;
1484 
1485     Uint32 magicNum = stream.readUint32();
1486     if (magicNum != SAVEMAGIC) {
1487         SDL_Log("Game::loadSaveGame(): No valid savegame! Expected magic number %.8X, but got %.8X!", SAVEMAGIC, magicNum);
1488         return false;
1489     }
1490 
1491     Uint32 savegameVersion = stream.readUint32();
1492     if (savegameVersion != SAVEGAMEVERSION) {
1493         SDL_Log("Game::loadSaveGame(): No valid savegame! Expected savegame version %d, but got %d!", SAVEGAMEVERSION, savegameVersion);
1494         return false;
1495     }
1496 
1497     std::string duneVersion = stream.readString();
1498 
1499     // if this is a multiplayer load we need to save some information before we overwrite gameInitSettings with the settings saved in the savegame
1500     bool bMultiplayerLoad = (gameInitSettings.getGameType() == GameType::LoadMultiplayer);
1501     GameInitSettings::HouseInfoList oldHouseInfoList = gameInitSettings.getHouseInfoList();
1502 
1503     // read gameInitSettings
1504     gameInitSettings = GameInitSettings(stream);
1505 
1506     // read the actual house setup choosen at the beginning of the game
1507     Uint32 numHouseInfo = stream.readUint32();
1508     for(Uint32 i=0;i<numHouseInfo;i++) {
1509         houseInfoListSetup.push_back(GameInitSettings::HouseInfo(stream));
1510     }
1511 
1512     //read map size
1513     short mapSizeX = stream.readUint32();
1514     short mapSizeY = stream.readUint32();
1515 
1516     //create the new map
1517     currentGameMap = new Map(mapSizeX, mapSizeY);
1518 
1519     //read GameCycleCount
1520     gameCycleCount = stream.readUint32();
1521 
1522     // read some settings
1523     gameType = static_cast<GameType>(stream.readSint8());
1524     techLevel = stream.readUint8();
1525     randomGen.setSeed(stream.readUint32());
1526 
1527     // read in the unit/structure data
1528     objectData.load(stream);
1529 
1530     //load the house(s) info
1531     for(int i=0; i<NUM_HOUSES; i++) {
1532         if (stream.readBool() == true) {
1533             //house in game
1534             house[i] = new House(stream);
1535         }
1536     }
1537 
1538     // we have to set the local player
1539     if(bMultiplayerLoad) {
1540         // get it from the gameInitSettings that started the game (not the one saved in the savegame)
1541         for(const GameInitSettings::HouseInfo& houseInfo : oldHouseInfoList) {
1542 
1543             // find the right house
1544             for(int i=0;i<NUM_HOUSES;i++) {
1545                 if((house[i] != nullptr) && (house[i]->getHouseID() == houseInfo.houseID)) {
1546                     // iterate over all players
1547                     auto& players = house[i]->getPlayerList();
1548                     std::list<std::shared_ptr<Player> >::const_iterator playerIter = players.begin();
1549                     for(const GameInitSettings::PlayerInfo& playerInfo : houseInfo.playerInfoList) {
1550                         if(playerInfo.playerClass == HUMANPLAYERCLASS) {
1551                             while(playerIter != players.end()) {
1552 
1553                                 std::shared_ptr<HumanPlayer> humanPlayer = std::dynamic_pointer_cast<HumanPlayer>(*playerIter);
1554                                 if(humanPlayer.get() != nullptr) {
1555                                     // we have actually found a human player and now assign the first unused name to it
1556                                     unregisterPlayer(humanPlayer.get());
1557                                     humanPlayer->setPlayername(playerInfo.playerName);
1558                                     registerPlayer(humanPlayer.get());
1559 
1560                                     if(playerInfo.playerName == getLocalPlayerName()) {
1561                                         pLocalHouse = house[i];
1562                                         pLocalPlayer = humanPlayer.get();
1563                                     }
1564 
1565                                     ++playerIter;
1566                                     break;
1567                                 } else {
1568                                     ++playerIter;
1569                                 }
1570                             }
1571                         }
1572                     }
1573                 }
1574             }
1575         }
1576     } else {
1577         // it is stored in the savegame, so set it up
1578         Uint8 localPlayerID = stream.readUint8();
1579         pLocalPlayer = dynamic_cast<HumanPlayer*>(getPlayerByID(localPlayerID));
1580         pLocalHouse = house[pLocalPlayer->getHouse()->getHouseID()];
1581     }
1582 
1583     debug = stream.readBool();
1584     bCheatsEnabled = stream.readBool();
1585 
1586     winFlags = stream.readUint32();
1587     loseFlags = stream.readUint32();
1588 
1589     currentGameMap->load(stream);
1590 
1591     //load the structures and units
1592     objectManager.load(stream);
1593 
1594     int numBullets = stream.readUint32();
1595     for(int i = 0; i < numBullets; i++) {
1596         bulletList.push_back(new Bullet(stream));
1597     }
1598 
1599     int numExplosions = stream.readUint32();
1600     for(int i = 0; i < numExplosions; i++) {
1601         explosionList.push_back(new Explosion(stream));
1602     }
1603 
1604     if(bMultiplayerLoad) {
1605         screenborder->adjustScreenBorderToMapsize(currentGameMap->getSizeX(), currentGameMap->getSizeY());
1606 
1607         screenborder->setNewScreenCenter(pLocalHouse->getCenterOfMainBase()*TILESIZE);
1608 
1609     } else {
1610         //load selection list
1611         selectedList = stream.readUint32Set();
1612 
1613         //load the screenborder info
1614         screenborder->adjustScreenBorderToMapsize(currentGameMap->getSizeX(), currentGameMap->getSizeY());
1615         screenborder->load(stream);
1616     }
1617 
1618     // load triggers
1619     triggerManager.load(stream);
1620 
1621     // CommandManager is at the very end of the file. DO NOT CHANGE THIS!
1622     cmdManager.load(stream);
1623 
1624     finished = false;
1625 
1626     return true;
1627 }
1628 
1629 
saveGame(const std::string & filename)1630 bool Game::saveGame(const std::string& filename)
1631 {
1632     OFileStream fs;
1633 
1634     if(fs.open(filename) == false) {
1635         SDL_Log("Game::saveGame(): %s", strerror(errno));
1636         currentGame->addToNewsTicker(std::string("Game NOT saved: Cannot open \"") + filename + "\".");
1637         return false;
1638     }
1639 
1640     fs.writeUint32(SAVEMAGIC);
1641 
1642     fs.writeUint32(SAVEGAMEVERSION);
1643 
1644     fs.writeString(VERSIONSTRING);
1645 
1646     // write gameInitSettings
1647     gameInitSettings.save(fs);
1648 
1649     fs.writeUint32(houseInfoListSetup.size());
1650     for(const GameInitSettings::HouseInfo& houseInfo : houseInfoListSetup) {
1651         houseInfo.save(fs);
1652     }
1653 
1654     //write the map size
1655     fs.writeUint32(currentGameMap->getSizeX());
1656     fs.writeUint32(currentGameMap->getSizeY());
1657 
1658     // write GameCycleCount
1659     fs.writeUint32(gameCycleCount);
1660 
1661     // write some settings
1662     fs.writeSint8(static_cast<Sint8>(gameType));
1663     fs.writeUint8(techLevel);
1664     fs.writeUint32(randomGen.getSeed());
1665 
1666     // write out the unit/structure data
1667     objectData.save(fs);
1668 
1669     //write the house(s) info
1670     for(int i=0; i<NUM_HOUSES; i++) {
1671         fs.writeBool(house[i] != nullptr);
1672 
1673         if(house[i] != nullptr) {
1674             house[i]->save(fs);
1675         }
1676     }
1677 
1678     if(gameInitSettings.getGameType() != GameType::CustomMultiplayer) {
1679         fs.writeUint8(pLocalPlayer->getPlayerID());
1680     }
1681 
1682     fs.writeBool(debug);
1683     fs.writeBool(bCheatsEnabled);
1684 
1685     fs.writeUint32(winFlags);
1686     fs.writeUint32(loseFlags);
1687 
1688     currentGameMap->save(fs);
1689 
1690     // save the structures and units
1691     objectManager.save(fs);
1692 
1693     fs.writeUint32(bulletList.size());
1694     for(const Bullet* pBullet : bulletList) {
1695         pBullet->save(fs);
1696     }
1697 
1698     fs.writeUint32(explosionList.size());
1699     for(const Explosion* pExplosion : explosionList) {
1700         pExplosion->save(fs);
1701     }
1702 
1703     if(gameInitSettings.getGameType() != GameType::CustomMultiplayer) {
1704         // save selection lists
1705 
1706         // write out selected units list
1707         fs.writeUint32Set(selectedList);
1708 
1709         // write the screenborder info
1710         screenborder->save(fs);
1711     }
1712 
1713     // save triggers
1714     triggerManager.save(fs);
1715 
1716     // CommandManager is at the very end of the file. DO NOT CHANGE THIS!
1717     cmdManager.save(fs);
1718 
1719     fs.close();
1720 
1721     return true;
1722 }
1723 
1724 
saveObject(OutputStream & stream,ObjectBase * obj)1725 void Game::saveObject(OutputStream& stream, ObjectBase* obj) {
1726     if(obj == nullptr)
1727         return;
1728 
1729     stream.writeUint32(obj->getItemID());
1730     obj->save(stream);
1731 }
1732 
1733 
loadObject(InputStream & stream,Uint32 objectID)1734 ObjectBase* Game::loadObject(InputStream& stream, Uint32 objectID)
1735 {
1736     Uint32 itemID;
1737 
1738     itemID = stream.readUint32();
1739 
1740     ObjectBase* newObject = ObjectBase::loadObject(stream, itemID, objectID);
1741     if(newObject == nullptr) {
1742         THROW(std::runtime_error, "Error while loading an object!");
1743     }
1744 
1745     return newObject;
1746 }
1747 
1748 
selectAll(const std::set<Uint32> & aList)1749 void Game::selectAll(const std::set<Uint32>& aList)
1750 {
1751     for(Uint32 objectID : aList) {
1752         objectManager.getObject(objectID)->setSelected(true);
1753     }
1754 }
1755 
1756 
unselectAll(const std::set<Uint32> & aList)1757 void Game::unselectAll(const std::set<Uint32>& aList)
1758 {
1759     for(Uint32 objectID : aList) {
1760         objectManager.getObject(objectID)->setSelected(false);
1761     }
1762 }
1763 
onReceiveSelectionList(const std::string & name,const std::set<Uint32> & newSelectionList,int groupListIndex)1764 void Game::onReceiveSelectionList(const std::string& name, const std::set<Uint32>& newSelectionList, int groupListIndex)
1765 {
1766     HumanPlayer* pHumanPlayer = dynamic_cast<HumanPlayer*>(getPlayerByName(name));
1767 
1768     if(pHumanPlayer == nullptr) {
1769         return;
1770     }
1771 
1772     if(groupListIndex == -1) {
1773         // the other player controlling the same house has selected some units
1774 
1775         if(pHumanPlayer->getHouse() != pLocalHouse) {
1776             return;
1777         }
1778 
1779         for(Uint32 objectID : selectedByOtherPlayerList) {
1780             ObjectBase* pObject = objectManager.getObject(objectID);
1781             if(pObject != nullptr) {
1782                 pObject->setSelectedByOtherPlayer(false);
1783             }
1784         }
1785 
1786         selectedByOtherPlayerList = newSelectionList;
1787 
1788         for(Uint32 objectID : selectedByOtherPlayerList) {
1789             ObjectBase* pObject = objectManager.getObject(objectID);
1790             if(pObject != nullptr) {
1791                 pObject->setSelectedByOtherPlayer(true);
1792             }
1793         }
1794     } else {
1795         // some other player has assigned a number to a list of units
1796         pHumanPlayer->setGroupList(groupListIndex, newSelectionList);
1797     }
1798 }
1799 
onPeerDisconnected(const std::string & name,bool bHost,int cause)1800 void Game::onPeerDisconnected(const std::string& name, bool bHost, int cause) {
1801     pInterface->getChatManager().addInfoMessage(name + " disconnected!");
1802 }
1803 
setGameWon()1804 void Game::setGameWon() {
1805     if(!bQuitGame && !finished) {
1806         won = true;
1807         finished = true;
1808         finishedLevelTime = SDL_GetTicks();
1809         soundPlayer->playVoice(YourMissionIsComplete,pLocalHouse->getHouseID());
1810     }
1811 }
1812 
1813 
setGameLost()1814 void Game::setGameLost() {
1815     if(!bQuitGame && !finished) {
1816         won = false;
1817         finished = true;
1818         finishedLevelTime = SDL_GetTicks();
1819         soundPlayer->playVoice(YouHaveFailedYourMission,pLocalHouse->getHouseID());
1820     }
1821 }
1822 
1823 
onRadarClick(Coord worldPosition,bool bRightMouseButton,bool bDrag)1824 bool Game::onRadarClick(Coord worldPosition, bool bRightMouseButton, bool bDrag) {
1825     if(bRightMouseButton) {
1826 
1827         if(handleSelectedObjectsActionClick(worldPosition.x / TILESIZE, worldPosition.y / TILESIZE)) {
1828             indicatorFrame = 0;
1829             indicatorPosition = worldPosition;
1830         }
1831 
1832         return false;
1833     } else {
1834 
1835         if(bDrag) {
1836             screenborder->setNewScreenCenter(worldPosition);
1837             return true;
1838         } else {
1839 
1840             switch(currentCursorMode) {
1841                 case CursorMode_Attack: {
1842                     handleSelectedObjectsAttackClick(worldPosition.x / TILESIZE, worldPosition.y / TILESIZE);
1843                     return false;
1844                 } break;
1845 
1846                 case CursorMode_Move: {
1847                     handleSelectedObjectsMoveClick(worldPosition.x / TILESIZE, worldPosition.y / TILESIZE);
1848                     return false;
1849                 } break;
1850 
1851                 case CursorMode_Capture: {
1852                     handleSelectedObjectsCaptureClick(worldPosition.x / TILESIZE, worldPosition.y / TILESIZE);
1853                     return false;
1854                 } break;
1855 
1856                 case CursorMode_CarryallDrop: {
1857                     handleSelectedObjectsRequestCarryallDropClick(worldPosition.x / TILESIZE, worldPosition.y / TILESIZE);
1858                     return false;
1859                 } break;
1860 
1861                 case CursorMode_Normal:
1862                 default: {
1863                     screenborder->setNewScreenCenter(worldPosition);
1864                     return true;
1865                 } break;
1866             }
1867         }
1868     }
1869 }
1870 
1871 
isOnRadarView(int mouseX,int mouseY)1872 bool Game::isOnRadarView(int mouseX, int mouseY) {
1873     return pInterface->getRadarView().isOnRadar(mouseX - (sideBarPos.x + SIDEBAR_COLUMN_WIDTH), mouseY - sideBarPos.y);
1874 }
1875 
1876 
handleChatInput(SDL_KeyboardEvent & keyboardEvent)1877 void Game::handleChatInput(SDL_KeyboardEvent& keyboardEvent) {
1878     if(keyboardEvent.keysym.sym == SDLK_ESCAPE) {
1879         chatMode = false;
1880     } else if(keyboardEvent.keysym.sym == SDLK_RETURN) {
1881         if(typingChatMessage.length() > 0) {
1882             unsigned char md5sum[16];
1883 
1884             md5((const unsigned char*) typingChatMessage.c_str(), typingChatMessage.size(), md5sum);
1885 
1886             std::stringstream md5stream;
1887             md5stream << std::setfill('0') << std::hex << std::uppercase << "0x";
1888             for(int i=0;i<16;i++) {
1889                 md5stream << std::setw(2) << (int) md5sum[i];
1890             }
1891 
1892             std::string md5string = md5stream.str();
1893 
1894             if((bCheatsEnabled == false) && (md5string == "0xB8766C8EC7A61036B69893FC17AAF21E")) {
1895                 bCheatsEnabled = true;
1896                 pInterface->getChatManager().addInfoMessage("Cheat mode enabled");
1897             } else if((bCheatsEnabled == true) && (md5string == "0xB8766C8EC7A61036B69893FC17AAF21E")) {
1898                 pInterface->getChatManager().addInfoMessage("Cheat mode already enabled");
1899             } else if((bCheatsEnabled == true) && (md5string == "0x57583291CB37F8167EDB0611D8D19E58")) {
1900                 if (gameType != GameType::CustomMultiplayer) {
1901                     pInterface->getChatManager().addInfoMessage("You win this game");
1902                     setGameWon();
1903                 }
1904             } else if((bCheatsEnabled == true) && (md5string == "0x1A12BE3DBE54C5A504CAA6EE9782C1C8")) {
1905                 if(debug == true) {
1906                     pInterface->getChatManager().addInfoMessage("You are already in debug mode");
1907                 } else if (gameType != GameType::CustomMultiplayer) {
1908                     pInterface->getChatManager().addInfoMessage("Debug mode enabled");
1909                     debug = true;
1910                 }
1911             } else if((bCheatsEnabled == true) && (md5string == "0x54F68155FC64A5BC66DCD50C1E925C0B")) {
1912                 if(debug == false) {
1913                     pInterface->getChatManager().addInfoMessage("You are not in debug mode");
1914                 } else if (gameType != GameType::CustomMultiplayer) {
1915                     pInterface->getChatManager().addInfoMessage("Debug mode disabled");
1916                     debug = false;
1917                 }
1918             } else if((bCheatsEnabled == true) && (md5string == "0xCEF1D26CE4B145DE985503CA35232ED8")) {
1919                 if (gameType != GameType::CustomMultiplayer) {
1920                     pInterface->getChatManager().addInfoMessage("You got some credits");
1921                     pLocalHouse->returnCredits(10000);
1922                 }
1923             } else {
1924                 if(pNetworkManager != nullptr) {
1925                     pNetworkManager->sendChatMessage(typingChatMessage);
1926                 }
1927                 pInterface->getChatManager().addChatMessage(getLocalPlayerName(), typingChatMessage);
1928             }
1929         }
1930 
1931         chatMode = false;
1932     } else if(keyboardEvent.keysym.sym == SDLK_BACKSPACE) {
1933         if(typingChatMessage.length() > 0) {
1934             typingChatMessage.resize(typingChatMessage.length() - 1);
1935         }
1936     }
1937 }
1938 
1939 
handleKeyInput(SDL_KeyboardEvent & keyboardEvent)1940 void Game::handleKeyInput(SDL_KeyboardEvent& keyboardEvent) {
1941     switch(keyboardEvent.keysym.sym) {
1942 
1943         case SDLK_0: {
1944             //if ctrl and 0 remove selected units from all groups
1945             if(SDL_GetModState() & KMOD_CTRL) {
1946                 for(Uint32 objectID : selectedList) {
1947                     ObjectBase* pObject = objectManager.getObject(objectID);
1948                     pObject->setSelected(false);
1949                     pObject->removeFromSelectionLists();
1950                     for(int i=0; i < NUMSELECTEDLISTS; i++) {
1951                         pLocalPlayer->getGroupList(i).erase(objectID);
1952                     }
1953                 }
1954                 selectedList.clear();
1955                 currentGame->selectionChanged();
1956                 currentCursorMode = CursorMode_Normal;
1957             } else {
1958                 for(Uint32 objectID : selectedList) {
1959                     objectManager.getObject(objectID)->setSelected(false);
1960                 }
1961                 selectedList.clear();
1962                 currentGame->selectionChanged();
1963                 currentCursorMode = CursorMode_Normal;
1964             }
1965         } break;
1966 
1967         case SDLK_1:
1968         case SDLK_2:
1969         case SDLK_3:
1970         case SDLK_4:
1971         case SDLK_5:
1972         case SDLK_6:
1973         case SDLK_7:
1974         case SDLK_8:
1975         case SDLK_9: {
1976             //for SDLK_1 to SDLK_9 select group with that number, if ctrl create group from selected obj
1977             int selectListIndex = keyboardEvent.keysym.sym - SDLK_1;
1978 
1979             if(SDL_GetModState() & KMOD_CTRL) {
1980                 pLocalPlayer->setGroupList(selectListIndex, selectedList);
1981 
1982                 pInterface->updateObjectInterface();
1983             } else {
1984                 std::set<Uint32>& groupList = pLocalPlayer->getGroupList(selectListIndex);
1985 
1986                 // find out if we are choosing a group with all items already selected
1987                 bool bEverythingWasSelected = (selectedList.size() == groupList.size());
1988                 Coord averagePosition;
1989                 for(Uint32 objectID : groupList) {
1990                     ObjectBase* pObject = objectManager.getObject(objectID);
1991                     bEverythingWasSelected = bEverythingWasSelected && pObject->isSelected();
1992                     averagePosition += pObject->getLocation();
1993                 }
1994 
1995                 if(groupList.empty() == false) {
1996                     averagePosition /= groupList.size();
1997                 }
1998 
1999 
2000                 if(SDL_GetModState() & KMOD_SHIFT) {
2001                     // we add the items from this list to the list of selected items
2002                 } else {
2003                     // we replace the list of the selected items with the items from this list
2004                     unselectAll(selectedList);
2005                     selectedList.clear();
2006                     currentGame->selectionChanged();
2007                 }
2008 
2009                 // now we add the selected items
2010                 for(Uint32 objectID : groupList) {
2011                     ObjectBase* pObject = objectManager.getObject(objectID);
2012                     if(pObject->getOwner() == pLocalHouse) {
2013                         pObject->setSelected(true);
2014                         selectedList.insert(pObject->getObjectID());
2015                         currentGame->selectionChanged();
2016                     }
2017                 }
2018 
2019                 if(bEverythingWasSelected && (groupList.empty() == false)) {
2020                     // we center around the newly selected units/structures
2021                     screenborder->setNewScreenCenter(averagePosition*TILESIZE);
2022                 }
2023             }
2024             currentCursorMode = CursorMode_Normal;
2025         } break;
2026 
2027         case SDLK_KP_MINUS:
2028         case SDLK_MINUS: {
2029             if(gameType != GameType::CustomMultiplayer) {
2030                 settings.gameOptions.gameSpeed = std::min(settings.gameOptions.gameSpeed+1,GAMESPEED_MAX);
2031                 INIFile myINIFile(getConfigFilepath());
2032                 myINIFile.setIntValue("Game Options","Game Speed", settings.gameOptions.gameSpeed);
2033                 myINIFile.saveChangesTo(getConfigFilepath());
2034                 currentGame->addToNewsTicker(fmt::sprintf(_("Game speed") + ": %d", settings.gameOptions.gameSpeed));
2035             }
2036         } break;
2037 
2038         case SDLK_KP_PLUS:
2039         case SDLK_PLUS:
2040         case SDLK_EQUALS: {
2041             if(gameType != GameType::CustomMultiplayer) {
2042                 settings.gameOptions.gameSpeed = std::max(settings.gameOptions.gameSpeed-1,GAMESPEED_MIN);
2043                 INIFile myINIFile(getConfigFilepath());
2044                 myINIFile.setIntValue("Game Options","Game Speed", settings.gameOptions.gameSpeed);
2045                 myINIFile.saveChangesTo(getConfigFilepath());
2046                 currentGame->addToNewsTicker(fmt::sprintf(_("Game speed") + ": %d", settings.gameOptions.gameSpeed));
2047             }
2048         } break;
2049 
2050         case SDLK_c: {
2051             //set object to capture
2052             if(currentCursorMode != CursorMode_Capture) {
2053                 for(Uint32 objectID : selectedList) {
2054                     ObjectBase* pObject = objectManager.getObject(objectID);
2055                     if(pObject->isAUnit() && (pObject->getOwner() == pLocalHouse) && pObject->isRespondable() && pObject->canAttack() && pObject->isInfantry()) {
2056                         currentCursorMode = CursorMode_Capture;
2057                         break;
2058                     }
2059                 }
2060             }
2061         } break;
2062 
2063         case SDLK_a: {
2064             //set object to attack
2065             if(currentCursorMode != CursorMode_Attack) {
2066                 for(Uint32 objectID : selectedList) {
2067                     ObjectBase* pObject = objectManager.getObject(objectID);
2068                     House* pOwner = pObject->getOwner();
2069                     if(pObject->isAUnit() && (pOwner == pLocalHouse) && pObject->isRespondable() && pObject->canAttack()) {
2070                         currentCursorMode = CursorMode_Attack;
2071                         break;
2072                     } else if((pObject->getItemID() == Structure_Palace) && ((pOwner->getHouseID() == HOUSE_HARKONNEN) || (pOwner->getHouseID() == HOUSE_SARDAUKAR))) {
2073                         if(static_cast<Palace*>(pObject)->isSpecialWeaponReady()) {
2074                             currentCursorMode = CursorMode_Attack;
2075                             break;
2076                         }
2077                     }
2078                 }
2079             }
2080         } break;
2081 
2082         case SDLK_t: {
2083             bShowTime = !bShowTime;
2084         } break;
2085 
2086         case SDLK_ESCAPE: {
2087             onOptions();
2088         } break;
2089 
2090         case SDLK_F1: {
2091             Coord oldCenterCoord = screenborder->getCurrentCenter();
2092             currentZoomlevel = 0;
2093             screenborder->adjustScreenBorderToMapsize(currentGameMap->getSizeX(), currentGameMap->getSizeY());
2094             screenborder->setNewScreenCenter(oldCenterCoord);
2095         } break;
2096 
2097         case SDLK_F2: {
2098             Coord oldCenterCoord = screenborder->getCurrentCenter();
2099             currentZoomlevel = 1;
2100             screenborder->adjustScreenBorderToMapsize(currentGameMap->getSizeX(), currentGameMap->getSizeY());
2101             screenborder->setNewScreenCenter(oldCenterCoord);
2102         } break;
2103 
2104         case SDLK_F3: {
2105             Coord oldCenterCoord = screenborder->getCurrentCenter();
2106             currentZoomlevel = 2;
2107             screenborder->adjustScreenBorderToMapsize(currentGameMap->getSizeX(), currentGameMap->getSizeY());
2108             screenborder->setNewScreenCenter(oldCenterCoord);
2109         } break;
2110 
2111         case SDLK_F4: {
2112             // skip a 30 seconds
2113             if(gameType != GameType::CustomMultiplayer || bReplay) {
2114                 skipToGameCycle = gameCycleCount + (10*1000)/GAMESPEED_DEFAULT;
2115             }
2116         } break;
2117 
2118         case SDLK_F5: {
2119             // skip a 30 seconds
2120             if(gameType != GameType::CustomMultiplayer || bReplay) {
2121                 skipToGameCycle = gameCycleCount + (30*1000)/GAMESPEED_DEFAULT;
2122             }
2123         } break;
2124 
2125         case SDLK_F6: {
2126             // skip 2 minutes
2127             if(gameType != GameType::CustomMultiplayer || bReplay) {
2128                 skipToGameCycle = gameCycleCount + (120*1000)/GAMESPEED_DEFAULT;
2129             }
2130         } break;
2131 
2132         case SDLK_F10: {
2133             soundPlayer->toggleSound();
2134         } break;
2135 
2136         case SDLK_F11: {
2137             musicPlayer->toggleSound();
2138         } break;
2139 
2140         case SDLK_F12: {
2141             bShowFPS = !bShowFPS;
2142         } break;
2143 
2144         case SDLK_m: {
2145             //set object to move
2146             if(currentCursorMode != CursorMode_Move) {
2147                 for(Uint32 objectID : selectedList) {
2148                     ObjectBase* pObject = objectManager.getObject(objectID);
2149                     if(pObject->isAUnit() && (pObject->getOwner() == pLocalHouse) && pObject->isRespondable()) {
2150                         currentCursorMode = CursorMode_Move;
2151                         break;
2152                     }
2153                 }
2154             }
2155         } break;
2156 
2157         case SDLK_g: {
2158             // select next construction yard
2159             std::set<Uint32> itemIDs;
2160             itemIDs.insert(Structure_ConstructionYard);
2161             selectNextStructureOfType(itemIDs);
2162         } break;
2163 
2164         case SDLK_f: {
2165             // select next factory
2166             std::set<Uint32> itemIDs;
2167             itemIDs.insert(Structure_Barracks);
2168             itemIDs.insert(Structure_WOR);
2169             itemIDs.insert(Structure_LightFactory);
2170             itemIDs.insert(Structure_HeavyFactory);
2171             itemIDs.insert(Structure_HighTechFactory);
2172             itemIDs.insert(Structure_StarPort);
2173             selectNextStructureOfType(itemIDs);
2174         } break;
2175 
2176         case SDLK_p: {
2177             if(SDL_GetModState() & KMOD_CTRL) {
2178                 // fall through to SDLK_PRINT
2179             } else {
2180                 // Place structure
2181                 if(selectedList.size() == 1) {
2182                     ConstructionYard* pConstructionYard = dynamic_cast<ConstructionYard*>(objectManager.getObject(*selectedList.begin()));
2183                     if(pConstructionYard != nullptr) {
2184                         if(currentCursorMode == CursorMode_Placing) {
2185                             currentCursorMode = CursorMode_Normal;
2186                         } else if(pConstructionYard->isWaitingToPlace()) {
2187                             currentCursorMode = CursorMode_Placing;
2188                         }
2189                     }
2190                 }
2191 
2192                 break;  // do not fall through
2193             }
2194 
2195         } // fall through
2196 
2197         case SDLK_PRINTSCREEN:
2198         case SDLK_SYSREQ: {
2199             if(SDL_GetModState() & KMOD_SHIFT) {
2200                 takePeriodicalScreenshots = !takePeriodicalScreenshots;
2201             } else {
2202                 takeScreenshot();
2203             }
2204         } break;
2205 
2206         case SDLK_h: {
2207             for(Uint32 objectID : selectedList) {
2208                 ObjectBase* pObject = objectManager.getObject(objectID);
2209                 if(pObject->getItemID() == Unit_Harvester) {
2210                     static_cast<Harvester*>(pObject)->handleReturnClick();
2211                 }
2212             }
2213         } break;
2214 
2215 
2216         case SDLK_r: {
2217             for(Uint32 objectID : selectedList) {
2218                 ObjectBase* pObject = objectManager.getObject(objectID);
2219                 if(pObject->isAStructure()) {
2220                     static_cast<StructureBase*>(pObject)->handleRepairClick();
2221                 } else if(pObject->isAGroundUnit() && pObject->getHealth() < pObject->getMaxHealth()) {
2222                     static_cast<GroundUnit*>(pObject)->handleSendToRepairClick();
2223                 }
2224             }
2225         } break;
2226 
2227 
2228         case SDLK_d: {
2229             if(currentCursorMode != CursorMode_CarryallDrop){
2230                 for(Uint32 objectID : selectedList) {
2231                     ObjectBase* pObject = objectManager.getObject(objectID);
2232                     if(pObject->isAGroundUnit() && pObject->getOwner()->hasCarryalls()) {
2233                         currentCursorMode = CursorMode_CarryallDrop;
2234                     }
2235                 }
2236             }
2237 
2238         } break;
2239 
2240         case SDLK_u: {
2241             for(Uint32 objectID : selectedList) {
2242                 ObjectBase* pObject = objectManager.getObject(objectID);
2243                 if(pObject->isABuilder()) {
2244                     BuilderBase* pBuilder = dynamic_cast<BuilderBase*>(pObject);
2245                     if(pBuilder->getHealth() >= pBuilder->getMaxHealth() && pBuilder->isAllowedToUpgrade()) {
2246                         pBuilder->handleUpgradeClick();
2247                     }
2248                 }
2249             }
2250         } break;
2251 
2252         case SDLK_RETURN: {
2253             if(SDL_GetModState() & KMOD_ALT) {
2254                 toogleFullscreen();
2255             } else {
2256                 typingChatMessage = "";
2257                 chatMode = true;
2258             }
2259         } break;
2260 
2261         case SDLK_TAB: {
2262             if(SDL_GetModState() & KMOD_ALT) {
2263                 SDL_MinimizeWindow(window);
2264             }
2265         } break;
2266 
2267         case SDLK_SPACE: {
2268             if(gameType != GameType::CustomMultiplayer) {
2269                 if(bPause) {
2270                     resumeGame();
2271                     pInterface->getChatManager().addInfoMessage(_("Game resumed!"));
2272                 } else {
2273                     pauseGame();
2274                     pInterface->getChatManager().addInfoMessage(_("Game paused!"));
2275                 }
2276             }
2277         } break;
2278 
2279         default: {
2280         } break;
2281     }
2282 }
2283 
2284 
handlePlacementClick(int xPos,int yPos)2285 bool Game::handlePlacementClick(int xPos, int yPos) {
2286     BuilderBase* pBuilder = nullptr;
2287 
2288     if(selectedList.size() == 1) {
2289         pBuilder = dynamic_cast<BuilderBase*>(objectManager.getObject(*selectedList.begin()));
2290     }
2291 
2292     int placeItem = pBuilder->getCurrentProducedItem();
2293     Coord structuresize = getStructureSize(placeItem);
2294 
2295     if(placeItem == Structure_Slab1) {
2296         if((currentGameMap->isWithinBuildRange(xPos, yPos, pBuilder->getOwner()))
2297             && (currentGameMap->okayToPlaceStructure(xPos, yPos, 1, 1, false, pBuilder->getOwner()))
2298             && (currentGameMap->getTile(xPos, yPos)->isConcrete() == false)) {
2299             getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_PLACE_STRUCTURE,pBuilder->getObjectID(), xPos, yPos));
2300             //the user has tried to place and has been successful
2301             soundPlayer->playSound(Sound_PlaceStructure);
2302             currentCursorMode = CursorMode_Normal;
2303             return true;
2304         } else {
2305             //the user has tried to place but clicked on impossible point
2306             currentGame->addToNewsTicker(_("@DUNE.ENG|135#Cannot place slab here."));
2307             soundPlayer->playSound(Sound_InvalidAction);    //can't place noise
2308             return false;
2309         }
2310     } else if(placeItem == Structure_Slab4) {
2311         if( (currentGameMap->isWithinBuildRange(xPos, yPos, pBuilder->getOwner()) || currentGameMap->isWithinBuildRange(xPos+1, yPos, pBuilder->getOwner())
2312                 || currentGameMap->isWithinBuildRange(xPos+1, yPos+1, pBuilder->getOwner()) || currentGameMap->isWithinBuildRange(xPos, yPos+1, pBuilder->getOwner()))
2313             && ((currentGameMap->okayToPlaceStructure(xPos, yPos, 1, 1, false, pBuilder->getOwner())
2314                 || currentGameMap->okayToPlaceStructure(xPos+1, yPos, 1, 1, false, pBuilder->getOwner())
2315                 || currentGameMap->okayToPlaceStructure(xPos+1, yPos+1, 1, 1, false, pBuilder->getOwner())
2316                 || currentGameMap->okayToPlaceStructure(xPos, yPos, 1, 1+1, false, pBuilder->getOwner())))
2317             && ((currentGameMap->getTile(xPos, yPos)->isConcrete() == false) || (currentGameMap->getTile(xPos+1, yPos)->isConcrete() == false)
2318                 || (currentGameMap->getTile(xPos, yPos+1)->isConcrete() == false) || (currentGameMap->getTile(xPos+1, yPos+1)->isConcrete() == false)) ) {
2319 
2320             getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_PLACE_STRUCTURE,pBuilder->getObjectID(), xPos, yPos));
2321             //the user has tried to place and has been successful
2322             soundPlayer->playSound(Sound_PlaceStructure);
2323             currentCursorMode = CursorMode_Normal;
2324             return true;
2325         } else {
2326             //the user has tried to place but clicked on impossible point
2327             currentGame->addToNewsTicker(_("@DUNE.ENG|135#Cannot place slab here."));
2328             soundPlayer->playSound(Sound_InvalidAction);    //can't place noise
2329             return false;
2330         }
2331     } else {
2332         if(currentGameMap->okayToPlaceStructure(xPos, yPos, structuresize.x, structuresize.y, false, pBuilder->getOwner())) {
2333             getCommandManager().addCommand(Command(pLocalPlayer->getPlayerID(), CMD_PLACE_STRUCTURE,pBuilder->getObjectID(), xPos, yPos));
2334             //the user has tried to place and has been successful
2335             soundPlayer->playSound(Sound_PlaceStructure);
2336             currentCursorMode = CursorMode_Normal;
2337             return true;
2338         } else {
2339             //the user has tried to place but clicked on impossible point
2340             currentGame->addToNewsTicker(fmt::sprintf(_("@DUNE.ENG|134#Cannot place %%s here."), resolveItemName(placeItem)));
2341             soundPlayer->playSound(Sound_InvalidAction);    //can't place noise
2342 
2343             // is this building area only blocked by units?
2344             if(currentGameMap->okayToPlaceStructure(xPos, yPos, structuresize.x, structuresize.y, false, pBuilder->getOwner(), true)) {
2345                 // then we try to move all units outside the building area
2346 
2347                 // generate a independent temporal random number generator as we are in input handling code (and outside game logic code)
2348                 Random tempRandomGen(getGameCycleCount());
2349 
2350                 for(int y = yPos; y < yPos + structuresize.y; y++) {
2351                     for(int x = xPos; x < xPos + structuresize.x; x++) {
2352                         Tile* pTile = currentGameMap->getTile(x,y);
2353                         if(pTile->hasANonInfantryGroundObject()) {
2354                             ObjectBase* pObject = pTile->getNonInfantryGroundObject();
2355                             if(pObject->isAUnit() && pObject->getOwner() == pBuilder->getOwner()) {
2356                                 UnitBase* pUnit = dynamic_cast<UnitBase*>(pObject);
2357                                 Coord newDestination = currentGameMap->findDeploySpot(pUnit, Coord(xPos, yPos), tempRandomGen, pUnit->getLocation(), structuresize);
2358                                 pUnit->handleMoveClick(newDestination.x, newDestination.y);
2359                             }
2360                         } else if(pTile->hasInfantry()) {
2361                             for(Uint32 objectID : pTile->getInfantryList()) {
2362                                 InfantryBase* pInfantry = dynamic_cast<InfantryBase*>(getObjectManager().getObject(objectID));
2363                                 if((pInfantry != nullptr) && (pInfantry->getOwner() == pBuilder->getOwner())) {
2364                                     Coord newDestination = currentGameMap->findDeploySpot(pInfantry, Coord(xPos, yPos), tempRandomGen, pInfantry->getLocation(), structuresize);
2365                                     pInfantry->handleMoveClick(newDestination.x, newDestination.y);
2366                                 }
2367                             }
2368                         }
2369                     }
2370                 }
2371             }
2372 
2373             return false;
2374         }
2375     }
2376 }
2377 
2378 
handleSelectedObjectsAttackClick(int xPos,int yPos)2379 bool Game::handleSelectedObjectsAttackClick(int xPos, int yPos) {
2380     UnitBase* pResponder = nullptr;
2381     for(Uint32 objectID : selectedList) {
2382         ObjectBase* pObject = objectManager.getObject(objectID);
2383         House* pOwner = pObject->getOwner();
2384         if(pObject->isAUnit() && (pOwner == pLocalHouse) && pObject->isRespondable()) {
2385             pResponder = static_cast<UnitBase*>(pObject);
2386             pResponder->handleAttackClick(xPos,yPos);
2387         } else if((pObject->getItemID() == Structure_Palace) && ((pOwner->getHouseID() == HOUSE_HARKONNEN) || (pOwner->getHouseID() == HOUSE_SARDAUKAR))) {
2388             Palace* pPalace = static_cast<Palace*>(pObject);
2389             if(pPalace->isSpecialWeaponReady()) {
2390                 pPalace->handleDeathhandClick(xPos, yPos);
2391             }
2392         }
2393     }
2394 
2395     currentCursorMode = CursorMode_Normal;
2396     if(pResponder) {
2397         pResponder->playConfirmSound();
2398         return true;
2399     } else {
2400         return false;
2401     }
2402 }
2403 
handleSelectedObjectsMoveClick(int xPos,int yPos)2404 bool Game::handleSelectedObjectsMoveClick(int xPos, int yPos) {
2405     UnitBase* pResponder = nullptr;
2406 
2407     for(Uint32 objectID : selectedList) {
2408         ObjectBase* pObject = objectManager.getObject(objectID);
2409         if (pObject->isAUnit() && (pObject->getOwner() == pLocalHouse) && pObject->isRespondable()) {
2410             pResponder = static_cast<UnitBase*>(pObject);
2411             pResponder->handleMoveClick(xPos,yPos);
2412         }
2413     }
2414 
2415     currentCursorMode = CursorMode_Normal;
2416     if(pResponder) {
2417         pResponder->playConfirmSound();
2418         return true;
2419     } else {
2420         return false;
2421     }
2422 }
2423 
2424 /**
2425     New method for transporting units quickly using carryalls
2426 **/
handleSelectedObjectsRequestCarryallDropClick(int xPos,int yPos)2427 bool Game::handleSelectedObjectsRequestCarryallDropClick(int xPos, int yPos) {
2428 
2429     UnitBase* pResponder = nullptr;
2430 
2431     /*
2432         If manual carryall mode isn't enabled then turn this off...
2433     */
2434     if(!getGameInitSettings().getGameOptions().manualCarryallDrops) {
2435         currentCursorMode = CursorMode_Normal;
2436         return false;
2437     }
2438 
2439     for(Uint32 objectID : selectedList) {
2440         ObjectBase* pObject = objectManager.getObject(objectID);
2441         if (pObject->isAGroundUnit() && (pObject->getOwner() == pLocalHouse) && pObject->isRespondable()) {
2442             pResponder = static_cast<UnitBase*>(pObject);
2443             pResponder->handleRequestCarryallDropClick(xPos,yPos);
2444         }
2445     }
2446 
2447     currentCursorMode = CursorMode_Normal;
2448     if(pResponder) {
2449         pResponder->playConfirmSound();
2450         return true;
2451     } else {
2452         return false;
2453     }
2454 }
2455 
2456 
2457 
handleSelectedObjectsCaptureClick(int xPos,int yPos)2458 bool Game::handleSelectedObjectsCaptureClick(int xPos, int yPos) {
2459     Tile* pTile = currentGameMap->getTile(xPos, yPos);
2460 
2461     if(pTile == nullptr) {
2462         return false;
2463     }
2464 
2465     StructureBase* pStructure = dynamic_cast<StructureBase*>(pTile->getGroundObject());
2466 
2467     if((pStructure != nullptr) && (pStructure->canBeCaptured()) && (pStructure->getOwner()->getTeam() != pLocalHouse->getTeam())) {
2468         InfantryBase* pResponder = nullptr;
2469 
2470         for(Uint32 objectID : selectedList) {
2471             ObjectBase* pObject = objectManager.getObject(objectID);
2472             if (pObject->isInfantry() && (pObject->getOwner() == pLocalHouse) && pObject->isRespondable()) {
2473                 pResponder = static_cast<InfantryBase*>(pObject);
2474                 pResponder->handleCaptureClick(xPos,yPos);
2475             }
2476         }
2477 
2478         currentCursorMode = CursorMode_Normal;
2479         if(pResponder) {
2480             pResponder->playConfirmSound();
2481             return true;
2482         } else {
2483             return false;
2484         }
2485     }
2486 
2487     return false;
2488 }
2489 
2490 
handleSelectedObjectsActionClick(int xPos,int yPos)2491 bool Game::handleSelectedObjectsActionClick(int xPos, int yPos) {
2492     //let unit handle right click on map or target
2493     ObjectBase  *pResponder = nullptr;
2494     for(Uint32 objectID : selectedList) {
2495         ObjectBase* pObject = objectManager.getObject(objectID);
2496         if(pObject->getOwner() == pLocalHouse && pObject->isRespondable()) {
2497             pObject->handleActionClick(xPos, yPos);
2498 
2499             //if this object obey the command
2500             if((pResponder == nullptr) && pObject->isRespondable())
2501                 pResponder = pObject;
2502         }
2503     }
2504 
2505     if(pResponder) {
2506         pResponder->playConfirmSound();
2507         return true;
2508     } else {
2509         return false;
2510     }
2511 }
2512 
2513 
takeScreenshot() const2514 void Game::takeScreenshot() const {
2515     std::string screenshotFilename;
2516     int i = 1;
2517     do {
2518         screenshotFilename = "Screenshot" + stringify(i) + ".png";
2519         i++;
2520     } while(existsFile(screenshotFilename) == true);
2521 
2522     SDL_Surface* pCurrentScreen = renderReadSurface(renderer);
2523     SavePNG(pCurrentScreen, screenshotFilename.c_str());
2524     currentGame->addToNewsTicker(_("Screenshot saved") + ": '" + screenshotFilename + "'");
2525     SDL_FreeSurface(pCurrentScreen);
2526 }
2527 
2528 
selectNextStructureOfType(const std::set<Uint32> & itemIDs)2529 void Game::selectNextStructureOfType(const std::set<Uint32>& itemIDs) {
2530     bool bSelectNext = true;
2531 
2532     if(selectedList.size() == 1) {
2533         ObjectBase* pObject = getObjectManager().getObject(*selectedList.begin());
2534         if((pObject != nullptr) && (itemIDs.count(pObject->getItemID()) == 1)) {
2535             bSelectNext = false;
2536         }
2537     }
2538 
2539     StructureBase* pStructure2Select = nullptr;
2540 
2541     for(StructureBase* pStructure : structureList) {
2542         if(bSelectNext) {
2543             if( (itemIDs.count(pStructure->getItemID()) == 1) && (pStructure->getOwner() == pLocalHouse) ) {
2544                 pStructure2Select = pStructure;
2545                 break;
2546             }
2547         } else {
2548             if(selectedList.size() == 1 && pStructure->isSelected()) {
2549                 bSelectNext = true;
2550             }
2551         }
2552     }
2553 
2554     if(pStructure2Select == nullptr) {
2555         // start over at the beginning
2556         for(StructureBase* pStructure : structureList) {
2557             if( (itemIDs.count(pStructure->getItemID()) == 1) && (pStructure->getOwner() == pLocalHouse) && !pStructure->isSelected() ) {
2558                 pStructure2Select = pStructure;
2559                 break;
2560             }
2561         }
2562     }
2563 
2564     if(pStructure2Select != nullptr) {
2565         unselectAll(selectedList);
2566         selectedList.clear();
2567 
2568         pStructure2Select->setSelected(true);
2569         selectedList.insert(pStructure2Select->getObjectID());
2570         currentGame->selectionChanged();
2571 
2572         // we center around the newly selected construction yard
2573         screenborder->setNewScreenCenter(pStructure2Select->getLocation()*TILESIZE);
2574     }
2575 }
2576 
getGameSpeed() const2577 int Game::getGameSpeed() const {
2578     if(gameType == GameType::CustomMultiplayer) {
2579         return gameInitSettings.getGameOptions().gameSpeed;
2580     } else {
2581         return settings.gameOptions.gameSpeed;
2582     }
2583 }
2584