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 <Map.h>
19 
20 #include <globals.h>
21 
22 #include <Game.h>
23 #include <House.h>
24 #include <ScreenBorder.h>
25 #include <sand.h>
26 
27 #include <units/UnitBase.h>
28 #include <units/InfantryBase.h>
29 #include <units/AirUnit.h>
30 #include <structures/StructureBase.h>
31 
32 #include <limits.h>
33 #include <stack>
34 #include <set>
35 
Map(int xSize,int ySize)36 Map::Map(int xSize, int ySize)
37  : sizeX(xSize), sizeY(ySize), tiles(nullptr), lastSinglySelectedObject(nullptr) {
38 
39     tiles = new Tile[sizeX*sizeY];
40 
41     for(int i=0; i<sizeX; i++) {
42         for(int j=0; j<sizeY; j++) {
43             tiles[i+j*sizeX].location.x = i;
44             tiles[i+j*sizeX].location.y = j;
45         }
46     }
47 }
48 
49 
~Map()50 Map::~Map() {
51     delete[] tiles;
52 }
53 
load(InputStream & stream)54 void Map::load(InputStream& stream) {
55     sizeX = stream.readSint32();
56     sizeY = stream.readSint32();
57 
58     for (int i = 0; i < sizeX; i++) {
59         for (int j = 0; j < sizeY; j++) {
60             getTile(i,j)->load(stream);
61             getTile(i,j)->location.x = i;
62             getTile(i,j)->location.y = j;
63         }
64     }
65 }
66 
save(OutputStream & stream) const67 void Map::save(OutputStream& stream) const {
68     stream.writeSint32(sizeX);
69     stream.writeSint32(sizeY);
70 
71     for (int i = 0; i < sizeX; i++) {
72         for (int j = 0; j < sizeY; j++) {
73             getTile(i,j)->save(stream);
74         }
75     }
76 }
77 
createSandRegions()78 void Map::createSandRegions() {
79     std::stack<Tile*> tileQueue;
80     std::vector<bool> visited(sizeX * sizeY);
81 
82     for(int i = 0; i < sizeX; i++) {
83         for(int j = 0; j < sizeY; j++)  {
84             getTile(i,j)->setSandRegion(NONE_ID);
85         }
86     }
87 
88     Uint32 region = 0;
89     for(int i = 0; i < sizeX; i++) {
90         for(int j = 0; j < sizeY; j++) {
91             if(!getTile(i,j)->isRock() && !visited[j*sizeX+i]) {
92                 tileQueue.push(getTile(i,j));
93 
94                 while(!tileQueue.empty()) {
95                     Tile* pTile = tileQueue.top();
96                     tileQueue.pop();
97 
98                     pTile->setSandRegion(region);
99                     for(int angle = 0; angle < NUM_ANGLES; angle++) {
100                         Coord pos = getMapPos(angle, pTile->location);
101                         if(tileExists(pos) && !getTile(pos)->isRock() && !visited[pos.y*sizeX + pos.x]) {
102                             tileQueue.push(getTile(pos));
103                             visited[pos.y*sizeX + pos.x] = true;
104                         }
105                     }
106                 }
107                 region++;
108             }
109         }
110     }
111 }
112 
damage(Uint32 damagerID,House * damagerOwner,const Coord & realPos,Uint32 bulletID,FixPoint damage,int damageRadius,bool air)113 void Map::damage(Uint32 damagerID, House* damagerOwner, const Coord& realPos, Uint32 bulletID, FixPoint damage, int damageRadius, bool air) {
114     Coord location = Coord(realPos.x/TILESIZE, realPos.y/TILESIZE);
115 
116     std::set<Uint32>    affectedAirUnits;
117     std::set<Uint32>    affectedGroundAndUndergroundUnits;
118 
119     for(int i = location.x-2; i <= location.x+2; i++) {
120         for(int j = location.y-2; j <= location.y+2; j++) {
121             if(tileExists(i, j)) {
122                 Tile* pTile = getTile(i,j);
123 
124                 affectedAirUnits.insert(pTile->getAirUnitList().begin(), pTile->getAirUnitList().end());
125                 affectedGroundAndUndergroundUnits.insert(pTile->getInfantryList().begin(), pTile->getInfantryList().end());
126                 affectedGroundAndUndergroundUnits.insert(pTile->getUndergroundUnitList().begin(), pTile->getUndergroundUnitList().end());
127                 affectedGroundAndUndergroundUnits.insert(pTile->getNonInfantryGroundObjectList().begin(), pTile->getNonInfantryGroundObjectList().end());
128             }
129         }
130     }
131 
132     if(bulletID == Bullet_Sandworm) {
133         for(Uint32 objectID : affectedGroundAndUndergroundUnits) {
134             ObjectBase* pObject = currentGame->getObjectManager().getObject(objectID);
135             if((pObject->getItemID() != Unit_Sandworm) && (pObject->isAGroundUnit() || pObject->isInfantry()) && (pObject->getLocation() == location)) {
136                 pObject->setVisible(VIS_ALL, false);
137                 pObject->handleDamage( lround(damage), damagerID, damagerOwner);
138             }
139         }
140     } else {
141 
142         if(air == true) {
143             // air damage
144             if((bulletID == Bullet_DRocket) || (bulletID == Bullet_Rocket) || (bulletID == Bullet_TurretRocket)|| (bulletID == Bullet_SmallRocket)) {
145                 for(Uint32 objectID : affectedAirUnits) {
146                     AirUnit* pAirUnit = dynamic_cast<AirUnit*>(currentGame->getObjectManager().getObject(objectID));
147                     if(pAirUnit == nullptr)
148                         continue;
149 
150 
151                     Coord centerPoint = pAirUnit->getCenterPoint();
152                     int distance = lround(distanceFrom(centerPoint, realPos));
153 
154                     if(distance <= damageRadius) {
155                         if(bulletID == Bullet_DRocket) {
156                             if((pAirUnit->getItemID() != Unit_Carryall) && (pAirUnit->getItemID() != Unit_Sandworm) && (pAirUnit->getItemID() != Unit_Frigate)) {
157                                 // try to deviate
158                                 if(currentGame->randomGen.randFixPoint() < getDeviateWeakness((HOUSETYPE) pAirUnit->getOriginalHouseID())) {
159                                     pAirUnit->deviate(damagerOwner);
160                                 }
161                             }
162                         } else {
163                             int scaledDamage = lround(damage) >> (distance/4 + 1);
164                             pAirUnit->handleDamage(scaledDamage, damagerID, damagerOwner);
165                         }
166                     }
167 
168                 }
169             }
170         } else {
171             // non air damage
172             for(Uint32 objectID : affectedGroundAndUndergroundUnits) {
173                 ObjectBase* pObject = currentGame->getObjectManager().getObject(objectID);
174 
175                 if(pObject->isAStructure()) {
176                     StructureBase* pStructure = dynamic_cast<StructureBase*>(pObject);
177 
178                     Coord topLeftCorner = pStructure->getLocation()*TILESIZE;
179                     Coord bottomRightCorner = topLeftCorner + pStructure->getStructureSize()*TILESIZE;
180 
181                     if(realPos.x >= topLeftCorner.x && realPos.y >= topLeftCorner.y && realPos.x < bottomRightCorner.x && realPos.y < bottomRightCorner.y) {
182                         pStructure->handleDamage(lround(damage), damagerID, damagerOwner);
183 
184                         if( (bulletID == Bullet_LargeRocket || bulletID == Bullet_Rocket || bulletID == Bullet_TurretRocket || bulletID == Bullet_SmallRocket)
185                             && (pStructure->getHealth() < pStructure->getMaxHealth()/2)) {
186                             if(pStructure->getNumSmoke() < 5) {
187                                 pStructure->addSmoke(realPos, currentGame->getGameCycleCount());
188                             }
189                         }
190                     }
191 
192 
193                 } else if(pObject->isAUnit()) {
194                     UnitBase* pUnit = dynamic_cast<UnitBase*>(pObject);
195 
196                     Coord centerPoint = pUnit->getCenterPoint();
197                     int distance = lround(distanceFrom(centerPoint, realPos));
198 
199                     if(distance <= damageRadius) {
200                         if(bulletID == Bullet_DRocket) {
201                             if((pUnit->getItemID() != Unit_Carryall) && (pUnit->getItemID() != Unit_Sandworm) && (pUnit->getItemID() != Unit_Frigate)) {
202                                 // try to deviate
203                                 if(currentGame->randomGen.randFixPoint() < getDeviateWeakness((HOUSETYPE) pUnit->getOriginalHouseID())) {
204                                     pUnit->deviate(damagerOwner);
205                                 }
206                             }
207                         } else if(bulletID == Bullet_Sonic) {
208                             pUnit->handleDamage(lround(damage), damagerID, damagerOwner);
209                         } else {
210                             int scaledDamage = lround(damage) >> (distance/16 + 1);
211                             pUnit->handleDamage(scaledDamage, damagerID, damagerOwner);
212                         }
213                     }
214                 }
215             }
216 
217 
218             if(currentGameMap->tileExists(location)) {
219 
220                 Tile* pTile = currentGameMap->getTile(location);
221 
222                 if(((bulletID == Bullet_Rocket) || (bulletID == Bullet_TurretRocket) || (bulletID == Bullet_SmallRocket) || (bulletID == Bullet_LargeRocket))
223                     && (!pTile->hasAGroundObject() || !pTile->getGroundObject()->isAStructure()) )
224                 {
225 
226                     if(((pTile->getType() == Terrain_Rock) && (pTile->getTerrainTile() == Tile::TerrainTile_RockFull)) || (pTile->getType() == Terrain_Slab)) {
227                         if(pTile->getType() == Terrain_Slab) {
228                             pTile->setType(Terrain_Rock);
229                             pTile->setDestroyedStructureTile(Destroyed1x1Structure);
230                             pTile->setOwner(NONE_ID);
231                         }
232 
233                         pTile->addDamage(Tile::Terrain_RockDamage, (bulletID==Bullet_SmallRocket) ? Tile::RockDamage1 : Tile::RockDamage2, realPos);
234 
235                     } else if((pTile->getType() == Terrain_Sand) || (pTile->getType() == Terrain_Spice)) {
236                         if(bulletID==Bullet_SmallRocket) {
237                             pTile->addDamage(Tile::Terrain_SandDamage, currentGame->randomGen.rand(Tile::SandDamage1, Tile::SandDamage2), realPos);
238                         } else {
239                             pTile->addDamage(Tile::Terrain_SandDamage, currentGame->randomGen.rand(Tile::SandDamage3, Tile::SandDamage4), realPos);
240                         }
241                     }
242                 }
243             }
244 
245         }
246     }
247 
248     if((bulletID != Bullet_Sonic) && (bulletID != Bullet_Sandworm) && tileExists(location) && getTile(location)->isSpiceBloom()) {
249         getTile(location)->triggerSpiceBloom(damagerOwner);
250     }
251 }
252 
253 /**
254     Check each tile which surrounds the building location to make sure there
255     is no building. We want to ensure we don't block units in and have
256     traffic lanes for our troops
257 **/
258 
isAStructureGap(int x,int y,int buildingSizeX,int buildingSizeY) const259 bool Map::isAStructureGap(int x, int y, int buildingSizeX, int buildingSizeY) const {
260 
261     bool hasAGap = true;
262 
263     // Spacing rues don't apply for rocket turrets
264     if(buildingSizeX == 1){
265         return hasAGap;
266     }
267 
268     int xMin = x - 1;
269     int xMax = x + buildingSizeX + 1;
270     int yMin = y - 1;
271     int yMax = y + buildingSizeY + 1;
272 
273     for(int i = xMin; i < xMax; i++) {
274         for(int j = yMin; j < yMax; j++) {
275             if(currentGameMap->tileExists(i,j)
276                && !((i == xMin || i == xMax) && (j == yMin || j == yMax))){ //Corners are ok as units can get through
277 
278                 const Tile* pTile = getTile(i,j);
279 
280                 if((pTile->hasAStructure() && !pTile->isConcrete())) { // I need some more conditions to make it ignore units
281 
282                         hasAGap = false;
283                 }
284             }
285         }
286     }
287 
288     return hasAGap;
289 }
290 
okayToPlaceStructure(int x,int y,int buildingSizeX,int buildingSizeY,bool tilesRequired,const House * pHouse,bool bIgnoreUnits) const291 bool Map::okayToPlaceStructure(int x, int y, int buildingSizeX, int buildingSizeY, bool tilesRequired, const House* pHouse, bool bIgnoreUnits) const {
292     bool withinBuildRange = false;
293 
294     for(int i = x; i < x + buildingSizeX; i++) {
295         for(int j = y; j < y + buildingSizeY; j++) {
296             if(!currentGameMap->tileExists(i,j)) {
297                 return false;
298             }
299 
300             const Tile* pTile = getTile(i,j);
301 
302             if(!pTile->isRock() || (tilesRequired && !pTile->isConcrete()) || (!bIgnoreUnits && pTile->isBlocked())) {
303                 return false;
304             }
305 
306             if((pHouse == nullptr) || isWithinBuildRange(i, j, pHouse)) {
307                 withinBuildRange = true;
308             }
309         }
310     }
311     return withinBuildRange;
312 }
313 
314 
isWithinBuildRange(int x,int y,const House * pHouse) const315 bool Map::isWithinBuildRange(int x, int y, const House* pHouse) const {
316     bool withinBuildRange = false;
317 
318     for (int i = x - BUILDRANGE; i <= x + BUILDRANGE; i++)
319         for (int j = y - BUILDRANGE; j <= y + BUILDRANGE; j++)
320             if (tileExists(i, j) && (getTile(i,j)->getOwner() == pHouse->getHouseID()))
321                 withinBuildRange = true;
322 
323     return withinBuildRange;
324 }
325 
326 /**
327     This method figures out the direction of tile pos relative to tile source.
328     \param  source  the starting point
329     \param  pos     the destination
330     \return one of RIGHT, RIGHTUP, UP, LEFTUP, LEFT, LEFTDOWN, DOWN, RIGHTDOWN or INVALID
331 */
getPosAngle(const Coord & source,const Coord & pos) const332 int Map::getPosAngle(const Coord& source, const Coord& pos) const {
333     if(pos.x > source.x) {
334         if(pos.y > source.y) {
335             return RIGHTDOWN;
336         } else if(pos.y < source.y) {
337             return RIGHTUP;
338         } else {
339             return RIGHT;
340         }
341     } else if(pos.x < source.x) {
342         if(pos.y > source.y) {
343             return LEFTDOWN;
344         } else if(pos.y < source.y) {
345             return LEFTUP;
346         } else {
347             return LEFT;
348         }
349     } else {
350         if(pos.y > source.y) {
351             return DOWN;
352         } else if(pos.y < source.y) {
353             return UP;
354         } else {
355             return INVALID;
356         }
357     }
358 }
359 
360 /**
361     This method calculates the coordinate of one of the neighbor tiles of source
362     \param  angle   one of RIGHT, RIGHTUP, UP, LEFTUP, LEFT, LEFTDOWN, DOWN, RIGHTDOWN
363     \param  source  the tile to calculate neighbor tiles from
364 */
getMapPos(int angle,const Coord & source) const365 Coord Map::getMapPos(int angle, const Coord& source) const {
366     switch (angle)
367     {
368         case (RIGHT):       return Coord(source.x + 1 , source.y);       break;
369         case (RIGHTUP):     return Coord(source.x + 1 , source.y - 1);   break;
370         case (UP):          return Coord(source.x     , source.y - 1);   break;
371         case (LEFTUP):      return Coord(source.x - 1 , source.y - 1);   break;
372         case (LEFT):        return Coord(source.x - 1 , source.y);       break;
373         case (LEFTDOWN):    return Coord(source.x - 1 , source.y + 1);   break;
374         case (DOWN):        return Coord(source.x     , source.y + 1);   break;
375         case (RIGHTDOWN):   return Coord(source.x + 1 , source.y + 1);   break;
376         default:            return Coord(source.x     , source.y);       break;
377     }
378 }
379 
380 //building size is num squares
findDeploySpot(UnitBase * pUnit,const Coord & origin,Random & randomGen,const Coord & gatherPoint,const Coord & buildingSize) const381 Coord Map::findDeploySpot(UnitBase* pUnit, const Coord& origin, Random& randomGen, const Coord& gatherPoint, const Coord& buildingSize) const {
382     FixPoint    closestDistance = FixPt_MAX;
383     Coord       closestPoint;
384     Coord       size;
385 
386     bool    found = false;
387     bool    foundClosest = false;
388 
389     int counter = 0;
390     int depth = 0;
391 
392     if(pUnit->isAFlyingUnit()) {
393         return origin;
394     }
395 
396     int ranX = origin.x;
397     int ranY = origin.y;
398 
399     do {
400         int edge = randomGen.rand(0, 3);
401         switch(edge) {
402             case 0: //right edge
403                 ranX = origin.x + buildingSize.x + depth;
404                 ranY = randomGen.rand(origin.y - depth, origin.y + buildingSize.y + depth);
405                 break;
406             case 1: //top edge
407                 ranX = randomGen.rand(origin.x - depth, origin.x + buildingSize.x + depth);
408                 ranY = origin.y - depth - ((buildingSize.y == 0) ? 0 : 1);
409                 break;
410             case 2: //left edge
411                 ranX = origin.x - depth - ((buildingSize.x == 0) ? 0 : 1);
412                 ranY = randomGen.rand(origin.y - depth, origin.y + buildingSize.y + depth);
413                 break;
414             case 3: //bottom edge
415                 ranX = randomGen.rand(origin.x - depth, origin.x + buildingSize.x + depth);
416                 ranY = origin.y + buildingSize.y + depth;
417                 break;
418             default:
419                 break;
420         }
421 
422         bool bOK2Deploy = pUnit->canPass(ranX, ranY);
423 
424         if(pUnit->isTracked() && tileExists(ranX, ranY) && getTile(ranX, ranY)->hasInfantry()) {
425             // we do not deploy on enemy infantry
426             bOK2Deploy = false;
427         }
428 
429         if(bOK2Deploy) {
430             if(gatherPoint.isInvalid()) {
431                 closestPoint.x = ranX;
432                 closestPoint.y = ranY;
433                 found = true;
434             } else {
435                 Coord temp = Coord(ranX, ranY);
436                 if(blockDistance(temp, gatherPoint) < closestDistance) {
437                     closestDistance = blockDistance(temp, gatherPoint);
438                     closestPoint.x = ranX;
439                     closestPoint.y = ranY;
440                     foundClosest = true;
441                 }
442             }
443         }
444 
445         if(counter++ >= 100) {
446             //if hasn't found a spot on tempObject layer in 100 tries, goto next
447 
448             counter = 0;
449             if(++depth > (std::max(currentGameMap->getSizeX(), currentGameMap->getSizeY()))) {
450                 closestPoint.invalidate();
451                 found = true;
452                 SDL_Log("Warning: Cannot find deploy position because the map is full!");
453             }
454         }
455     } while (!found && (!foundClosest || (counter > 0)));
456 
457     return closestPoint;
458 }
459 
460 /**
461     This method finds the tile which is at a map border and is at minimal distance to the structure
462     specified by origin and buildingsSize. This method is especcially useful for Carryalls and Frigates
463     that have to enter the map to deliver units.
464     \param origin           the position of the structure in map coordinates
465     \param buildingSize    the number of tiles occupied by the building (e.g. 3x2 for refinery)
466 */
findClosestEdgePoint(const Coord & origin,const Coord & buildingSize) const467 Coord Map::findClosestEdgePoint(const Coord& origin, const Coord& buildingSize) const {
468     int closestDistance = INT_MAX;
469     Coord closestPoint;
470 
471     if(origin.x < (sizeX - (origin.x + buildingSize.x))) {
472         closestPoint.x = 0;
473         closestDistance = origin.x;
474     } else {
475         closestPoint.x = sizeX - 1;
476         closestDistance = sizeX - (origin.x + buildingSize.x);
477     }
478     closestPoint.y = origin.y;
479 
480     if(origin.y < closestDistance) {
481         closestPoint.x = origin.x;
482         closestPoint.y = 0;
483         closestDistance = origin.y;
484     }
485 
486     if((sizeY - (origin.y + buildingSize.y)) < closestDistance) {
487         closestPoint.x = origin.x;
488         closestPoint.y = sizeY - 1;
489         // closestDistance = origin.y;  //< Not needed anymore
490     }
491 
492     return closestPoint;
493 }
494 
495 
removeObjectFromMap(Uint32 objectID)496 void Map::removeObjectFromMap(Uint32 objectID) {
497     for(int y = 0; y < sizeY ; y++) {
498         for(int x = 0 ; x < sizeX ; x++) {
499             getTile(x,y)->unassignObject(objectID);
500         }
501     }
502 }
503 
selectObjects(int houseID,int x1,int y1,int x2,int y2,int realX,int realY,bool objectARGMode)504 void Map::selectObjects(int houseID, int x1, int y1, int x2, int y2, int realX, int realY, bool objectARGMode) {
505 
506     ObjectBase  *lastCheckedObject = nullptr;
507     ObjectBase *lastSelectedObject = nullptr;
508 
509     //if selection rectangle is checking only one tile and has shift selected we want to add/ remove that unit from the selected group of units
510     if(!objectARGMode) {
511         currentGame->unselectAll(currentGame->getSelectedList());
512         currentGame->getSelectedList().clear();
513         currentGame->selectionChanged();
514     }
515 
516     if((x1 == x2) && (y1 == y2) && tileExists(x1, y1)) {
517 
518         if(getTile(x1,y1)->isExplored(houseID) || debug) {
519             lastCheckedObject = getTile(x1,y1)->getObjectAt(realX, realY);
520         } else {
521             lastCheckedObject = nullptr;
522         }
523 
524         if((lastCheckedObject != nullptr) && (lastCheckedObject->getOwner()->getHouseID() == houseID)) {
525             if((lastCheckedObject == lastSinglySelectedObject) && ( !lastCheckedObject->isAStructure())) {
526                 for(int i = screenborder->getTopLeftTile().x; i <= screenborder->getBottomRightTile().x; i++) {
527                     for(int j = screenborder->getTopLeftTile().y; j <= screenborder->getBottomRightTile().y; j++) {
528                         if(tileExists(i,j) && getTile(i,j)->hasAnObject()) {
529                             getTile(i,j)->selectAllPlayersUnitsOfType(houseID, lastSinglySelectedObject->getItemID(), &lastCheckedObject, &lastSelectedObject);
530                         }
531                     }
532                 }
533                 lastSinglySelectedObject = nullptr;
534 
535             } else if(!lastCheckedObject->isSelected()) {
536 
537                 lastCheckedObject->setSelected(true);
538                 currentGame->getSelectedList().insert(lastCheckedObject->getObjectID());
539                 currentGame->selectionChanged();
540                 lastSelectedObject = lastCheckedObject;
541                 lastSinglySelectedObject = lastSelectedObject;
542 
543             } else if(objectARGMode) {
544                 //holding down shift, unselect this unit
545                 lastCheckedObject->setSelected(false);
546                 currentGame->getSelectedList().erase(lastCheckedObject->getObjectID());
547                 currentGame->selectionChanged();
548             }
549 
550         } else {
551             lastSinglySelectedObject = nullptr;
552         }
553 
554     } else {
555         lastSinglySelectedObject = nullptr;
556         for(int i = std::min(x1, x2); i <= std::max(x1, x2); i++) {
557             for(int j = std::min(y1, y2); j <= std::max(y1, y2); j++) {
558                 if(tileExists(i,j) && getTile(i,j)->hasAnObject() && getTile(i,j)->isExplored(houseID) && !getTile(i,j)->isFogged(houseID)) {
559                     getTile(i,j)->selectAllPlayersUnits(houseID, &lastCheckedObject, &lastSelectedObject);
560                 }
561             }
562         }
563     }
564 
565     //select an enemy unit if none of your units found
566     if(currentGame->getSelectedList().empty() && (lastCheckedObject != nullptr) && !lastCheckedObject->isSelected()) {
567         lastCheckedObject->setSelected(true);
568         lastSelectedObject = lastCheckedObject;
569         currentGame->getSelectedList().insert(lastCheckedObject->getObjectID());
570         currentGame->selectionChanged();
571     } else if (lastSelectedObject != nullptr) {
572         lastSelectedObject->playSelectSound();  //we only want one unit responding
573     }
574 }
575 
576 
findSpice(Coord & destination,const Coord & origin) const577 bool Map::findSpice(Coord& destination, const Coord& origin) const {
578     bool found = false;
579 
580     int counter = 0;
581     int depth = 1;
582 
583     do {
584         int ranX = 0;
585         int ranY = 0;
586         do {
587             ranX = currentGame->randomGen.rand(origin.x-depth, origin.x + depth);
588             ranY = currentGame->randomGen.rand(origin.y-depth, origin.y + depth);
589         } while(((ranX >= (origin.x+1 - depth)) && (ranX < (origin.x + depth))) && ((ranY >= (origin.y+1 - depth)) && (ranY < (origin.y + depth))));
590 
591         if(tileExists(ranX,ranY) && !getTile(ranX,ranY)->hasAGroundObject() && getTile(ranX,ranY)->hasSpice()) {
592             found = true;
593             destination.x = ranX;
594             destination.y = ranY;
595         }
596 
597         counter++;
598         if(counter >= 100) {
599             //if hasn't found a spot on tempObject layer in 100 tries, goto next
600             counter = 0;
601             depth++;
602         }
603 
604         if(depth > std::max(sizeX, sizeY)) {
605             return false;   //there is possibly no spice left anywhere on map
606         }
607     } while (!found);
608 
609     if((depth > 1) && (getTile(origin)->hasSpice())) {
610         destination = origin;
611     }
612 
613     return true;
614 }
615 
616 /**
617     This method fixes surounding thick spice tiles after spice gone to make things look smooth.
618     \param coord    the coordinate where spice was removed from
619 */
spiceRemoved(const Coord & coord)620 void Map::spiceRemoved(const Coord& coord) {
621 
622     if(tileExists(coord)) { //this is the center tile
623         if(getTile(coord)->getType() == Terrain_Sand) {
624             //thickspice tiles can't handle non-(thick)spice tiles next to them, if this happens after changes, make it non thick
625             for(int i = coord.x-1; i <= coord.x+1; i++) {
626                 for(int j = coord.y-1; j <= coord.y+1; j++) {
627                     if (tileExists(i, j) && (((i==coord.x) && (j!=coord.y)) || ((i!=coord.x) && (j==coord.y))) && getTile(i,j)->isThickSpice()) {
628                         //only check tile right, up, left and down of this one
629                         getTile(i,j)->setType(Terrain_Spice);
630                     }
631                 }
632             }
633         }
634     }
635 }
636 
viewMap(const int playerTeam,const Coord & location,const int maxViewRange)637 void Map::viewMap(const int playerTeam, const Coord& location, const int maxViewRange) {
638 
639 //makes map viewable in an area like as shown below
640 //
641 //                    *
642 //                  *****
643 //                  *****
644 //                 ***T***
645 //                  *****
646 //                  *****
647 //                    *
648 
649 
650     Coord coord;
651     int startY = std::max(0, location.y - maxViewRange);
652     int endY = std::min(sizeY-1, location.y + maxViewRange);
653     for(coord.y = startY; coord.y <= endY; coord.y++) {
654         int startX = std::max(0, location.x - maxViewRange);
655         int endX = std::min(sizeX-1, location.x + maxViewRange);
656         for(coord.x = startX; coord.x <= endX; coord.x++) {
657             if((maxViewRange <= 1) ? (maximumDistance(location, coord) <= maxViewRange) : (blockDistanceApprox(location, coord) <= maxViewRange)) {
658                 for(int i = 0; i < NUM_HOUSES; i++) {
659                     House* pHouse = currentGame->getHouse(i);
660                     if((pHouse != nullptr) && (pHouse->getTeam() == playerTeam)) {
661                         getTile(coord)->setExplored(i,currentGame->getGameCycleCount());
662                     }
663                 }
664             }
665         }
666     }
667 }
668 
669 /**
670     Creates a spice field of the given radius at the given location.
671     \param  location            the location in tile coordinates
672     \param  radius              the radius in tiles (0 = only one tile is filled)
673     \param  centerIsThickSpice  if set the center is filled with thick spice
674 */
createSpiceField(Coord location,int radius,bool centerIsThickSpice)675 void Map::createSpiceField(Coord location, int radius, bool centerIsThickSpice) {
676     Coord offset;
677     for(offset.x = -radius; offset.x <= radius; offset.x++) {
678         for(offset.y = -radius; offset.y <= radius; offset.y++) {
679             if(currentGameMap->tileExists(location + offset)) {
680                 Tile* pTile = currentGameMap->getTile(location + offset);
681 
682                 if(pTile->isSand() && (distanceFrom(location, location + offset) <= radius)) {
683                     if(centerIsThickSpice && (offset.x == 0) && (offset.y == 0)) {
684                         pTile->setType(Terrain_ThickSpice);
685                     } else {
686                         pTile->setType(Terrain_Spice);
687                     }
688                 }
689             }
690         }
691     }
692 }
693