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