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