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