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 <MapEditor/MapEditor.h>
19 
20 #include <MapEditor/MapGenerator.h>
21 #include <MapEditor/MapMirror.h>
22 
23 #include <FileClasses/GFXManager.h>
24 #include <FileClasses/TextManager.h>
25 #include <FileClasses/INIFile.h>
26 #include <FileClasses/LoadSavePNG.h>
27 
28 #include <structures/Wall.h>
29 
30 #include <misc/FileSystem.h>
31 #include <misc/draw_util.h>
32 #include <misc/format.h>
33 
34 #include <globals.h>
35 #include <mmath.h>
36 #include <sand.h>
37 #include <main.h>
38 #include <Tile.h>
39 
40 #include <config.h>
41 
42 #include <typeinfo>
43 #include <algorithm>
44 
45 extern int currentZoomlevel;
46 
47 // functor for std::find_if
48 class CoordDistance {
49 public:
CoordDistance(Coord centerCoord,int distance)50     CoordDistance(Coord centerCoord, int distance)
51      : centerCoord(centerCoord), distance(distance) {
52     }
53 
operator ()(Coord & coord)54     bool operator()(Coord& coord) {
55         return distanceFrom(centerCoord, coord) <= 5;
56     }
57 
58 private:
59     Coord centerCoord;
60     int distance;
61 };
62 
63 
MapEditor()64 MapEditor::MapEditor() : pInterface(nullptr) {
65     bQuitEditor = false;
66     scrollDownMode = false;
67     scrollLeftMode = false;
68     scrollRightMode = false;
69     scrollUpMode = false;
70     shift = false;
71 
72     bChangedSinceLastSave = false;
73 
74     bLeftMousePressed = false;
75     lastTerrainEditPosX = -1;
76     lastTerrainEditPosY = -1;
77 
78     selectedUnitID = INVALID;
79     selectedStructureID = INVALID;
80     selectedMapItemCoord.invalidate();
81 
82     currentZoomlevel = settings.video.preferredZoomLevel;
83 
84     sideBarPos = calcAlignedDrawingRect(pGFXManager->getUIGraphic(UI_SideBar), HAlign::Right, VAlign::Top);
85     topBarPos = calcAlignedDrawingRect(pGFXManager->getUIGraphic(UI_TopBar), HAlign::Left, VAlign::Top);
86     bottomBarPos = calcAlignedDrawingRect(pGFXManager->getUIGraphic(UI_MapEditor_BottomBar), HAlign::Left, VAlign::Bottom);
87 
88     SDL_Rect gameBoardRect = { 0, topBarPos.h, sideBarPos.x, getRendererHeight() - topBarPos.h - bottomBarPos.h };
89     screenborder = new ScreenBorder(gameBoardRect);
90 
91     setMap(MapData(128,128,Terrain_Sand), MapInfo());
92     setMirrorMode(MirrorModeNone);
93 
94     pInterface = new MapEditorInterface(this);
95 
96     pInterface->onNew();
97 }
98 
~MapEditor()99 MapEditor::~MapEditor() {
100     delete pInterface;
101 
102     delete screenborder;
103     screenborder = nullptr;
104 }
105 
generateMapname() const106 std::string MapEditor::generateMapname() const {
107     int numPlayers = std::count_if( players.begin(),
108                                     players.end(),
109                                     [](const MapEditor::Player& player) {
110                                         return player.bActive;
111                                     });
112 
113     return stringify(numPlayers) + "P - " + stringify(map.getSizeX()) + "x" + stringify(map.getSizeY()) + " - " + _("New Map");
114 }
115 
setMirrorMode(MirrorMode newMirrorMode)116 void MapEditor::setMirrorMode(MirrorMode newMirrorMode) {
117     currentMirrorMode = newMirrorMode;
118 
119     mapMirror = std::shared_ptr<MapMirror>(MapMirror::createMapMirror(currentMirrorMode,map.getSizeX(), map.getSizeY()));
120 }
121 
RunEditor()122 void MapEditor::RunEditor() {
123     while(!bQuitEditor) {
124 
125         int frameStart = SDL_GetTicks();
126 
127         processInput();
128         drawScreen();
129 
130         int frameTime = SDL_GetTicks() - frameStart;
131         if(settings.video.frameLimit == true) {
132             if(frameTime < 32) {
133                 SDL_Delay(32 - frameTime);
134             }
135         }
136     }
137 }
138 
setMap(const MapData & mapdata,const MapInfo & newMapInfo)139 void MapEditor::setMap(const MapData& mapdata, const MapInfo& newMapInfo) {
140     map = mapdata;
141     mapInfo = newMapInfo;
142 
143     screenborder->adjustScreenBorderToMapsize(map.getSizeX(),map.getSizeY());
144 
145     // reset tools
146     selectedUnitID = INVALID;
147     selectedStructureID = INVALID;
148     selectedMapItemCoord.invalidate();
149 
150     if(pInterface != nullptr) {
151         pInterface->deselectAll();
152     }
153 
154     while(!redoOperationStack.empty()) {
155         redoOperationStack.pop();
156     }
157 
158     while(!undoOperationStack.empty()) {
159         undoOperationStack.pop();
160     }
161 
162     // reset other map properties
163     loadedINIFile.reset();
164     lastSaveName = "";
165 
166     spiceBlooms.clear();
167     specialBlooms.clear();
168     spiceFields.clear();
169     choam.clear();
170     reinforcements.clear();
171     teams.clear();
172     structures.clear();
173     units.clear();
174     players.clear();
175 
176     // setup default players
177     if(getMapVersion() < 2) {
178         players.push_back(Player(getHouseNameByNumber(HOUSE_HARKONNEN),HOUSE_HARKONNEN,HOUSE_HARKONNEN,true,false,"Human",25));
179         players.push_back(Player(getHouseNameByNumber(HOUSE_ATREIDES),HOUSE_ATREIDES,HOUSE_ATREIDES,true,false,"CPU",25));
180         players.push_back(Player(getHouseNameByNumber(HOUSE_ORDOS),HOUSE_ORDOS,HOUSE_ORDOS,true,false,"CPU",25));
181         players.push_back(Player(getHouseNameByNumber(HOUSE_FREMEN),HOUSE_FREMEN,HOUSE_FREMEN,false,false,"CPU",25));
182         players.push_back(Player(getHouseNameByNumber(HOUSE_SARDAUKAR),HOUSE_SARDAUKAR,HOUSE_SARDAUKAR,true,false,"CPU",25));
183         players.push_back(Player(getHouseNameByNumber(HOUSE_MERCENARY),HOUSE_MERCENARY,HOUSE_MERCENARY,false,false,"CPU",25));
184     } else {
185         players.push_back(Player(getHouseNameByNumber(HOUSE_HARKONNEN),HOUSE_HARKONNEN,HOUSE_HARKONNEN,true,true,"Team1"));
186         players.push_back(Player(getHouseNameByNumber(HOUSE_ATREIDES),HOUSE_ATREIDES,HOUSE_ATREIDES,true,true,"Team2"));
187         players.push_back(Player(getHouseNameByNumber(HOUSE_ORDOS),HOUSE_ORDOS,HOUSE_ORDOS,true,true,"Team3"));
188         players.push_back(Player(getHouseNameByNumber(HOUSE_FREMEN),HOUSE_FREMEN,HOUSE_FREMEN,false,false,"Team4"));
189         players.push_back(Player(getHouseNameByNumber(HOUSE_SARDAUKAR),HOUSE_SARDAUKAR,HOUSE_SARDAUKAR,true,true,"Team5"));
190         players.push_back(Player(getHouseNameByNumber(HOUSE_MERCENARY),HOUSE_MERCENARY,HOUSE_MERCENARY,false,false,"Team6"));
191     }
192 
193     // setup default choam
194     choam[Unit_Carryall] = 2;
195     choam[Unit_Harvester] = 4;
196     choam[Unit_Launcher] = 5;
197     choam[Unit_MCV] = 2;
198     choam[Unit_Ornithopter] = 5;
199     choam[Unit_Quad] = 5;
200     choam[Unit_SiegeTank] = 6;
201     choam[Unit_Tank] = 6;
202     choam[Unit_Trike] = 5;
203 
204     if(pInterface != nullptr) {
205         pInterface->onNewMap();
206         pInterface->onHouseChanges();
207     }
208 
209     currentEditorMode = EditorMode();
210 
211     bChangedSinceLastSave = true;
212 }
213 
isTileBlocked(int x,int y,bool bSlabIsBlocking,bool bUnitsAreBlocking) const214 bool MapEditor::isTileBlocked(int x, int y, bool bSlabIsBlocking, bool bUnitsAreBlocking) const {
215     for(const Structure& structure : structures) {
216         if(!bSlabIsBlocking && ((structure.itemID == Structure_Slab1) || (structure.itemID == Structure_Slab4)) ) {
217             continue;
218         }
219 
220         Coord structureSize = getStructureSize(structure.itemID);
221         Coord position = structure.position;
222         if((x >= position.x) && (x < position.x+structureSize.x) && (y >= position.y) && (y < position.y+structureSize.y)) {
223             return true;
224         }
225     }
226 
227     if(bUnitsAreBlocking) {
228         for(const Unit& unit : units) {
229             if((x == unit.position.x) && (y == unit.position.y)) {
230                 return true;
231             }
232         }
233     }
234 
235     return false;
236 }
237 
getMirrorStructures(int structureID)238 std::vector<int> MapEditor::getMirrorStructures(int structureID) {
239     std::vector<int> mirrorStructures;
240 
241     MapEditor::Structure* pStructure = getStructure(structureID);
242 
243     if(pStructure == nullptr) {
244         return mirrorStructures;
245     }
246 
247     Coord structureSize = getStructureSize(pStructure->itemID);
248 
249     for(int i=0;i<mapMirror->getSize();i++) {
250         Coord position = mapMirror->getCoord( pStructure->position, i, structureSize);
251 
252         for(const Structure& structure : structures) {
253             if(structure.position == position) {
254                 mirrorStructures.push_back(structure.id);
255                 break;
256             }
257         }
258     }
259 
260     return mirrorStructures;
261 }
262 
getMirrorUnits(int unitID,bool bAddMissingAsInvalid)263 std::vector<int> MapEditor::getMirrorUnits(int unitID, bool bAddMissingAsInvalid) {
264     std::vector<int> mirrorUnits;
265 
266     MapEditor::Unit* pUnit = getUnit(unitID);
267 
268     if(pUnit == nullptr) {
269         return mirrorUnits;
270     }
271 
272     for(int i=0;i<mapMirror->getSize();i++) {
273         Coord position = mapMirror->getCoord( pUnit->position, i);
274 
275         for(const Unit& unit : units) {
276             if(unit.position == position) {
277                 mirrorUnits.push_back(unit.id);
278                 break;
279             }
280         }
281 
282         if(bAddMissingAsInvalid && (mirrorUnits.size() < (unsigned int) (i+1) )) {
283             mirrorUnits.push_back(INVALID);
284         }
285     }
286 
287     return mirrorUnits;
288 }
289 
setEditorMode(const EditorMode & newEditorMode)290 void MapEditor::setEditorMode(const EditorMode& newEditorMode) {
291 
292     if(pInterface != nullptr) {
293         pInterface->deselectObject();
294     }
295 
296     selectedUnitID = INVALID;
297     selectedStructureID = INVALID;
298     selectedMapItemCoord.invalidate();
299 
300     currentEditorMode = newEditorMode;
301 }
302 
startOperation()303 void MapEditor::startOperation() {
304     if(undoOperationStack.empty() || !std::dynamic_pointer_cast<MapEditorStartOperation>( undoOperationStack.top() )) {
305         addUndoOperation(std::shared_ptr<MapEditorOperation>(new MapEditorStartOperation()));
306     }
307 }
308 
undoLastOperation()309 void MapEditor::undoLastOperation() {
310     if(!undoOperationStack.empty()) {
311         redoOperationStack.push(std::shared_ptr<MapEditorOperation>(new MapEditorStartOperation()));
312 
313         while((!undoOperationStack.empty()) && !std::dynamic_pointer_cast<MapEditorStartOperation>( undoOperationStack.top() )) {
314             redoOperationStack.push(undoOperationStack.top()->perform(this));
315             undoOperationStack.pop();
316         }
317 
318         if(!undoOperationStack.empty()) {
319             undoOperationStack.pop();
320         }
321     }
322 }
323 
redoLastOperation()324 void MapEditor::redoLastOperation() {
325     if(!redoOperationStack.empty()) {
326         undoOperationStack.push(std::shared_ptr<MapEditorOperation>(new MapEditorStartOperation()));
327 
328         while((!redoOperationStack.empty()) && !std::dynamic_pointer_cast<MapEditorStartOperation>( redoOperationStack.top() )) {
329             undoOperationStack.push(redoOperationStack.top()->perform(this));
330             redoOperationStack.pop();
331         }
332 
333         if(!redoOperationStack.empty()) {
334             redoOperationStack.pop();
335         }
336     }
337 }
338 
loadMap(const std::string & filepath)339 void MapEditor::loadMap(const std::string& filepath) {
340     // reset tools
341     selectedUnitID = INVALID;
342     selectedStructureID = INVALID;
343 
344     if(pInterface != nullptr) {
345         pInterface->deselectAll();
346     }
347 
348     while(!redoOperationStack.empty()) {
349         redoOperationStack.pop();
350     }
351 
352     while(!undoOperationStack.empty()) {
353         undoOperationStack.pop();
354     }
355 
356     // reset other map properties
357     spiceBlooms.clear();
358     specialBlooms.clear();
359     spiceFields.clear();
360     choam.clear();
361     reinforcements.clear();
362     teams.clear();
363     structures.clear();
364     units.clear();
365     players.clear();
366 
367     players.push_back(Player(getHouseNameByNumber(HOUSE_HARKONNEN),HOUSE_HARKONNEN,HOUSE_HARKONNEN,false,true,"Team1"));
368     players.push_back(Player(getHouseNameByNumber(HOUSE_ATREIDES),HOUSE_ATREIDES,HOUSE_ATREIDES,false,true,"Team2"));
369     players.push_back(Player(getHouseNameByNumber(HOUSE_ORDOS),HOUSE_ORDOS,HOUSE_ORDOS,false,true,"Team3"));
370     players.push_back(Player(getHouseNameByNumber(HOUSE_FREMEN),HOUSE_FREMEN,HOUSE_FREMEN,false,false,"Team4"));
371     players.push_back(Player(getHouseNameByNumber(HOUSE_SARDAUKAR),HOUSE_SARDAUKAR,HOUSE_SARDAUKAR,false,false,"Team5"));
372     players.push_back(Player(getHouseNameByNumber(HOUSE_MERCENARY),HOUSE_MERCENARY,HOUSE_MERCENARY,false,false,"Team6"));
373 
374     // load map
375     loadedINIFile = std::shared_ptr<INIFile>(new INIFile(filepath, false));
376     lastSaveName = filepath;
377 
378     INIMapEditorLoader* pINIMapEditorLoader = new INIMapEditorLoader(this, loadedINIFile);
379     delete pINIMapEditorLoader;
380 
381     // update interface
382     if(pInterface != nullptr) {
383         pInterface->onNewMap();
384         pInterface->onHouseChanges();
385     }
386 
387     currentEditorMode = EditorMode();
388 
389     bChangedSinceLastSave = false;
390 }
391 
saveMap(const std::string & filepath)392 void MapEditor::saveMap(const std::string& filepath) {
393     if(!loadedINIFile) {
394         std::string comment = "Created with Dune Legacy " + std::string(VERSION) + " Map Editor.";
395         loadedINIFile = std::shared_ptr<INIFile>(new INIFile(false, comment));
396     }
397 
398     int version = (mapInfo.mapSeed == INVALID) ? 2 : 1;
399 
400     if(version > 1) {
401         loadedINIFile->setIntValue("BASIC", "Version", version);
402     }
403 
404     if(!mapInfo.license.empty()) {
405         loadedINIFile->setStringValue("BASIC", "License", mapInfo.license);
406     }
407 
408     if(!mapInfo.author.empty()) {
409         loadedINIFile->setStringValue("BASIC", "Author", mapInfo.author);
410     }
411 
412     if((version > 1) && (mapInfo.techLevel > 0)) {
413         loadedINIFile->setIntValue("BASIC", "TechLevel", mapInfo.techLevel);
414     }
415 
416     loadedINIFile->setIntValue("BASIC", "WinFlags", mapInfo.winFlags);
417     loadedINIFile->setIntValue("BASIC", "LoseFlags", mapInfo.loseFlags);
418 
419     loadedINIFile->setStringValue("BASIC", "LosePicture", mapInfo.losePicture, false);
420     loadedINIFile->setStringValue("BASIC", "WinPicture", mapInfo.winPicture, false);
421     loadedINIFile->setStringValue("BASIC", "BriefPicture", mapInfo.briefPicture, false);
422 
423     loadedINIFile->setIntValue("BASIC","TimeOut", mapInfo.timeout);
424 
425     int logicalSizeX;
426     //int logicalSizeY;
427     int logicalOffsetX;
428     int logicalOffsetY;
429 
430     if(version < 2) {
431         logicalSizeX = 64;
432         //logicalSizeY = 64;
433 
434         int mapscale = 0;
435         switch(map.getSizeX()) {
436             case 21: {
437                 mapscale = 2;
438                 logicalOffsetX = logicalOffsetY = 11;
439             } break;
440             case 32: {
441                 mapscale = 1;
442                 logicalOffsetX = logicalOffsetY = 16;
443             } break;
444             case 62:
445             default: {
446                 mapscale = 0;
447                 logicalOffsetX = logicalOffsetY = 1;
448             } break;
449 
450         }
451 
452         loadedINIFile->setIntValue("BASIC", "MapScale", mapscale);
453 
454         int cursorPos = (logicalOffsetY+mapInfo.cursorPos.y) * logicalSizeX + (logicalOffsetX+mapInfo.cursorPos.x);
455         loadedINIFile->setIntValue("BASIC", "CursorPos", cursorPos);
456         int tacticalPos = (logicalOffsetY+mapInfo.tacticalPos.y) * logicalSizeX + (logicalOffsetX+mapInfo.tacticalPos.x);
457         loadedINIFile->setIntValue("BASIC", "TacticalPos", tacticalPos);
458 
459         // field, spice bloom and special bloom
460         std::string strSpiceBloom = "";
461         for(size_t i=0;i<spiceBlooms.size();++i) {
462             if(i>0) {
463                 strSpiceBloom += ",";
464             }
465 
466             int position = (logicalOffsetY+spiceBlooms[i].y) * logicalSizeX + (logicalOffsetX+spiceBlooms[i].x);
467             strSpiceBloom += stringify(position);
468         }
469 
470         if(!strSpiceBloom.empty()) {
471             loadedINIFile->setStringValue("MAP", "Bloom", strSpiceBloom, false);
472         } else {
473             loadedINIFile->removeKey("MAP", "Bloom");
474         }
475 
476 
477         std::string strSpecialBloom = "";
478         for(size_t i=0;i<specialBlooms.size();++i) {
479             if(i>0) {
480                 strSpecialBloom += ",";
481             }
482 
483             int position = (logicalOffsetY+specialBlooms[i].y) * logicalSizeX + (logicalOffsetX+specialBlooms[i].x);
484             strSpecialBloom += stringify(position);
485         }
486 
487         if(!strSpecialBloom.empty()) {
488             loadedINIFile->setStringValue("MAP", "Special", strSpecialBloom, false);
489         } else {
490             loadedINIFile->removeKey("MAP", "Special");
491         }
492 
493 
494         std::string strFieldBloom = "";
495         for(size_t i=0;i<spiceFields.size();++i) {
496             if(i>0) {
497                 strFieldBloom += ",";
498             }
499 
500             int position = (logicalOffsetY+spiceFields[i].y) * logicalSizeX + (logicalOffsetX+spiceFields[i].x);
501             strFieldBloom += stringify(position);
502         }
503 
504         if(!strFieldBloom.empty()) {
505             loadedINIFile->setStringValue("MAP", "Field", strFieldBloom, false);
506         } else {
507             loadedINIFile->removeKey("MAP", "Field");
508         }
509 
510 
511         loadedINIFile->setIntValue("MAP", "Seed", mapInfo.mapSeed);
512     } else {
513         logicalSizeX = map.getSizeX();
514         //logicalSizeY = map.getSizeY();
515         logicalOffsetX = logicalOffsetY = 0;
516 
517         loadedINIFile->clearSection("MAP");
518         loadedINIFile->setIntValue("MAP", "SizeX", map.getSizeX());
519         loadedINIFile->setIntValue("MAP", "SizeY", map.getSizeY());
520 
521         for(int y = 0; y < map.getSizeY(); y++) {
522             std::string rowKey = fmt::sprintf("%.3d", y);
523 
524             std::string row = "";
525             for(int x = 0; x < map.getSizeX(); x++) {
526                 switch(map(x,y)) {
527 
528                     case Terrain_Dunes: {
529                         // Sand dunes
530                         row += '^';
531                     } break;
532 
533                     case Terrain_Spice: {
534                         // Spice
535                         row += '~';
536                     } break;
537 
538                     case Terrain_ThickSpice: {
539                         // Thick spice
540                         row += '+';
541                     } break;
542 
543                     case Terrain_Rock: {
544                         // Rock
545                         row += '%';
546                     } break;
547 
548                     case Terrain_Mountain: {
549                         // Mountain
550                         row += '@';
551                     } break;
552 
553                     case Terrain_SpiceBloom: {
554                         // Spice Bloom
555                         row += 'O';
556                     } break;
557 
558                     case Terrain_SpecialBloom: {
559                         // Special Bloom
560                         row += 'Q';
561                     } break;
562 
563                     case Terrain_Sand:
564                     default: {
565                         // Normal sand
566                         row += '-';
567                     } break;
568                 }
569             }
570 
571             loadedINIFile->setStringValue("MAP", rowKey, row, false);
572         }
573     }
574 
575 
576     for(int i=1;i<=NUM_HOUSES;i++) {
577         loadedINIFile->removeSection("player" + stringify(i));
578     }
579 
580     std::string house2housename[NUM_HOUSES];
581     int currentAnyHouseNumber = 1;
582     int i = 0;
583     for(const Player& player : players) {
584         if(player.bAnyHouse) {
585             house2housename[i] =  "Player" + stringify(currentAnyHouseNumber);
586         } else {
587             house2housename[i] =  player.name;
588         }
589 
590         if(player.bActive) {
591             if(version < 2) {
592                 loadedINIFile->setIntValue(house2housename[i], "Quota", player.quota);
593                 loadedINIFile->setIntValue(house2housename[i], "Credits", player.credits);
594                 loadedINIFile->setStringValue(house2housename[i], "Brain", player.brain, false);
595                 loadedINIFile->setIntValue(house2housename[i], "MaxUnit", player.maxunit);
596             } else {
597                 if(player.quota > 0) {
598                     loadedINIFile->setIntValue(house2housename[i], "Quota", player.quota);
599                 } else {
600                     loadedINIFile->removeKey(house2housename[i], "Quota");
601                 }
602                 loadedINIFile->setIntValue(house2housename[i], "Credits", player.credits);
603                 loadedINIFile->setStringValue(house2housename[i], "Brain", player.brain, false);
604 
605                 if(player.bAnyHouse) {
606                     currentAnyHouseNumber++;
607                 }
608             }
609 
610             if(player.bAnyHouse) {
611                 // remove corresponding house name
612                 loadedINIFile->removeSection(player.name);
613             }
614 
615         } else {
616             // remove corresponding house name
617             loadedINIFile->removeSection(player.name);
618         }
619         i++;
620     }
621 
622     // remove players that are leftovers
623     for(int i=currentAnyHouseNumber;i<NUM_HOUSES;i++) {
624         loadedINIFile->removeSection("Player" + stringify(i));
625     }
626 
627 
628     if(choam.empty()) {
629         loadedINIFile->removeSection("CHOAM");
630     } else {
631         loadedINIFile->clearSection("CHOAM");
632 
633         for(auto& choamEntry : choam) {
634             int itemID = choamEntry.first;
635             int num = choamEntry.second;
636 
637             if(num == 0) {
638                 num = -1;
639             }
640 
641             loadedINIFile->setIntValue("CHOAM", getItemNameByID(itemID), num);
642         }
643     }
644 
645     if(teams.empty()) {
646         loadedINIFile->removeSection("TEAMS");
647     } else {
648         loadedINIFile->clearSection("TEAMS");
649 
650         // we start at 0 for version 1 maps if we have 16 entries to not overflow the table
651         int currentIndex = ((getMapVersion() < 2) && (teams.size() >= 16)) ? 0 : 1;
652         for(const TeamInfo& teamInfo : teams) {
653             std::string value = house2housename[teamInfo.houseID] + "," + getTeamBehaviorNameByID(teamInfo.teamBehavior) + "," + getTeamTypeNameByID(teamInfo.teamType) + "," + stringify(teamInfo.minUnits) + "," + stringify(teamInfo.maxUnits);
654             loadedINIFile->setStringValue("TEAMS", stringify(currentIndex), value, false);
655             currentIndex++;
656         }
657     }
658 
659 
660     loadedINIFile->clearSection("UNITS");
661     for(const Unit& unit : units) {
662         std::string unitKey = fmt::sprintf("ID%.3d", unit.id);
663 
664         int position = (logicalOffsetY+unit.position.y) * logicalSizeX + (logicalOffsetX+unit.position.x);
665 
666         int angle = (int) unit.angle;
667 
668         angle = (((NUM_ANGLES - angle) + 2) % NUM_ANGLES) * 32;
669 
670         std::string unitValue = house2housename[unit.house] + "," + getItemNameByID(unit.itemID) + "," + stringify(unit.health)
671                                 + "," + stringify(position) + "," + stringify(angle) + "," + getAttackModeNameByMode(unit.attackmode);
672 
673         loadedINIFile->setStringValue("UNITS", unitKey, unitValue, false);
674     }
675 
676     loadedINIFile->clearSection("STRUCTURES");
677     for(const Structure& structure : structures) {
678         int position = (logicalOffsetY+structure.position.y) * logicalSizeX + (logicalOffsetX+structure.position.x);
679 
680         if((structure.itemID == Structure_Slab1) || (structure.itemID == Structure_Slab4) || (structure.itemID == Structure_Wall)) {
681             std::string structureKey = fmt::sprintf("GEN%.3d", position);
682 
683             std::string structureValue = house2housename[structure.house] + "," + getItemNameByID(structure.itemID);
684 
685             loadedINIFile->setStringValue("STRUCTURES", structureKey, structureValue, false);
686 
687         } else {
688 
689             std::string structureKey = fmt::sprintf("ID%.3d", structure.id);
690 
691             std::string structureValue = house2housename[structure.house] + "," + getItemNameByID(structure.itemID) + "," + stringify(structure.health) + "," + stringify(position);
692 
693             loadedINIFile->setStringValue("STRUCTURES", structureKey, structureValue, false);
694         }
695     }
696 
697     if(reinforcements.empty()) {
698         loadedINIFile->removeSection("REINFORCEMENTS");
699     } else {
700         loadedINIFile->clearSection("REINFORCEMENTS");
701 
702         // we start at 0 for version 1 maps if we have 16 entries to not overflow the table
703         int currentIndex = ((getMapVersion() < 2) && (reinforcements.size() >= 16)) ? 0 : 1;
704         for(const ReinforcementInfo& reinforcement : reinforcements) {
705             std::string value = house2housename[reinforcement.houseID] + "," + getItemNameByID(reinforcement.unitID) + "," + getDropLocationNameByID(reinforcement.dropLocation) + "," + stringify(reinforcement.droptime);
706             if(reinforcement.bRepeat) {
707                 value += ",+";
708             }
709             loadedINIFile->setStringValue("REINFORCEMENTS", stringify(currentIndex), value, false);
710             currentIndex++;
711         }
712     }
713 
714     loadedINIFile->saveChangesTo(filepath, getMapVersion() < 2);
715 
716     lastSaveName = filepath;
717     bChangedSinceLastSave = false;
718 }
719 
performMapEdit(int xpos,int ypos,bool bRepeated)720 void MapEditor::performMapEdit(int xpos, int ypos, bool bRepeated) {
721     switch(currentEditorMode.mode) {
722         case EditorMode::EditorMode_Terrain: {
723             clearRedoOperations();
724 
725             if(!bRepeated) {
726                 startOperation();
727             }
728 
729             if(getMapVersion() < 2) {
730                 // classic map
731                 if(!bRepeated && map.isInsideMap(xpos, ypos)) {
732                     TERRAINTYPE terrainType = currentEditorMode.terrainType;
733 
734                     switch(terrainType) {
735                         case Terrain_SpiceBloom: {
736                             MapEditorTerrainAddSpiceBloomOperation editOperation(xpos, ypos);
737                             addUndoOperation(editOperation.perform(this));
738                         } break;
739 
740                         case Terrain_SpecialBloom: {
741                             MapEditorTerrainAddSpecialBloomOperation editOperation(xpos, ypos);
742                             addUndoOperation(editOperation.perform(this));
743                         } break;
744 
745                         case Terrain_Spice: {
746                             MapEditorTerrainAddSpiceFieldOperation editOperation(xpos, ypos);
747                             addUndoOperation(editOperation.perform(this));
748                         } break;
749 
750                         default: {
751                         } break;
752                     }
753 
754 
755                 }
756 
757             } else {
758                 for(int i=0;i<mapMirror->getSize();i++) {
759 
760                     Coord position = mapMirror->getCoord( Coord(xpos, ypos), i);
761 
762                     int halfsize = currentEditorMode.pensize/2;
763                     for(int y = position.y - halfsize; y <= position.y + halfsize; y++) {
764                         for(int x = position.x - halfsize; x <= position.x + halfsize; x++) {
765                             if(map.isInsideMap(x, y)) {
766                                 performTerrainChange(x, y, currentEditorMode.terrainType);
767                             }
768                         }
769                     }
770 
771                 }
772             }
773 
774         } break;
775 
776         case EditorMode::EditorMode_Structure: {
777             if(!bRepeated || currentEditorMode.itemID == Structure_Slab1 || currentEditorMode.itemID == Structure_Wall) {
778 
779                 Coord structureSize = getStructureSize(currentEditorMode.itemID);
780 
781                 if(mapMirror->mirroringPossible( Coord(xpos, ypos), structureSize) == false) {
782                     return;
783                 }
784 
785                 // check if all places are free
786                 for(int i=0;i<mapMirror->getSize();i++) {
787                     Coord position = mapMirror->getCoord( Coord(xpos, ypos), i, structureSize);
788 
789                     for(int x = position.x; x < position.x + structureSize.x; x++) {
790                         for(int y = position.y; y < position.y + structureSize.y; y++) {
791                             if(!map.isInsideMap(x,y) || isTileBlocked(x, y, true, (currentEditorMode.itemID != Structure_Slab1) )) {
792                                 return;
793                             }
794                         }
795                     }
796                 }
797 
798                 clearRedoOperations();
799 
800                 if(!bRepeated) {
801                     startOperation();
802                 }
803 
804                 int currentHouse = currentEditorMode.house;
805                 bool bHouseIsActive = players[currentHouse].bActive;
806                 for(int i=0;i<mapMirror->getSize();i++) {
807 
808                     int nextHouse = HOUSE_INVALID;
809                     for(int k = currentHouse; k < currentHouse+NUM_HOUSES;k++) {
810                         if(players[k%NUM_HOUSES].bActive == bHouseIsActive) {
811                             nextHouse = k;
812                             break;
813                         }
814                     }
815 
816                     if(nextHouse != HOUSE_INVALID) {
817                         Coord position = mapMirror->getCoord( Coord(xpos, ypos), i, structureSize);
818 
819                         MapEditorStructurePlaceOperation placeOperation(position, (HOUSETYPE) (nextHouse%NUM_HOUSES), currentEditorMode.itemID, currentEditorMode.health);
820 
821                         addUndoOperation(placeOperation.perform(this));
822 
823                         currentHouse = nextHouse + 1;
824                     }
825                 }
826             }
827         } break;
828 
829         case EditorMode::EditorMode_Unit: {
830             if(!bRepeated) {
831 
832                 // first check if all places are free
833                 for(int i=0;i<mapMirror->getSize();i++) {
834                     Coord position = mapMirror->getCoord( Coord(xpos, ypos), i);
835 
836                     if(!map.isInsideMap(position.x,position.y) || isTileBlocked(position.x, position.y, false, true)) {
837                         return;
838                     }
839                 }
840 
841                 clearRedoOperations();
842 
843                 startOperation();
844 
845 
846                 int currentHouse = currentEditorMode.house;
847                 bool bHouseIsActive = players[currentHouse].bActive;
848                 for(int i=0;i<mapMirror->getSize();i++) {
849 
850                     int nextHouse = HOUSE_INVALID;
851                     for(int k = currentHouse; k < currentHouse+NUM_HOUSES;k++) {
852                         if(players[k%NUM_HOUSES].bActive == bHouseIsActive) {
853                             nextHouse = k;
854                             break;
855                         }
856                     }
857 
858                     if(nextHouse != HOUSE_INVALID) {
859                         Coord position = mapMirror->getCoord( Coord(xpos, ypos), i);
860 
861                         int angle =  mapMirror->getAngle(currentEditorMode.angle, i);
862 
863                         MapEditorUnitPlaceOperation placeOperation(position, (HOUSETYPE) (nextHouse%NUM_HOUSES), currentEditorMode.itemID, currentEditorMode.health, angle, currentEditorMode.attackmode);
864 
865                         addUndoOperation(placeOperation.perform(this));
866                         currentHouse = nextHouse + 1;
867                     }
868                 }
869             }
870         } break;
871 
872         case EditorMode::EditorMode_TacticalPos: {
873             if(!map.isInsideMap(xpos,ypos)) {
874                 return;
875             }
876 
877             clearRedoOperations();
878 
879             startOperation();
880 
881             MapEditorSetTacticalPositionOperation setOperation(xpos,ypos);
882 
883             addUndoOperation(setOperation.perform(this));
884 
885             setEditorMode(EditorMode());
886         } break;
887 
888         default: {
889 
890         } break;
891 
892     }
893 }
894 
performTerrainChange(int x,int y,TERRAINTYPE terrainType)895 void MapEditor::performTerrainChange(int x, int y, TERRAINTYPE terrainType) {
896 
897     MapEditorTerrainEditOperation editOperation(x, y, terrainType);
898     addUndoOperation(editOperation.perform(this));
899 
900     switch(terrainType) {
901         case Terrain_Mountain: {
902             if(map.isInsideMap(x-1, y) && (map(x-1,y) != Terrain_Mountain) && (map(x-1,y) != Terrain_Rock))     performTerrainChange(x-1,y,Terrain_Rock);
903             if(map.isInsideMap(x, y-1) && (map(x,y-1) != Terrain_Mountain) && (map(x,y-1) != Terrain_Rock))     performTerrainChange(x,y-1,Terrain_Rock);
904             if(map.isInsideMap(x+1, y) && (map(x+1,y) != Terrain_Mountain) && (map(x+1,y) != Terrain_Rock))     performTerrainChange(x+1,y,Terrain_Rock);
905             if(map.isInsideMap(x, y+1) && (map(x,y+1) != Terrain_Mountain) && (map(x,y+1) != Terrain_Rock))     performTerrainChange(x,y+1,Terrain_Rock);
906         } break;
907 
908         case Terrain_ThickSpice: {
909             if(map.isInsideMap(x-1, y) && (map(x-1,y) != Terrain_ThickSpice) && (map(x-1,y) != Terrain_Spice))     performTerrainChange(x-1,y,Terrain_Spice);
910             if(map.isInsideMap(x, y-1) && (map(x,y-1) != Terrain_ThickSpice) && (map(x,y-1) != Terrain_Spice))     performTerrainChange(x,y-1,Terrain_Spice);
911             if(map.isInsideMap(x+1, y) && (map(x+1,y) != Terrain_ThickSpice) && (map(x+1,y) != Terrain_Spice))     performTerrainChange(x+1,y,Terrain_Spice);
912             if(map.isInsideMap(x, y+1) && (map(x,y+1) != Terrain_ThickSpice) && (map(x,y+1) != Terrain_Spice))     performTerrainChange(x,y+1,Terrain_Spice);
913         } break;
914 
915         case Terrain_Rock: {
916             if(map.isInsideMap(x-1, y) && (map(x-1,y) == Terrain_ThickSpice))     performTerrainChange(x-1,y,Terrain_Spice);
917             if(map.isInsideMap(x, y-1) && (map(x,y-1) == Terrain_ThickSpice))     performTerrainChange(x,y-1,Terrain_Spice);
918             if(map.isInsideMap(x+1, y) && (map(x+1,y) == Terrain_ThickSpice))     performTerrainChange(x+1,y,Terrain_Spice);
919             if(map.isInsideMap(x, y+1) && (map(x,y+1) == Terrain_ThickSpice))     performTerrainChange(x,y+1,Terrain_Spice);
920         } break;
921 
922         case Terrain_Spice: {
923             if(map.isInsideMap(x-1, y) && (map(x-1,y) == Terrain_Mountain))     performTerrainChange(x-1,y,Terrain_Rock);
924             if(map.isInsideMap(x, y-1) && (map(x,y-1) == Terrain_Mountain))     performTerrainChange(x,y-1,Terrain_Rock);
925             if(map.isInsideMap(x+1, y) && (map(x+1,y) == Terrain_Mountain))     performTerrainChange(x+1,y,Terrain_Rock);
926             if(map.isInsideMap(x, y+1) && (map(x,y+1) == Terrain_Mountain))     performTerrainChange(x,y+1,Terrain_Rock);
927         } break;
928 
929         case Terrain_Sand:
930         case Terrain_Dunes:
931         case Terrain_SpiceBloom:
932         case Terrain_SpecialBloom: {
933             if(map.isInsideMap(x-1, y) && (map(x-1,y) == Terrain_Mountain))     performTerrainChange(x-1,y,Terrain_Rock);
934             if(map.isInsideMap(x, y-1) && (map(x,y-1) == Terrain_Mountain))     performTerrainChange(x,y-1,Terrain_Rock);
935             if(map.isInsideMap(x+1, y) && (map(x+1,y) == Terrain_Mountain))     performTerrainChange(x+1,y,Terrain_Rock);
936             if(map.isInsideMap(x, y+1) && (map(x,y+1) == Terrain_Mountain))     performTerrainChange(x,y+1,Terrain_Rock);
937 
938             if(map.isInsideMap(x-1, y) && (map(x-1,y) == Terrain_ThickSpice))     performTerrainChange(x-1,y,Terrain_Spice);
939             if(map.isInsideMap(x, y-1) && (map(x,y-1) == Terrain_ThickSpice))     performTerrainChange(x,y-1,Terrain_Spice);
940             if(map.isInsideMap(x+1, y) && (map(x+1,y) == Terrain_ThickSpice))     performTerrainChange(x+1,y,Terrain_Spice);
941             if(map.isInsideMap(x, y+1) && (map(x,y+1) == Terrain_ThickSpice))     performTerrainChange(x,y+1,Terrain_Spice);
942         } break;
943 
944         default: {
945         } break;
946     }
947 
948 }
949 
drawScreen()950 void MapEditor::drawScreen() {
951     // clear whole screen
952     SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
953     SDL_RenderClear(renderer);
954 
955     //the actuall map
956     drawMap(screenborder, false);
957 
958     pInterface->draw(Point(0,0));
959     pInterface->drawOverlay(Point(0,0));
960 
961     // Cursor
962     drawCursor();
963 
964     SDL_RenderPresent(renderer);
965 }
966 
processInput()967 void MapEditor::processInput() {
968     SDL_Event event;
969 
970     while(SDL_PollEvent(&event)) {
971 
972         // first of all update mouse
973         if(event.type == SDL_MOUSEMOTION) {
974             SDL_MouseMotionEvent* mouse = &event.motion;
975             drawnMouseX = std::max(0, std::min(mouse->x, settings.video.width-1));
976             drawnMouseY = std::max(0, std::min(mouse->y, settings.video.height-1));
977         }
978 
979         if(pInterface->hasChildWindow()) {
980             pInterface->handleInput(event);
981         } else {
982             switch (event.type) {
983                 case SDL_KEYDOWN:
984                 {
985                     switch(event.key.keysym.sym) {
986 
987                         case SDLK_RETURN: {
988                             if(SDL_GetModState() & KMOD_ALT) {
989                                 toogleFullscreen();
990                             }
991                         } break;
992 
993                         case SDLK_TAB: {
994                             if(SDL_GetModState() & KMOD_ALT) {
995                                 SDL_MinimizeWindow(window);
996                             }
997                         } break;
998 
999                         case SDLK_F1: {
1000                             Coord oldCenterCoord = screenborder->getCurrentCenter();
1001                             currentZoomlevel = 0;
1002                             screenborder->adjustScreenBorderToMapsize(map.getSizeX(), map.getSizeY());
1003                             screenborder->setNewScreenCenter(oldCenterCoord);
1004                         } break;
1005 
1006                         case SDLK_F2: {
1007                             Coord oldCenterCoord = screenborder->getCurrentCenter();
1008                             currentZoomlevel = 1;
1009                             screenborder->adjustScreenBorderToMapsize(map.getSizeX(), map.getSizeY());
1010                             screenborder->setNewScreenCenter(oldCenterCoord);
1011                         } break;
1012 
1013                         case SDLK_F3: {
1014                             Coord oldCenterCoord = screenborder->getCurrentCenter();
1015                             currentZoomlevel = 2;
1016                             screenborder->adjustScreenBorderToMapsize(map.getSizeX(), map.getSizeY());
1017                             screenborder->setNewScreenCenter(oldCenterCoord);
1018                         } break;
1019 
1020                         case SDLK_p: {
1021                             if(SDL_GetModState() & KMOD_CTRL) {
1022                                 saveMapshot();
1023                             }
1024                         } break;
1025 
1026                         case SDLK_PRINTSCREEN:
1027                         case SDLK_SYSREQ: {
1028                             saveMapshot();
1029                         } break;
1030 
1031                         case SDLK_z: {
1032                             if(SDL_GetModState() & KMOD_CTRL) {
1033                                     pInterface->onUndo();
1034                             }
1035                         } break;
1036 
1037                         case SDLK_y: {
1038                             if(SDL_GetModState() & KMOD_CTRL) {
1039                                     pInterface->onRedo();
1040                             }
1041                         } break;
1042 
1043                         default:
1044                             break;
1045                     }
1046 
1047                 } break;
1048 
1049                 case SDL_KEYUP:
1050                 {
1051                     switch(event.key.keysym.sym) {
1052                         case SDLK_ESCAPE: {
1053                             // quiting
1054                             pInterface->onQuit();
1055                         } break;
1056 
1057                         case SDLK_DELETE:
1058                         case SDLK_BACKSPACE: {
1059 
1060                             // check units first
1061                             if(selectedUnitID != INVALID) {
1062                                 clearRedoOperations();
1063                                 startOperation();
1064 
1065                                 std::vector<int> selectedUnits = getMirrorUnits(selectedUnitID);
1066 
1067                                 for(size_t i=0;i<selectedUnits.size();i++) {
1068                                     MapEditorRemoveUnitOperation removeOperation(selectedUnits[i]);
1069                                     addUndoOperation(removeOperation.perform(this));
1070                                 }
1071                                 selectedUnitID = INVALID;
1072 
1073                                 pInterface->deselectAll();
1074                             } else if(selectedStructureID != INVALID) {
1075                                 // We only try deleting structures if we had not yet deleted a unit (e.g. a unit on concrete)
1076                                 clearRedoOperations();
1077                                 startOperation();
1078 
1079                                 std::vector<int> selectedStructures = getMirrorStructures(selectedStructureID);
1080 
1081                                 for(size_t i=0;i<selectedStructures.size();i++) {
1082                                     MapEditorRemoveStructureOperation removeOperation(selectedStructures[i]);
1083                                     addUndoOperation(removeOperation.perform(this));
1084                                 }
1085                                 selectedStructureID = INVALID;
1086 
1087                                 pInterface->deselectAll();
1088                             } else if(selectedMapItemCoord.isValid()) {
1089                                 std::vector<Coord>::iterator iter = std::find(specialBlooms.begin(), specialBlooms.end(), selectedMapItemCoord);
1090 
1091                                 if(iter != specialBlooms.end()) {
1092                                     clearRedoOperations();
1093                                     startOperation();
1094                                     MapEditorTerrainRemoveSpecialBloomOperation removeOperation(iter->x, iter->y);
1095                                     addUndoOperation(removeOperation.perform(this));
1096 
1097                                     selectedMapItemCoord.invalidate();
1098                                 } else {
1099                                     iter = std::find(spiceBlooms.begin(), spiceBlooms.end(), selectedMapItemCoord);
1100 
1101                                     if(iter != spiceBlooms.end()) {
1102                                         clearRedoOperations();
1103                                         startOperation();
1104                                         MapEditorTerrainRemoveSpiceBloomOperation removeOperation(iter->x, iter->y);
1105                                         addUndoOperation(removeOperation.perform(this));
1106 
1107                                         selectedMapItemCoord.invalidate();
1108                                     } else {
1109                                         iter = std::find(spiceFields.begin(), spiceFields.end(), selectedMapItemCoord);
1110 
1111                                         if(iter != spiceBlooms.end()) {
1112                                             clearRedoOperations();
1113                                             startOperation();
1114                                             MapEditorTerrainRemoveSpiceFieldOperation removeOperation(iter->x, iter->y);
1115                                             addUndoOperation(removeOperation.perform(this));
1116 
1117                                             selectedMapItemCoord.invalidate();
1118                                         }
1119                                     }
1120                                 }
1121                             }
1122 
1123                         } break;
1124 
1125                         default:
1126                             break;
1127                     }
1128                 } break;
1129 
1130                 case SDL_MOUSEMOTION:
1131                 {
1132                     pInterface->handleMouseMovement(drawnMouseX,drawnMouseY);
1133 
1134                     if(bLeftMousePressed) {
1135                         if(screenborder->isScreenCoordInsideMap(drawnMouseX, drawnMouseY) == true) {
1136                             //if mouse is not over side bar
1137 
1138                             int xpos = screenborder->screen2MapX(drawnMouseX);
1139                             int ypos = screenborder->screen2MapY(drawnMouseY);
1140 
1141                             if((xpos != lastTerrainEditPosX) || (ypos != lastTerrainEditPosY)) {
1142                                 performMapEdit(xpos, ypos, true);
1143                             }
1144                         }
1145                     }
1146                 } break;
1147 
1148                 case SDL_MOUSEWHEEL: {
1149                     if (event.wheel.y != 0) {
1150                         if(screenborder->isScreenCoordInsideMap(drawnMouseX, drawnMouseY) == true) {
1151                             //if mouse is not over side bar
1152                             int xpos = screenborder->screen2MapX(drawnMouseX);
1153                             int ypos = screenborder->screen2MapY(drawnMouseY);
1154 
1155                             for(const Unit& unit : units) {
1156                                 Coord position = unit.position;
1157                                 if((position.x == xpos) && (position.y == ypos)) {
1158                                     if(event.wheel.y > 0) {
1159                                         pInterface->onUnitRotateLeft(unit.id);
1160                                     } else {
1161                                         pInterface->onUnitRotateRight(unit.id);
1162                                     }
1163                                     break;
1164                                 }
1165                             }
1166                         }
1167 
1168                         pInterface->handleMouseWheel(drawnMouseX,drawnMouseY,(event.wheel.y > 0));
1169                     }
1170                 } break;
1171 
1172                 case SDL_MOUSEBUTTONDOWN:
1173                 {
1174                     SDL_MouseButtonEvent* mouse = &event.button;
1175 
1176                     switch(mouse->button) {
1177                         case SDL_BUTTON_LEFT: {
1178                             if(pInterface->handleMouseLeft(mouse->x, mouse->y, true) == false) {
1179 
1180                                 bLeftMousePressed = true;
1181 
1182                                 if(screenborder->isScreenCoordInsideMap(mouse->x, mouse->y) == true) {
1183                                     //if mouse is not over side bar
1184 
1185                                     int xpos = screenborder->screen2MapX(mouse->x);
1186                                     int ypos = screenborder->screen2MapY(mouse->y);
1187 
1188                                     performMapEdit(xpos, ypos, false);
1189                                 }
1190                             }
1191 
1192                         } break;
1193 
1194                         case SDL_BUTTON_RIGHT: {
1195                             if(pInterface->handleMouseRight(mouse->x, mouse->y, true) == false) {
1196 
1197                                 if(screenborder->isScreenCoordInsideMap(mouse->x, mouse->y) == true) {
1198                                     //if mouse is not over side bar
1199                                     setEditorMode(EditorMode());
1200                                     pInterface->deselectAll();
1201                                 }
1202                             }
1203                         } break;
1204 
1205                     }
1206                 } break;
1207 
1208                 case SDL_MOUSEBUTTONUP:
1209                 {
1210                     SDL_MouseButtonEvent* mouse = &event.button;
1211 
1212                     switch(mouse->button) {
1213                         case SDL_BUTTON_LEFT: {
1214 
1215                             pInterface->handleMouseLeft(mouse->x, mouse->y, false);
1216 
1217                             if(bLeftMousePressed) {
1218 
1219                                 bLeftMousePressed = false;
1220                                 lastTerrainEditPosX = -1;
1221                                 lastTerrainEditPosY = -1;
1222 
1223                                 if(currentEditorMode.mode == EditorMode::EditorMode_Selection) {
1224                                     if(screenborder->isScreenCoordInsideMap(mouse->x, mouse->y) == true) {
1225                                         //if mouse is not over side bar
1226 
1227                                         int xpos = screenborder->screen2MapX(mouse->x);
1228                                         int ypos = screenborder->screen2MapY(mouse->y);
1229 
1230                                         selectedUnitID = INVALID;
1231                                         selectedStructureID = INVALID;
1232                                         selectedMapItemCoord.invalidate();
1233 
1234                                         bool bUnitSelected = false;
1235 
1236                                         for(const Unit& unit : units) {
1237                                             Coord position = unit.position;
1238 
1239                                             if((position.x == xpos) && (position.y == ypos)) {
1240                                                 selectedUnitID = unit.id;
1241                                                 bUnitSelected = true;
1242                                                 pInterface->onObjectSelected();
1243                                                 mapInfo.cursorPos = position;
1244                                                 break;
1245                                             }
1246                                         }
1247 
1248                                         bool bStructureSelected = false;
1249 
1250                                         for(const Structure& structure : structures) {
1251                                             const Coord& position = structure.position;
1252                                             Coord structureSize = getStructureSize(structure.itemID);
1253 
1254                                             if(!bUnitSelected && (xpos >= position.x) && (xpos < position.x+structureSize.x) && (ypos >= position.y) && (ypos < position.y+structureSize.y)) {
1255                                                 selectedStructureID = structure.id;
1256                                                 bStructureSelected = true;
1257                                                 pInterface->onObjectSelected();
1258                                                 mapInfo.cursorPos = position;
1259                                                 break;
1260                                             }
1261                                         }
1262 
1263                                         if(!bUnitSelected && !bStructureSelected) {
1264                                             pInterface->deselectAll();
1265 
1266                                             // find map items (spice bloom, special bloom or spice field)
1267                                             if( (std::find(spiceBlooms.begin(), spiceBlooms.end(), Coord(xpos,ypos)) != spiceBlooms.end())
1268                                                 || (std::find(specialBlooms.begin(), specialBlooms.end(), Coord(xpos,ypos)) != specialBlooms.end())
1269                                                 || (std::find(spiceFields.begin(), spiceFields.end(), Coord(xpos,ypos)) != spiceFields.end())) {
1270                                                 selectedMapItemCoord = Coord(xpos, ypos);
1271                                             }
1272                                         }
1273 
1274 
1275                                     }
1276                                 }
1277                             }
1278                         } break;
1279 
1280                         case SDL_BUTTON_RIGHT: {
1281                             pInterface->handleMouseRight(mouse->x, mouse->y, false);
1282                         } break;
1283                     }
1284                 } break;
1285 
1286                 case SDL_QUIT:
1287                 {
1288                     bQuitEditor = true;
1289                 } break;
1290             }
1291         }
1292     }
1293 
1294     if((pInterface->hasChildWindow() == false) && (SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_FOCUS)) {
1295         const Uint8 *keystate = SDL_GetKeyboardState(nullptr);
1296         scrollDownMode =  (drawnMouseY >= getRendererHeight()-1-SCROLLBORDER) || keystate[SDL_SCANCODE_DOWN];
1297         scrollLeftMode = (drawnMouseX <= SCROLLBORDER) || keystate[SDL_SCANCODE_LEFT];
1298         scrollRightMode = (drawnMouseX >= getRendererWidth()-1-SCROLLBORDER) || keystate[SDL_SCANCODE_RIGHT];
1299         scrollUpMode = (drawnMouseY <= SCROLLBORDER) || keystate[SDL_SCANCODE_UP];
1300 
1301         if(scrollLeftMode && scrollRightMode) {
1302             // do nothing
1303         } else if(scrollLeftMode) {
1304             scrollLeftMode = screenborder->scrollLeft();
1305         } else if(scrollRightMode) {
1306             scrollRightMode = screenborder->scrollRight();
1307         }
1308 
1309         if(scrollDownMode && scrollUpMode) {
1310             // do nothing
1311         } else if(scrollDownMode) {
1312             scrollDownMode = screenborder->scrollDown();
1313         } else if(scrollUpMode) {
1314             scrollUpMode = screenborder->scrollUp();
1315         }
1316     } else {
1317         scrollDownMode = false;
1318         scrollLeftMode = false;
1319         scrollRightMode = false;
1320         scrollUpMode = false;
1321     }
1322 }
1323 
drawCursor()1324 void MapEditor::drawCursor() {
1325 
1326     if(!(SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_FOCUS)) {
1327         return;
1328     }
1329 
1330     SDL_Texture* pCursor = nullptr;
1331     SDL_Rect dest = { 0, 0, 0, 0};
1332     if(scrollLeftMode || scrollRightMode || scrollUpMode || scrollDownMode) {
1333         if(scrollLeftMode && !scrollRightMode) {
1334             pCursor = pGFXManager->getUIGraphic(UI_CursorLeft);
1335             dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY-5, HAlign::Left, VAlign::Top);
1336         } else if(scrollRightMode && !scrollLeftMode) {
1337             pCursor = pGFXManager->getUIGraphic(UI_CursorRight);
1338             dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY-5, HAlign::Center, VAlign::Top);
1339         }
1340 
1341         if(pCursor == nullptr) {
1342             if(scrollUpMode && !scrollDownMode) {
1343                 pCursor = pGFXManager->getUIGraphic(UI_CursorUp);
1344                 dest = calcDrawingRect(pCursor, drawnMouseX-5, drawnMouseY, HAlign::Left, VAlign::Top);
1345             } else if(scrollDownMode && !scrollUpMode) {
1346                 pCursor = pGFXManager->getUIGraphic(UI_CursorDown);
1347                 dest = calcDrawingRect(pCursor, drawnMouseX-5, drawnMouseY, HAlign::Left, VAlign::Center);
1348             } else {
1349                 pCursor = pGFXManager->getUIGraphic(UI_CursorNormal);
1350                 dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY, HAlign::Left, VAlign::Top);
1351             }
1352         }
1353     } else {
1354         pCursor = pGFXManager->getUIGraphic(UI_CursorNormal);
1355         dest = calcDrawingRect(pCursor, drawnMouseX, drawnMouseY, HAlign::Left, VAlign::Top);
1356 
1357         if((drawnMouseX < sideBarPos.x) && (drawnMouseY > topBarPos.h) && (currentMirrorMode != MirrorModeNone) && (pInterface->hasChildWindow() == false)) {
1358 
1359             SDL_Texture* pMirrorIcon = nullptr;
1360             switch(currentMirrorMode) {
1361                 case MirrorModeHorizontal:  pMirrorIcon = pGFXManager->getUIGraphic(UI_MapEditor_MirrorHorizontalIcon);  break;
1362                 case MirrorModeVertical:    pMirrorIcon = pGFXManager->getUIGraphic(UI_MapEditor_MirrorVerticalIcon);    break;
1363                 case MirrorModeBoth:        pMirrorIcon = pGFXManager->getUIGraphic(UI_MapEditor_MirrorBothIcon);        break;
1364                 case MirrorModePoint:       pMirrorIcon = pGFXManager->getUIGraphic(UI_MapEditor_MirrorPointIcon);       break;
1365                 default:                    pMirrorIcon = pGFXManager->getUIGraphic(UI_MapEditor_MirrorNoneIcon);       break;
1366             }
1367 
1368             SDL_Rect dest2 = calcDrawingRect(pMirrorIcon, drawnMouseX + 5, drawnMouseY + 5);
1369             SDL_RenderCopy(renderer, pMirrorIcon, nullptr, &dest2);
1370         }
1371     }
1372 
1373     SDL_RenderCopy(renderer, pCursor, nullptr, &dest);
1374 }
1375 
getTerrain(int x,int y)1376 TERRAINTYPE MapEditor::getTerrain(int x, int y) {
1377     TERRAINTYPE terrainType = map(x,y);
1378 
1379     if(map(x,y) == Terrain_Sand) {
1380         if(std::find(spiceFields.begin(), spiceFields.end(), Coord(x,y)) != spiceFields.end()) {
1381             terrainType = Terrain_ThickSpice;
1382         } else if(std::find_if(spiceFields.begin(), spiceFields.end(), CoordDistance(Coord(x,y),5)) != spiceFields.end()) {
1383             terrainType = Terrain_Spice;
1384         }
1385     }
1386 
1387     // check for classic map items (spice blooms, special blooms)
1388     if(std::find(spiceBlooms.begin(), spiceBlooms.end(), Coord(x,y)) != spiceBlooms.end()) {
1389         terrainType = Terrain_SpiceBloom;
1390     }
1391 
1392     if(std::find(specialBlooms.begin(), specialBlooms.end(), Coord(x,y)) != specialBlooms.end()) {
1393         terrainType = Terrain_SpecialBloom;
1394     }
1395 
1396     return terrainType;
1397 }
1398 
drawMap(ScreenBorder * pScreenborder,bool bCompleteMap)1399 void MapEditor::drawMap(ScreenBorder* pScreenborder, bool bCompleteMap) {
1400     int zoomedTilesize = world2zoomedWorld(TILESIZE);
1401 
1402     Coord TopLeftTile = pScreenborder->getTopLeftTile();
1403     Coord BottomRightTile = pScreenborder->getBottomRightTile();
1404 
1405     // extend the view a little bit to avoid graphical glitches
1406     TopLeftTile.x = std::max(0, TopLeftTile.x - 1);
1407     TopLeftTile.y = std::max(0, TopLeftTile.y - 1);
1408     BottomRightTile.x = std::min(map.getSizeX()-1, BottomRightTile.x + 1);
1409     BottomRightTile.y = std::min(map.getSizeY()-1, BottomRightTile.y + 1);
1410 
1411     // Load Terrain Surface
1412     SDL_Texture* TerrainSprite = pGFXManager->getObjPic(ObjPic_Terrain)[currentZoomlevel];
1413 
1414     /* draw ground */
1415     for(int y = TopLeftTile.y; y <= BottomRightTile.y; y++) {
1416         for(int x = TopLeftTile.x; x <= BottomRightTile.x; x++) {
1417 
1418             int tile;
1419 
1420             switch(getTerrain(x,y)) {
1421                 case Terrain_Slab: {
1422                     tile = Tile::TerrainTile_Slab;
1423                 } break;
1424 
1425                 case Terrain_Sand: {
1426                     tile = Tile::TerrainTile_Sand;
1427                 } break;
1428 
1429                 case Terrain_Rock: {
1430                     //determine which surounding tiles are rock
1431                     bool up = (y-1 < 0) || (getTerrain(x, y-1) == Terrain_Rock) || (getTerrain(x, y-1) == Terrain_Slab) || (getTerrain(x, y-1) == Terrain_Mountain);
1432                     bool right = (x+1 >= map.getSizeX()) || (getTerrain(x+1, y) == Terrain_Rock) || (getTerrain(x+1, y) == Terrain_Slab) || (getTerrain(x+1, y) == Terrain_Mountain);
1433                     bool down = (y+1 >= map.getSizeY()) || (getTerrain(x, y+1) == Terrain_Rock) || (getTerrain(x, y+1) == Terrain_Slab) || (getTerrain(x, y+1) == Terrain_Mountain);
1434                     bool left = (x-1 < 0) || (getTerrain(x-1, y) == Terrain_Rock) || (getTerrain(x-1, y) == Terrain_Slab) || (getTerrain(x-1, y) == Terrain_Mountain);
1435 
1436                     tile = Tile::TerrainTile_Rock + (up | (right << 1) | (down << 2) | (left << 3));
1437                 } break;
1438 
1439                 case Terrain_Dunes: {
1440                     //determine which surounding tiles are dunes
1441                     bool up = (y-1 < 0) || (getTerrain(x, y-1) == Terrain_Dunes);
1442                     bool right = (x+1 >= map.getSizeX()) || (getTerrain(x+1, y) == Terrain_Dunes);
1443                     bool down = (y+1 >= map.getSizeY()) || (getTerrain(x, y+1) == Terrain_Dunes);
1444                     bool left = (x-1 < 0) || (getTerrain(x-1, y) == Terrain_Dunes);
1445 
1446                     tile = Tile::TerrainTile_Dunes + (up | (right << 1) | (down << 2) | (left << 3));
1447                 } break;
1448 
1449                 case Terrain_Mountain: {
1450                     //determine which surounding tiles are mountains
1451                     bool up = (y-1 < 0) || (getTerrain(x, y-1) == Terrain_Mountain);
1452                     bool right = (x+1 >= map.getSizeX()) || (getTerrain(x+1, y) == Terrain_Mountain);
1453                     bool down = (y+1 >= map.getSizeY()) || (getTerrain(x, y+1) == Terrain_Mountain);
1454                     bool left = (x-1 < 0) || (getTerrain(x-1, y) == Terrain_Mountain);
1455 
1456                     tile = Tile::TerrainTile_Mountain + (up | (right << 1) | (down << 2) | (left << 3));
1457                 } break;
1458 
1459                 case Terrain_Spice: {
1460                     //determine which surounding tiles are spice
1461                     bool up = (y-1 < 0) || (getTerrain(x, y-1) == Terrain_Spice) || (getTerrain(x, y-1) == Terrain_ThickSpice);
1462                     bool right = (x+1 >= map.getSizeX()) || (getTerrain(x+1, y) == Terrain_Spice) || (getTerrain(x+1, y) == Terrain_ThickSpice);
1463                     bool down = (y+1 >= map.getSizeY()) || (getTerrain(x, y+1) == Terrain_Spice) || (getTerrain(x, y+1) == Terrain_ThickSpice);
1464                     bool left = (x-1 < 0) || (getTerrain(x-1, y) == Terrain_Spice) || (getTerrain(x-1, y) == Terrain_ThickSpice);
1465 
1466                     tile = Tile::TerrainTile_Spice + (up | (right << 1) | (down << 2) | (left << 3));
1467                 } break;
1468 
1469                 case Terrain_ThickSpice: {
1470                     //determine which surounding tiles are thick spice
1471                     bool up = (y-1 < 0) || (getTerrain(x, y-1) == Terrain_ThickSpice);
1472                     bool right = (x+1 >= map.getSizeX()) || (getTerrain(x+1, y) == Terrain_ThickSpice);
1473                     bool down = (y+1 >= map.getSizeY()) || (getTerrain(x, y+1) == Terrain_ThickSpice);
1474                     bool left = (x-1 < 0) || (getTerrain(x-1, y) == Terrain_ThickSpice);
1475 
1476                     tile = Tile::TerrainTile_ThickSpice + (up | (right << 1) | (down << 2) | (left << 3));
1477                 } break;
1478 
1479                 case Terrain_SpiceBloom: {
1480                     tile = Tile::TerrainTile_SpiceBloom;
1481                 } break;
1482 
1483                 case Terrain_SpecialBloom: {
1484                     tile = Tile::TerrainTile_SpecialBloom;
1485                 } break;
1486 
1487                 default: {
1488                     THROW(std::runtime_error, "MapEditor::DrawMap(): Invalid terrain type");
1489                 } break;
1490             }
1491 
1492             //draw map[x][y]
1493             SDL_Rect source = { (tile % NUM_TERRAIN_TILES_X)*zoomedTilesize, (tile / NUM_TERRAIN_TILES_X)*zoomedTilesize,
1494                                 zoomedTilesize, zoomedTilesize };
1495             SDL_Rect drawLocation = {   pScreenborder->world2screenX(x*TILESIZE), pScreenborder->world2screenY(y*TILESIZE),
1496                                         zoomedTilesize, zoomedTilesize };
1497             SDL_RenderCopy(renderer, TerrainSprite, &source, &drawLocation);
1498         }
1499     }
1500 
1501 
1502     std::vector<int> selectedStructures = getMirrorStructures(selectedStructureID);
1503 
1504     for(const Structure& structure : structures) {
1505 
1506         Coord position = structure.position;
1507 
1508         SDL_Rect selectionDest;
1509         if(structure.itemID == Structure_Slab1) {
1510             // Load Terrain sprite
1511             SDL_Texture* TerrainSprite = pGFXManager->getObjPic(ObjPic_Terrain)[currentZoomlevel];
1512 
1513             SDL_Rect source = { Tile::TerrainTile_Slab * zoomedTilesize, 0, zoomedTilesize, zoomedTilesize };
1514             SDL_Rect dest = { pScreenborder->world2screenX(position.x*TILESIZE), pScreenborder->world2screenY(position.y*TILESIZE), zoomedTilesize, zoomedTilesize };
1515 
1516             SDL_RenderCopy(renderer, TerrainSprite, &source, &dest);
1517 
1518             selectionDest = dest;
1519         } else if(structure.itemID == Structure_Slab4) {
1520             // Load Terrain Surface
1521             SDL_Texture* TerrainSprite = pGFXManager->getObjPic(ObjPic_Terrain)[currentZoomlevel];
1522 
1523             for(int y = position.y; y < position.y+2; y++) {
1524                 for(int x = position.x; x < position.x+2; x++) {
1525                     SDL_Rect source = { Tile::TerrainTile_Slab * zoomedTilesize, 0, zoomedTilesize, zoomedTilesize };
1526                     SDL_Rect dest = { pScreenborder->world2screenX(x*TILESIZE), pScreenborder->world2screenY(y*TILESIZE), zoomedTilesize, zoomedTilesize };
1527 
1528                     SDL_RenderCopy(renderer, TerrainSprite, &source, &dest);
1529                 }
1530             }
1531 
1532             selectionDest.x = pScreenborder->world2screenX(position.x*TILESIZE);
1533             selectionDest.y = pScreenborder->world2screenY(position.y*TILESIZE);
1534             selectionDest.w = world2zoomedWorld(2*TILESIZE);
1535             selectionDest.h = world2zoomedWorld(2*TILESIZE);
1536         } else if(structure.itemID == Structure_Wall) {
1537             bool left = false;
1538             bool down = false;
1539             bool right = false;
1540             bool up = false;
1541             for(const Structure& structure : structures) {
1542                 if(structure.itemID == Structure_Wall) {
1543                     if((structure.position.x == position.x - 1) && (structure.position.y == position.y))  left = true;
1544                     if((structure.position.x == position.x) && (structure.position.y == position.y + 1))  down = true;
1545                     if((structure.position.x == position.x + 1) && (structure.position.y == position.y))  right = true;
1546                     if((structure.position.x == position.x) && (structure.position.y == position.y - 1))  up = true;
1547                 }
1548             }
1549 
1550             int maketile = 0;
1551             if((left == true) && (right == true) && (up == true) && (down == true)) {
1552                 maketile = Wall::Wall_Full; //solid wall
1553             } else if((left == false) && (right == true) && (up == true) && (down == true)) {
1554                 maketile = Wall::Wall_UpDownRight; //missing left edge
1555             } else if((left == true) && (right == false)&& (up == true) && (down == true)) {
1556                 maketile = Wall::Wall_UpDownLeft; //missing right edge
1557             } else if((left == true) && (right == true) && (up == false) && (down == true)) {
1558                 maketile = Wall::Wall_DownLeftRight; //missing top edge
1559             } else if((left == true) && (right == true) && (up == true) && (down == false)) {
1560                 maketile = Wall::Wall_UpLeftRight; //missing bottom edge
1561             } else if((left == false) && (right == true) && (up == false) && (down == true)) {
1562                 maketile = Wall::Wall_DownRight; //missing top left edge
1563             } else if((left == true) && (right == false) && (up == true) && (down == false)) {
1564                 maketile = Wall::Wall_UpLeft; //missing bottom right edge
1565             } else if((left == true) && (right == false) && (up == false) && (down == true)) {
1566                 maketile = Wall::Wall_DownLeft; //missing top right edge
1567             } else if((left == false) && (right == true) && (up == true) && (down == false)) {
1568                 maketile = Wall::Wall_UpRight; //missing bottom left edge
1569             } else if((left == true) && (right == false) && (up == false) && (down == false)) {
1570                 maketile = Wall::Wall_LeftRight; //missing above, right and below
1571             } else if((left == false) && (right == true) && (up == false) && (down == false)) {
1572                 maketile = Wall::Wall_LeftRight; //missing above, left and below
1573             } else if((left == false) && (right == false) && (up == true) && (down == false)) {
1574                 maketile = Wall::Wall_UpDown; //only up
1575             } else if((left == false) && (right == false) && (up == false) && (down == true)) {
1576                 maketile = Wall::Wall_UpDown; //only down
1577             } else if((left == true) && (right == true) && (up == false) && (down == false)) {
1578                 maketile = Wall::Wall_LeftRight; //missing above and below
1579             } else if((left == false) && (right == false) && (up == true) && (down == true)) {
1580                 maketile = Wall::Wall_UpDown; //missing left and right
1581             } else if((left == false) && (right == false) && (up == false) && (down == false)) {
1582                 maketile = Wall::Wall_Standalone; //missing left and right
1583             }
1584 
1585             // Load Wall texture
1586             SDL_Texture* WallSprite = pGFXManager->getObjPic(ObjPic_Wall)[currentZoomlevel];
1587 
1588             SDL_Rect source = { maketile * zoomedTilesize, 0, zoomedTilesize, zoomedTilesize };
1589             SDL_Rect dest = { pScreenborder->world2screenX(position.x*TILESIZE), pScreenborder->world2screenY(position.y*TILESIZE), zoomedTilesize, zoomedTilesize };
1590 
1591             SDL_RenderCopy(renderer, WallSprite, &source, &dest);
1592 
1593             selectionDest = dest;
1594         } else {
1595 
1596             int objectPic = 0;
1597             switch(structure.itemID) {
1598                 case Structure_Barracks:            objectPic = ObjPic_Barracks;            break;
1599                 case Structure_ConstructionYard:    objectPic = ObjPic_ConstructionYard;    break;
1600                 case Structure_GunTurret:           objectPic = ObjPic_GunTurret;           break;
1601                 case Structure_HeavyFactory:        objectPic = ObjPic_HeavyFactory;        break;
1602                 case Structure_HighTechFactory:     objectPic = ObjPic_HighTechFactory;     break;
1603                 case Structure_IX:                  objectPic = ObjPic_IX;                  break;
1604                 case Structure_LightFactory:        objectPic = ObjPic_LightFactory;        break;
1605                 case Structure_Palace:              objectPic = ObjPic_Palace;              break;
1606                 case Structure_Radar:               objectPic = ObjPic_Radar;               break;
1607                 case Structure_Refinery:            objectPic = ObjPic_Refinery;            break;
1608                 case Structure_RepairYard:          objectPic = ObjPic_RepairYard;          break;
1609                 case Structure_RocketTurret:        objectPic = ObjPic_RocketTurret;        break;
1610                 case Structure_Silo:                objectPic = ObjPic_Silo;                break;
1611                 case Structure_StarPort:            objectPic = ObjPic_Starport;            break;
1612                 case Structure_Wall:                objectPic = ObjPic_Wall;                break;
1613                 case Structure_WindTrap:            objectPic = ObjPic_Windtrap;            break;
1614                 case Structure_WOR:                 objectPic = ObjPic_WOR;                 break;
1615                 default:                            objectPic = 0;                          break;
1616             }
1617 
1618             SDL_Texture* ObjectSprite = pGFXManager->getObjPic(objectPic, structure.house)[currentZoomlevel];
1619 
1620             Coord frameSize = world2zoomedWorld(getStructureSize(structure.itemID)*TILESIZE);
1621 
1622             SDL_Rect source = { frameSize.x*(structure.itemID == Structure_WindTrap ? 9 : 2), 0, frameSize.x, frameSize.y };
1623             SDL_Rect dest = { pScreenborder->world2screenX(position.x*TILESIZE), pScreenborder->world2screenY(position.y*TILESIZE), frameSize.x, frameSize.y };
1624 
1625             SDL_RenderCopy(renderer, ObjectSprite, &source, &dest);
1626 
1627             selectionDest = dest;
1628         }
1629 
1630         // draw selection frame
1631         if(!bCompleteMap && (std::find(selectedStructures.begin(), selectedStructures.end(), structure.id) != selectedStructures.end()) ) {
1632             //now draw the selection box thing, with parts at all corners of structure
1633 
1634             // top left bit
1635             for(int i=0;i<=currentZoomlevel;i++) {
1636                 renderDrawHLine(renderer, selectionDest.x+i, selectionDest.y+i, selectionDest.x+(currentZoomlevel+1)*3, COLOR_WHITE);
1637                 renderDrawVLine(renderer, selectionDest.x+i, selectionDest.y+i, selectionDest.y+(currentZoomlevel+1)*3, COLOR_WHITE);
1638             }
1639 
1640             // top right bit
1641             for(int i=0;i<=currentZoomlevel;i++) {
1642                 renderDrawHLine(renderer, selectionDest.x + selectionDest.w-1 - i, selectionDest.y+i, selectionDest.x + selectionDest.w-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
1643                 renderDrawVLine(renderer, selectionDest.x + selectionDest.w-1 - i, selectionDest.y+i, selectionDest.y+(currentZoomlevel+1)*3, COLOR_WHITE);
1644             }
1645 
1646             // bottom left bit
1647             for(int i=0;i<=currentZoomlevel;i++) {
1648                 renderDrawHLine(renderer, selectionDest.x+i, selectionDest.y + selectionDest.h-1 - i, selectionDest.x+(currentZoomlevel+1)*3, COLOR_WHITE);
1649                 renderDrawVLine(renderer, selectionDest.x+i, selectionDest.y + selectionDest.h-1 - i, selectionDest.y + selectionDest.h-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
1650             }
1651 
1652             // bottom right bit
1653             for(int i=0;i<=currentZoomlevel;i++) {
1654                 renderDrawHLine(renderer, selectionDest.x + selectionDest.w-1 - i, selectionDest.y + selectionDest.h-1 - i, selectionDest.x + selectionDest.w-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
1655                 renderDrawVLine(renderer, selectionDest.x + selectionDest.w-1 - i, selectionDest.y + selectionDest.h-1 - i, selectionDest.y + selectionDest.h-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
1656             }
1657         }
1658 
1659     }
1660 
1661     for(const Unit& unit : units) {
1662 
1663         const Coord& position = unit.position;
1664 
1665         const Coord tankTurretOffset[] =    {   Coord(0, 0),
1666                                                 Coord(0, 0),
1667                                                 Coord(0, 0),
1668                                                 Coord(0, 0),
1669                                                 Coord(0, 0),
1670                                                 Coord(0, 0),
1671                                                 Coord(0, 0),
1672                                                 Coord(0, 0)
1673                                             };
1674 
1675         const Coord siegeTankTurretOffset[] =   {   Coord(8, -12),
1676                                                     Coord(0, -20),
1677                                                     Coord(0, -20),
1678                                                     Coord(-4, -20),
1679                                                     Coord(-8, -12),
1680                                                     Coord(-8, -4),
1681                                                     Coord(-4, -12),
1682                                                     Coord(8, -4)
1683                                             };
1684 
1685         const Coord sonicTankTurretOffset[] =   {   Coord(0, -8),
1686                                                     Coord(0, -8),
1687                                                     Coord(0, -8),
1688                                                     Coord(0, -8),
1689                                                     Coord(0, -8),
1690                                                     Coord(0, -8),
1691                                                     Coord(0, -8),
1692                                                     Coord(0, -8)
1693                                                 };
1694 
1695         const Coord launcherTurretOffset[] =    {   Coord(0, -12),
1696                                                     Coord(0, -8),
1697                                                     Coord(0, -8),
1698                                                     Coord(0, -8),
1699                                                     Coord(0, -12),
1700                                                     Coord(0, -8),
1701                                                     Coord(0, -8),
1702                                                     Coord(0, -8)
1703                                                 };
1704 
1705         const Coord devastatorTurretOffset[] =  {
1706                                                     Coord(8, -16),
1707                                                     Coord(-4, -12),
1708                                                     Coord(0, -16),
1709                                                     Coord(4, -12),
1710                                                     Coord(-8, -16),
1711                                                     Coord(0, -12),
1712                                                     Coord(-4, -12),
1713                                                     Coord(0, -12)
1714                                                 };
1715 
1716 
1717 
1718         int objectPicBase = 0;
1719         int framesX = NUM_ANGLES;
1720         int framesY = 1;
1721         int objectPicGun = -1;
1722         const Coord* gunOffset = nullptr;
1723 
1724         switch(unit.itemID) {
1725             case Unit_Carryall:         objectPicBase = ObjPic_Carryall;        framesY = 2;                                                                    break;
1726             case Unit_Devastator:       objectPicBase = ObjPic_Devastator_Base; objectPicGun = ObjPic_Devastator_Gun;   gunOffset = devastatorTurretOffset;     break;
1727             case Unit_Deviator:         objectPicBase = ObjPic_Tank_Base;       objectPicGun = ObjPic_Launcher_Gun;     gunOffset = launcherTurretOffset;       break;
1728             case Unit_Frigate:          objectPicBase = ObjPic_Frigate;                                                                                         break;
1729             case Unit_Harvester:        objectPicBase = ObjPic_Harvester;                                                                                       break;
1730             case Unit_Soldier:          objectPicBase = ObjPic_Soldier;         framesX = 4;    framesY = 3;                                                    break;
1731             case Unit_Launcher:         objectPicBase = ObjPic_Tank_Base;       objectPicGun = ObjPic_Launcher_Gun;     gunOffset = launcherTurretOffset;       break;
1732             case Unit_MCV:              objectPicBase = ObjPic_MCV;                                                                                             break;
1733             case Unit_Ornithopter:      objectPicBase = ObjPic_Ornithopter;     framesY = 3;                                                                    break;
1734             case Unit_Quad:             objectPicBase = ObjPic_Quad;                                                                                            break;
1735             case Unit_Saboteur:         objectPicBase = ObjPic_Saboteur;        framesX = 4;    framesY = 3;                                                    break;
1736             case Unit_Sandworm:         objectPicBase = ObjPic_Sandworm;        framesX = 1;    framesY = 9;                                                    break;
1737             case Unit_SiegeTank:        objectPicBase = ObjPic_Siegetank_Base;  objectPicGun = ObjPic_Siegetank_Gun;    gunOffset = siegeTankTurretOffset;      break;
1738             case Unit_SonicTank:        objectPicBase = ObjPic_Tank_Base;       objectPicGun = ObjPic_Sonictank_Gun;    gunOffset = sonicTankTurretOffset;      break;
1739             case Unit_Tank:             objectPicBase = ObjPic_Tank_Base;       objectPicGun = ObjPic_Tank_Gun;         gunOffset = tankTurretOffset;           break;
1740             case Unit_Trike:            objectPicBase = ObjPic_Trike;                                                                                           break;
1741             case Unit_RaiderTrike:      objectPicBase = ObjPic_Trike;                                                                                           break;
1742             case Unit_Trooper:          objectPicBase = ObjPic_Trooper;         framesX = 4;    framesY = 3;                                                    break;
1743             case Unit_Special:          objectPicBase = ObjPic_Devastator_Base; objectPicGun = ObjPic_Devastator_Gun;   gunOffset = devastatorTurretOffset;     break;
1744             case Unit_Infantry:         objectPicBase = ObjPic_Infantry;         framesX = 4;    framesY = 4;                                                   break;
1745             case Unit_Troopers:         objectPicBase = ObjPic_Troopers;         framesX = 4;    framesY = 4;                                                   break;
1746         }
1747 
1748         SDL_Texture* pObjectSprite = pGFXManager->getObjPic(objectPicBase, unit.house)[currentZoomlevel];
1749 
1750         int angle = unit.angle / (NUM_ANGLES/framesX);
1751 
1752         int frame = (unit.itemID == Unit_Sandworm) ? 5 : 0;
1753 
1754         SDL_Rect source = calcSpriteSourceRect(pObjectSprite, angle, framesX, frame, framesY);
1755         int frameSizeX = source.w;
1756         int frameSizeY = source.h;
1757         SDL_Rect drawLocation = calcSpriteDrawingRect(  pObjectSprite,
1758                                                         pScreenborder->world2screenX((position.x*TILESIZE)+(TILESIZE/2)),
1759                                                         pScreenborder->world2screenY((position.y*TILESIZE)+(TILESIZE/2)),
1760                                                         framesX, framesY, HAlign::Center, VAlign::Center);
1761 
1762         SDL_RenderCopy(renderer, pObjectSprite, &source, &drawLocation);
1763 
1764         if(objectPicGun >= 0) {
1765             SDL_Texture* pGunSprite = pGFXManager->getObjPic(objectPicGun, unit.house)[currentZoomlevel];
1766 
1767             SDL_Rect source2 = calcSpriteSourceRect(pGunSprite, unit.angle, NUM_ANGLES);
1768             SDL_Rect drawLocation2 = calcSpriteDrawingRect( pGunSprite,
1769                                                             pScreenborder->world2screenX((position.x*TILESIZE)+(TILESIZE/2)+gunOffset[unit.angle].x),
1770                                                             pScreenborder->world2screenY((position.y*TILESIZE)+(TILESIZE/2)+gunOffset[unit.angle].y),
1771                                                             NUM_ANGLES, 1, HAlign::Center, VAlign::Center);
1772 
1773             SDL_RenderCopy(renderer, pGunSprite, &source2, &drawLocation2);
1774         }
1775 
1776         if(unit.itemID == Unit_RaiderTrike || unit.itemID == Unit_Deviator || unit.itemID == Unit_Special) {
1777             SDL_Texture* pStarSprite = pGFXManager->getObjPic(ObjPic_Star)[currentZoomlevel];
1778 
1779             SDL_Rect drawLocation2 = calcDrawingRect(   pStarSprite,
1780                                                         pScreenborder->world2screenX((position.x*TILESIZE)+(TILESIZE/2)) + frameSizeX/2 - 1,
1781                                                         pScreenborder->world2screenY((position.y*TILESIZE)+(TILESIZE/2)) + frameSizeY/2 - 1,
1782                                                         HAlign::Right, VAlign::Bottom);
1783 
1784             SDL_RenderCopy(renderer, pStarSprite, nullptr, &drawLocation2);
1785         }
1786 
1787     }
1788 
1789     // draw tactical pos rectangle (the starting screen)
1790     if(!bCompleteMap && getMapVersion() < 2 && mapInfo.tacticalPos.isValid()) {
1791 
1792         SDL_Rect dest;
1793         dest.x = pScreenborder->world2screenX( mapInfo.tacticalPos.x*TILESIZE);
1794         dest.y = pScreenborder->world2screenY( mapInfo.tacticalPos.y*TILESIZE);
1795         dest.w = world2zoomedWorld(15*TILESIZE);
1796         dest.h = world2zoomedWorld(10*TILESIZE);
1797 
1798         renderDrawRect(renderer, &dest, COLOR_DARKGREY);
1799     }
1800 
1801     SDL_Texture* validPlace = nullptr;
1802     SDL_Texture* invalidPlace = nullptr;
1803     SDL_Texture* greyPlace = nullptr;
1804 
1805     switch(currentZoomlevel) {
1806         case 0: {
1807             validPlace = pGFXManager->getUIGraphic(UI_ValidPlace_Zoomlevel0);
1808             invalidPlace = pGFXManager->getUIGraphic(UI_InvalidPlace_Zoomlevel0);
1809             greyPlace = pGFXManager->getUIGraphic(UI_GreyPlace_Zoomlevel0);
1810         } break;
1811 
1812         case 1: {
1813             validPlace = pGFXManager->getUIGraphic(UI_ValidPlace_Zoomlevel1);
1814             invalidPlace = pGFXManager->getUIGraphic(UI_InvalidPlace_Zoomlevel1);
1815             greyPlace = pGFXManager->getUIGraphic(UI_GreyPlace_Zoomlevel1);
1816         } break;
1817 
1818         case 2:
1819         default: {
1820             validPlace = pGFXManager->getUIGraphic(UI_ValidPlace_Zoomlevel2);
1821             invalidPlace = pGFXManager->getUIGraphic(UI_InvalidPlace_Zoomlevel2);
1822             greyPlace = pGFXManager->getUIGraphic(UI_GreyPlace_Zoomlevel2);
1823         } break;
1824     }
1825 
1826     if(!bCompleteMap && !pInterface->hasChildWindow() && pScreenborder->isScreenCoordInsideMap(drawnMouseX, drawnMouseY)) {
1827 
1828         int xPos = pScreenborder->screen2MapX(drawnMouseX);
1829         int yPos = pScreenborder->screen2MapY(drawnMouseY);
1830 
1831         if(currentEditorMode.mode == EditorMode::EditorMode_Terrain) {
1832 
1833             int halfsize = currentEditorMode.pensize/2;
1834 
1835             for(int m=0;m<mapMirror->getSize();m++) {
1836 
1837                 Coord position = mapMirror->getCoord( Coord(xPos, yPos), m);
1838 
1839 
1840                 SDL_Rect dest;
1841                 dest.x = pScreenborder->world2screenX( (position.x-halfsize)*TILESIZE);
1842                 dest.y = pScreenborder->world2screenY( (position.y-halfsize)*TILESIZE);
1843                 dest.w = world2zoomedWorld(currentEditorMode.pensize*TILESIZE);
1844                 dest.h = world2zoomedWorld(currentEditorMode.pensize*TILESIZE);
1845 
1846                 // now draw the box with parts at all corners
1847                 // top left bit
1848                 for(int i=0;i<=currentZoomlevel;i++) {
1849                     renderDrawHLine(renderer, dest.x+i, dest.y+i, dest.x+(currentZoomlevel+1)*3, COLOR_WHITE);
1850                     renderDrawVLine(renderer, dest.x+i, dest.y+i, dest.y+(currentZoomlevel+1)*3, COLOR_WHITE);
1851                 }
1852 
1853                 // top right bit
1854                 for(int i=0;i<=currentZoomlevel;i++) {
1855                     renderDrawHLine(renderer, dest.x + dest.w-1 - i, dest.y+i, dest.x + dest.w-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
1856                     renderDrawVLine(renderer, dest.x + dest.w-1 - i, dest.y+i, dest.y+(currentZoomlevel+1)*3, COLOR_WHITE);
1857                 }
1858 
1859                 // bottom left bit
1860                 for(int i=0;i<=currentZoomlevel;i++) {
1861                     renderDrawHLine(renderer, dest.x+i, dest.y + dest.h-1 - i, dest.x+(currentZoomlevel+1)*3, COLOR_WHITE);
1862                     renderDrawVLine(renderer, dest.x+i, dest.y + dest.h-1 - i, dest.y + dest.h-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
1863                 }
1864 
1865                 // bottom right bit
1866                 for(int i=0;i<=currentZoomlevel;i++) {
1867                     renderDrawHLine(renderer, dest.x + dest.w-1 - i, dest.y + dest.h-1 - i, dest.x + dest.w-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
1868                     renderDrawVLine(renderer, dest.x + dest.w-1 - i, dest.y + dest.h-1 - i, dest.y + dest.h-1 - (currentZoomlevel+1)*3, COLOR_WHITE);
1869                 }
1870             }
1871 
1872         } else if(currentEditorMode.mode == EditorMode::EditorMode_Structure) {
1873             Coord structureSize = getStructureSize(currentEditorMode.itemID);
1874 
1875             for(int m=0;m<mapMirror->getSize();m++) {
1876 
1877                 Coord position = mapMirror->getCoord( Coord(xPos, yPos), m, structureSize);
1878 
1879                 for(int x = position.x; x < (position.x + structureSize.x); x++) {
1880                     for(int y = position.y; y < (position.y + structureSize.y); y++) {
1881                         SDL_Texture* image = validPlace;
1882 
1883                         // check if mirroring of the original (!) position is possible
1884                         if(mapMirror->mirroringPossible( Coord(xPos, yPos), structureSize) == false) {
1885                             image = invalidPlace;
1886                         }
1887 
1888                         // check all mirrored places
1889                         for(int k=0;k<mapMirror->getSize();k++) {
1890                             Coord pos = mapMirror->getCoord( Coord(x, y), k);
1891 
1892                             if(!map.isInsideMap(pos.x,pos.y) || isTileBlocked(pos.x, pos.y, true, (currentEditorMode.itemID != Structure_Slab1) )) {
1893                                 image = invalidPlace;
1894                             } else if((image != invalidPlace) && (map(pos.x,pos.y) != Terrain_Rock)) {
1895                                 image = greyPlace;
1896                             }
1897                         }
1898 
1899                         SDL_Rect drawLocation = {   pScreenborder->world2screenX(x*TILESIZE), pScreenborder->world2screenY(y*TILESIZE),
1900                                                     zoomedTilesize, zoomedTilesize };
1901                         SDL_RenderCopy(renderer, image, nullptr, &drawLocation);
1902                     }
1903                 }
1904             }
1905         } else if(currentEditorMode.mode == EditorMode::EditorMode_Unit) {
1906             for(int m=0;m<mapMirror->getSize();m++) {
1907 
1908                 Coord position = mapMirror->getCoord( Coord(xPos, yPos), m);
1909 
1910                 SDL_Texture* image = validPlace;
1911                 // check all mirrored places
1912                 for(int k=0;k<mapMirror->getSize();k++) {
1913                     Coord pos = mapMirror->getCoord( position, k);
1914 
1915                     if(!map.isInsideMap(pos.x,pos.y) || isTileBlocked(pos.x, pos.y, false, true)) {
1916                         image = invalidPlace;
1917                     }
1918                 }
1919                 SDL_Rect drawLocation = calcDrawingRect(image, pScreenborder->world2screenX(position.x*TILESIZE), pScreenborder->world2screenY(position.y*TILESIZE));
1920                 SDL_RenderCopy(renderer, image, nullptr, &drawLocation);
1921             }
1922         } else if(currentEditorMode.mode == EditorMode::EditorMode_TacticalPos) {
1923             // draw tactical pos rectangle (the starting screen)
1924             if(mapInfo.tacticalPos.isValid()) {
1925 
1926                 SDL_Rect dest = {   pScreenborder->world2screenX(xPos*TILESIZE), pScreenborder->world2screenY(yPos*TILESIZE),
1927                                     world2zoomedWorld(15*TILESIZE), world2zoomedWorld(10*TILESIZE) };
1928                 renderDrawRect(renderer, &dest, COLOR_WHITE);
1929             }
1930         }
1931     }
1932 
1933     // draw selection rect for units (selection rect for structures is already drawn)
1934     if(!bCompleteMap) {
1935         std::vector<int> selectedUnits = getMirrorUnits(selectedUnitID);
1936 
1937         for(const Unit& unit : units) {
1938             if(std::find(selectedUnits.begin(), selectedUnits.end(), unit.id) != selectedUnits.end()) {
1939                 const Coord& position = unit.position;
1940 
1941                 SDL_Texture* selectionBox = nullptr;
1942 
1943                 switch(currentZoomlevel) {
1944                     case 0:     selectionBox = pGFXManager->getUIGraphic(UI_SelectionBox_Zoomlevel0);   break;
1945                     case 1:     selectionBox = pGFXManager->getUIGraphic(UI_SelectionBox_Zoomlevel1);   break;
1946                     case 2:
1947                     default:    selectionBox = pGFXManager->getUIGraphic(UI_SelectionBox_Zoomlevel2);   break;
1948                 }
1949 
1950                 SDL_Rect dest = calcDrawingRect(selectionBox,
1951                                                 pScreenborder->world2screenX((position.x*TILESIZE)+(TILESIZE/2)),
1952                                                 pScreenborder->world2screenY((position.y*TILESIZE)+(TILESIZE/2)),
1953                                                 HAlign::Center, VAlign::Center);
1954 
1955                 SDL_RenderCopy(renderer, selectionBox, nullptr, &dest);
1956             }
1957         }
1958     }
1959 
1960     // draw selection rect for map items (spice bloom, special bloom or spice field)
1961     if(!bCompleteMap && selectedMapItemCoord.isValid()
1962         && ( (std::find(spiceBlooms.begin(), spiceBlooms.end(), selectedMapItemCoord) != spiceBlooms.end())
1963             || (std::find(specialBlooms.begin(), specialBlooms.end(), selectedMapItemCoord) != specialBlooms.end())
1964             || (std::find(spiceFields.begin(), spiceFields.end(), selectedMapItemCoord) != spiceFields.end()) )) {
1965 
1966         SDL_Texture* selectionBox = nullptr;
1967 
1968         switch(currentZoomlevel) {
1969             case 0:     selectionBox = pGFXManager->getUIGraphic(UI_SelectionBox_Zoomlevel0);   break;
1970             case 1:     selectionBox = pGFXManager->getUIGraphic(UI_SelectionBox_Zoomlevel1);   break;
1971             case 2:
1972             default:    selectionBox = pGFXManager->getUIGraphic(UI_SelectionBox_Zoomlevel2);   break;
1973         }
1974 
1975         SDL_Rect dest = calcDrawingRect(selectionBox,
1976                                         pScreenborder->world2screenX((selectedMapItemCoord.x*TILESIZE)+(TILESIZE/2)),
1977                                         pScreenborder->world2screenY((selectedMapItemCoord.y*TILESIZE)+(TILESIZE/2)),
1978                                         HAlign::Center, VAlign::Center);
1979 
1980         SDL_RenderCopy(renderer, selectionBox, nullptr, &dest);
1981     }
1982 }
1983 
saveMapshot()1984 void MapEditor::saveMapshot() {
1985     int oldCurrentZoomlevel = currentZoomlevel;
1986     currentZoomlevel = 0;
1987 
1988     std::string mapshotFilename = (lastSaveName.empty() ? generateMapname() : getBasename(lastSaveName, true)) + ".png";
1989 
1990     int sizeX = world2zoomedWorld(map.getSizeX()*TILESIZE);
1991     int sizeY = world2zoomedWorld(map.getSizeY()*TILESIZE);
1992 
1993     SDL_Rect board = { 0, 0, sizeX, sizeY };
1994 
1995     ScreenBorder tmpScreenborder(board);
1996     tmpScreenborder.adjustScreenBorderToMapsize(map.getSizeX(), map.getSizeY());
1997 
1998     SDL_Texture* renderTarget = SDL_CreateTexture(renderer, SCREEN_FORMAT, SDL_TEXTUREACCESS_TARGET, sizeX, sizeY);
1999     if(renderTarget == nullptr) {
2000         SDL_Log("SDL_CreateTexture() failed: %s", SDL_GetError());
2001         currentZoomlevel = oldCurrentZoomlevel;
2002         return;
2003     }
2004 
2005     SDL_Texture* oldRenderTarget = SDL_GetRenderTarget(renderer);
2006     if(SDL_SetRenderTarget(renderer, renderTarget) != 0) {
2007         SDL_Log("SDL_SetRenderTarget() failed: %s", SDL_GetError());
2008         SDL_SetRenderTarget(renderer, oldRenderTarget);
2009         SDL_DestroyTexture(renderTarget);
2010         currentZoomlevel = oldCurrentZoomlevel;
2011         return;
2012     }
2013 
2014     SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
2015     SDL_RenderClear(renderer);
2016 
2017     drawMap(&tmpScreenborder, true);
2018 
2019     SDL_Surface* pMapshotSurface = renderReadSurface(renderer);
2020     SavePNG(pMapshotSurface, mapshotFilename.c_str());
2021     SDL_FreeSurface(pMapshotSurface);
2022 
2023     SDL_SetRenderTarget(renderer, oldRenderTarget);
2024     SDL_DestroyTexture(renderTarget);
2025 
2026     currentZoomlevel = oldCurrentZoomlevel;
2027 }
2028