Loading [MathJax]/extensions/MathMenu.js
Cytopia  0.3
A city building simulation game
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
MapFunctions.cxx
Go to the documentation of this file.
1 #include "MapFunctions.hxx"
2 #include <PointFunctions.hxx>
3 #include "../../services/Randomizer.hxx"
5 #include <Constants.hxx>
6 #include <Filesystem.hxx>
7 #include <Camera.hxx>
8 #include <ResourcesManager.hxx>
9 #include <MapLayers.hxx>
10 #include <isoMath.hxx>
11 #include <SignalMediator.hxx>
12 
13 #include <set>
14 #include <queue>
15 
17 
18 bool MapFunctions::updateHeight(Point coordinate, const bool elevate)
19 {
20  if (getMapNode(coordinate).changeHeight(elevate))
21  {
22  for (const auto neighbor : PointFunctions::getNeighbors(coordinate, false))
23  {
24  if (getMapNode(neighbor).isLayerOccupied(Layer::ZONE))
25  {
27  }
28  }
29 
30  return true;
31  }
32 
33  return false;
34 }
35 
36 void MapFunctions::changeHeight(const Point &isoCoordinates, const bool elevate)
37 {
38  std::vector<Point> nodesToUpdate{isoCoordinates};
39  std::vector<Point> neighorCoordinates = PointFunctions::getNeighbors(isoCoordinates, true);
40 
41  if (updateHeight(isoCoordinates, elevate))
42  {
43  // If lowering node height, than all nodes around should be lowered to be on same height with the central one.
44  if (!elevate)
45  {
46  const int centerHeight = getMapNode(isoCoordinates).getCoordinates().height;
47 
48  for (const Point &neighborCoord : neighorCoordinates)
49  {
50  MapNode &neighborNode = getMapNode(neighborCoord);
51  if (centerHeight < neighborNode.getCoordinates().height)
52  {
53  neighborNode.changeHeight(false);
54  nodesToUpdate.push_back(neighborNode.getCoordinates());
55  }
56  }
57  }
58  demolishNode(neighorCoordinates);
59  updateNodeNeighbors(nodesToUpdate);
60  }
61 }
62 
63 void MapFunctions::levelHeight(const Point &startCoordinate, const std::vector<Point> levelArea)
64 {
65  const MapNode &startNode = getMapNode(startCoordinate);
66  int initialHeight = startCoordinate.height;
67 
68  /* If the initial node is sloped, check if a majority of it's neighbors,
69  * not counting other sloped nodes are elevated.
70  * If so, the initial node should be regarded as elevated too,
71  * i.e. the height should be incremented.
72  * This seems to yield the most intuitive behavior on slopes.
73  */
74  if (startNode.isSlopeNode())
75  {
76  char directNeighbors =
77  NeighborNodesPosition::LEFT | NeighborNodesPosition::TOP | NeighborNodesPosition::RIGHT | NeighborNodesPosition::BOTTOM;
78  char elBitmask = startNode.getElevationBitmask();
79 
80  int nonSelectedNeighborsCount = 0;
81  int elevatedNonSelectedNeighborsCount = 0;
82 
83  for (Point neighbor : PointFunctions::getNeighbors(startCoordinate, false))
84  {
85  const NeighborNodesPosition neighborPosToOrigin = PointFunctions::getNeighborPositionToOrigin(neighbor, startCoordinate);
86  const bool isSloped = getMapNode(neighbor).isSlopeNode();
87 
88  // Only look at non-sloped direct neighbors not in the selection.
89  if (!isSloped && (neighborPosToOrigin & directNeighbors) > 0 &&
90  std::find(levelArea.begin(), levelArea.end(), neighbor) == levelArea.end())
91  {
92  nonSelectedNeighborsCount++;
93 
94  if ((neighborPosToOrigin & elBitmask) > 0)
95  elevatedNonSelectedNeighborsCount++;
96  }
97  }
98 
99  if (elevatedNonSelectedNeighborsCount * 2 > nonSelectedNeighborsCount)
100  // No need to check max height since there are elevated neighbors.
101  initialHeight++;
102  }
103 
104  std::vector<Point> neighborsToLower;
105 
106  for (const Point &levelPoint : levelArea)
107  {
108  // If a node gets lowered, save all it's neighbors to be lowered aswell.
109  // We have to get the node first, since the coordinates in the area are generated with height=0
110  if (getMapNode(levelPoint).getCoordinates().height > initialHeight)
111  {
112  // This possibly stores nodes that have already been processed for another round.
113  // It's faster than checking if each node is in levelArea first though.
114  std::vector<Point> neighbors = PointFunctions::getNeighbors(levelPoint, false);
115  neighborsToLower.insert(neighborsToLower.end(), neighbors.begin(), neighbors.end());
116  }
117 
118  Point newCoordinates = Point(levelPoint.x, levelPoint.y, levelPoint.z, initialHeight);
119  MapNode &levelNode = getMapNode(levelPoint);
120  levelNode.setCoordinates(newCoordinates);
121  }
122 
123  for (const Point &levelPoint : neighborsToLower)
124  {
125  Point newCoordinates = Point(levelPoint.x, levelPoint.y, levelPoint.z, initialHeight);
126  MapNode &levelNode = getMapNode(levelPoint);
127  levelNode.setCoordinates(newCoordinates);
128  }
129 
130  updateNodeNeighbors(levelArea);
131  updateNodeNeighbors(neighborsToLower);
132 }
133 
134 void MapFunctions::updateNodeNeighbors(const std::vector<Point> &nodes)
135 {
136  // those bitmask combinations require the tile to be elevated.
137  constexpr unsigned char elevateTileComb[] = {
138  NeighborNodesPosition::TOP | NeighborNodesPosition::BOTTOM,
139  NeighborNodesPosition::LEFT | NeighborNodesPosition::RIGHT,
140  NeighborNodesPosition::TOP_LEFT | NeighborNodesPosition::RIGHT | NeighborNodesPosition::BOTTOM,
141  NeighborNodesPosition::TOP_RIGHT | NeighborNodesPosition::LEFT | NeighborNodesPosition::BOTTOM,
142  NeighborNodesPosition::BOTTOM_LEFT | NeighborNodesPosition::RIGHT | NeighborNodesPosition::TOP,
143  NeighborNodesPosition::BOTTOM_RIGHT | NeighborNodesPosition::LEFT | NeighborNodesPosition::TOP};
144 
145  std::vector<Point> nodesToBeUpdated;
146  std::map<int, std::vector<Point>> nodeCache;
147  std::queue<Point> updatedNodes;
148  std::vector<Point> nodesToElevate;
149  std::vector<Point> nodesToDemolish;
150 
151  for (auto currentNode : nodes)
152  {
153  updatedNodes.push(currentNode);
154 
155  while (!updatedNodes.empty() || !nodesToElevate.empty())
156  {
157  while (!updatedNodes.empty())
158  {
159  const Point pHeighChangedNode = getMapNode(updatedNodes.front()).getCoordinates();
160  updatedNodes.pop();
161  const int tileHeight = pHeighChangedNode.height;
162 
163  if (nodeCache.count(pHeighChangedNode.toIndex()) == 0)
164  {
165  nodeCache[pHeighChangedNode.toIndex()] = PointFunctions::getNeighbors(pHeighChangedNode, false);
166  }
167 
168  if (std::find(nodesToElevate.begin(), nodesToElevate.end(), pHeighChangedNode) == nodesToElevate.end())
169  {
170  nodesToElevate.push_back(pHeighChangedNode);
171  }
172 
173  for (auto neighbor : nodeCache[pHeighChangedNode.toIndex()])
174  {
175  // get real coords that include height
176  const Point neighborCoords = getMapNode(neighbor).getCoordinates();
177  const int heightDiff = tileHeight - neighborCoords.height;
178 
179  if (nodeCache.count(neighborCoords.toIndex()) == 0)
180  {
181  nodeCache[neighborCoords.toIndex()] = PointFunctions::getNeighbors(neighborCoords, false);
182  }
183 
184  if (std::find(nodesToElevate.begin(), nodesToElevate.end(), neighborCoords) == nodesToElevate.end())
185  {
186  nodesToElevate.push_back(neighborCoords);
187  }
188 
189  if (std::abs(heightDiff) > 1)
190  {
191  updatedNodes.push(getMapNode(neighborCoords).getCoordinates());
192  updateHeight(neighborCoords, (heightDiff > 1) ? true : false);
193  }
194  }
195  }
196 
197  while (updatedNodes.empty() && !nodesToElevate.empty())
198  {
199  Point nodeToElevate = nodesToElevate.back();
200  nodesToBeUpdated.push_back(nodeToElevate);
201  nodesToElevate.pop_back();
202 
203  if (nodeCache.count(nodeToElevate.toIndex()) == 0)
204  {
205  nodeCache[nodeToElevate.toIndex()] = PointFunctions::getNeighbors(nodeToElevate, false);
206  }
207  const unsigned char elevationBitmask = getElevatedNeighborBitmask(nodeToElevate);
208 
209  if (elevationBitmask != getMapNode(nodeToElevate).getElevationBitmask())
210  {
211  nodesToDemolish.push_back(nodeToElevate);
212  getMapNode(nodeToElevate).setElevationBitMask(elevationBitmask);
213  }
214 
215  for (const auto &elBitMask : elevateTileComb)
216  {
217  if ((elevationBitmask & elBitMask) == elBitMask)
218  {
219  updateHeight(nodeToElevate, true);
220  updatedNodes.push(getMapNode(nodeToElevate).getCoordinates());
221  break;
222  }
223  }
224  }
225  }
226  }
227 
228  if (!nodesToDemolish.empty())
229  {
230  demolishNode(nodesToDemolish);
231  }
232 
233  for (Point node : nodesToBeUpdated)
234  {
236  }
237 
238  for (Point node : nodesToBeUpdated)
239  {
240  getMapNode(node).updateTexture();
241  }
242 }
243 
245 {
246  std::vector<Point> allCoords(m_map->mapNodes.size());
247  std::transform(m_map->mapNodes.begin(), m_map->mapNodes.end(), allCoords.begin(),
248  [](MapNode &mn) { return mn.getCoordinates(); });
249  updateNodeNeighbors(allCoords);
251 }
252 
253 std::vector<NeighborNode> MapFunctions::getNeighborNodes(const Point &isoCoordinates, const bool includeCentralNode)
254 {
255  std::vector<NeighborNode> neighbors;
256 
257  for (auto it : PointFunctions::getNeighbors(isoCoordinates, includeCentralNode))
258  {
259  neighbors.push_back({&getMapNode(it), PointFunctions::getNeighborPositionToOrigin(it, isoCoordinates)});
260  }
261 
262  return neighbors;
263 }
264 
265 bool MapFunctions::isPlacementOnNodeAllowed(const Point &isoCoordinates, const std::string &tileID) const
266 {
267  return m_map->mapNodes[isoCoordinates.toIndex()].isPlacementAllowed(tileID);
268 }
269 
270 bool MapFunctions::isPlacementOnAreaAllowed(const std::vector<Point> &targetCoordinates, const std::string &tileID) const
271 {
272  // This function can be divided into two policies:
273  // Whether we need all nodes in the area to be placed or not
274  bool shouldAllNodesPlaced = true;
275  bool areaPlacementAllowed = true;
276  bool tilePlacementAllowed = true;
277 
278  const Layer layer = TileManager::instance().getTileLayer(tileID);
279  // Only buildings and roads has to be placed on all of the tile selected.
280  // Other layers can be placed on part of tile in the area, such as zone, water, flora.
281  if (layer == BUILDINGS)
282  {
283  shouldAllNodesPlaced = true;
284  }
285  else
286  {
287  shouldAllNodesPlaced = false;
288  }
289  areaPlacementAllowed = shouldAllNodesPlaced;
290 
291  for (auto coord : targetCoordinates)
292  {
293  tilePlacementAllowed = isPlacementOnNodeAllowed(coord, tileID);
294 
295  if (tilePlacementAllowed && !shouldAllNodesPlaced)
296  {
297  areaPlacementAllowed = true;
298  break;
299  }
300  if (!tilePlacementAllowed && shouldAllNodesPlaced)
301  {
302  areaPlacementAllowed = false;
303  break;
304  }
305  }
306 
307  return areaPlacementAllowed;
308 }
309 
310 unsigned char MapFunctions::getElevatedNeighborBitmask(Point centerCoordinates)
311 {
312  unsigned char bitmask = 0;
313  const auto centralNodeHeight = getMapNode(centerCoordinates).getCoordinates().height;
314 
315  for (const auto &neighborCoordinates : PointFunctions::getNeighbors(centerCoordinates, false))
316  {
317  if (getMapNode(neighborCoordinates).getCoordinates().height > centralNodeHeight)
318  {
319  bitmask |= PointFunctions::getNeighborPositionToOrigin(neighborCoordinates, centerCoordinates);
320  }
321  }
322 
323  return bitmask;
324 }
325 
327 {
328  if ((layer != Layer::NONE) && isoCoordinates.isWithinMapBoundaries())
329  {
330  return getMapNode(isoCoordinates).getOrigCornerPoint(layer);
331  }
332 
333  return Point::INVALID();
334 }
335 
336 std::vector<uint8_t> MapFunctions::calculateAutotileBitmask(Point coordinate)
337 {
338  std::vector<uint8_t> tileOrientationBitmask(LAYERS_COUNT, 0);
339 
340  for (auto currentLayer : allLayersOrdered)
341  {
342  auto pCurrentTileData = getMapNode(coordinate).getMapNodeDataForLayer(currentLayer).tileData;
343 
344  if (pCurrentTileData)
345  {
346  if (pCurrentTileData->tileType == +TileType::TERRAIN)
347  {
348  for (const auto &neighbor : getNeighborNodes(coordinate, false))
349  {
350  const auto pTileData = neighbor.pNode->getMapNodeDataForLayer(Layer::WATER).tileData;
351 
352  if (pTileData && pTileData->tileType == +TileType::WATER)
353  {
354  tileOrientationBitmask[currentLayer] |= neighbor.position;
355  }
356  }
357  }
358 
359  // only auto-tile categories that can be tiled.
360  const std::string &nodeTileId = getMapNode(coordinate).getMapNodeDataForLayer(currentLayer).tileID;
361  if (TileManager::instance().isTileIDAutoTile(nodeTileId))
362  {
363  for (const auto &neighbor : getNeighborNodes(coordinate, false))
364  {
365  const MapNodeData &nodeData = neighbor.pNode->getMapNodeDataForLayer(currentLayer);
366 
367  if (nodeData.tileData && ((nodeData.tileID == nodeTileId) || (pCurrentTileData->tileType == +TileType::ROAD)))
368  {
369  tileOrientationBitmask[currentLayer] |= neighbor.position;
370  }
371  }
372  }
373  }
374  }
375  return tileOrientationBitmask;
376 }
377 
378 bool MapFunctions::setTileID(const std::string &tileID, Point coordinate)
379 {
380  TileData *tileData = TileManager::instance().getTileData(tileID);
381  std::vector<Point> targetCoordinates = TileManager::instance().getTargetCoordsOfTileID(coordinate, tileID);
382 
383  if (!tileData || targetCoordinates.empty())
384  { // if the node would not outside of map boundaries, targetCoordinates would be empty
385  return false;
386  }
387 
388  if (!isPlacementOnAreaAllowed(targetCoordinates, tileID))
389  return false;
390 
391  Layer layer = TileManager::instance().getTileLayer(tileID);
392  std::string randomGroundDecorationTileID;
393  std::vector<Point> nodesToBeUpdated;
394 
395  // if this building has groundDeco, grab a random tileID from the list
396  if (!tileData->groundDecoration.empty())
397  {
398  randomGroundDecorationTileID =
399  *Randomizer::instance().choose(tileData->groundDecoration.begin(), tileData->groundDecoration.end());
400  }
401 
402  // for >1x1 buildings, clear all the nodes that are going to be occupied before placing anything.
403  if (targetCoordinates.size() >= 1 && layer == +Layer::BUILDINGS)
404  {
405  demolishNode(targetCoordinates, false, Layer::FLORA); // remove trees under the buildings
406  demolishNode(targetCoordinates, false, Layer::POWERLINES); // remove power lines under buildings
407  }
408 
409  for (auto coord : targetCoordinates)
410  { // now we can place our building
411 
412  MapNode &currentMapNode = getMapNode(coord);
413 
414  if (coord != coordinate && targetCoordinates.size() > 1)
415  { // for buildings >1x1 set every node on the layer that will be occupied to invisible exepct of the origin node
416  currentMapNode.getSprite()->setRenderFlag(layer, false);
417  }
418  else
419  { // 1x1 buildings should be set to visible
420  currentMapNode.getSprite()->setRenderFlag(layer, true);
421  }
422 
423  if ((!targetCoordinates.size()) == 1)
424  { // if it's not a >1x1 building, place tileID on the current coordinate (e.g. ground decoration beneath a > 1x1 building)
425  currentMapNode.setTileID(tileID, coord);
426  }
427  else
428  { // set the tileID for the mapNode of the origin coordinates only on the origin coordinate
429  currentMapNode.setTileID(tileID, coordinate);
430  }
431 
432  // place ground deco if we have one
433  if (!randomGroundDecorationTileID.empty())
434  {
435  currentMapNode.setTileID(randomGroundDecorationTileID, coord);
436  }
437 
438  // For layers that autotile to each other, we need to update their neighbors too
439  if (TileManager::instance().isTileIDAutoTile(tileID))
440  {
441  nodesToBeUpdated.push_back(currentMapNode.getCoordinates());
442  }
443 
444  // emit a signal that setTileID has been called
445  SignalMediator::instance().signalSetTileID.emit(currentMapNode);
446  }
447 
448  if (!nodesToBeUpdated.empty())
449  {
450  updateNodeNeighbors(nodesToBeUpdated);
451  }
452  return true;
453 }
454 
455 bool MapFunctions::setTileID(const std::string &tileID, const std::vector<Point> &coordinates)
456 {
457  bool setTileResult = false;
458  for (auto coord : coordinates)
459  {
460  setTileResult |= setTileID(tileID, coord);
461  }
462  return setTileResult;
463 }
464 
465 void MapFunctions::demolishNode(const std::vector<Point> &isoCoordinates, bool updateNeighboringTiles, Layer layer)
466 {
467  std::vector<Point> nodesToDemolish;
468 
469  for (Point currentCoordinate : isoCoordinates)
470  {
471  if (currentCoordinate.isWithinMapBoundaries())
472  {
473  const MapNode &node = getMapNode(currentCoordinate);
474 
475  // Check for multi-node buildings first. Those are on the buildings layer, even if we want to demolish another layer than Buildings.
476  // In case we add more Layers that support Multi-node, add a for loop here
477  // If demolishNode is called for layer GROUNDECORATION, we'll still need to gather all nodes from the multi-node building to delete the decoration under the entire building
478  auto pNodeTileData = node.getMapNodeDataForLayer(Layer::BUILDINGS).tileData;
479 
480  if (pNodeTileData && ((pNodeTileData->RequiredTiles.height > 1) || (pNodeTileData->RequiredTiles.width > 1)))
481  {
482  const Point origCornerPoint = node.getOrigCornerPoint(Layer::BUILDINGS);
483 
484  if (origCornerPoint.isWithinMapBoundaries())
485  {
486  const std::string &tileID = getMapNode(origCornerPoint).getTileID(Layer::BUILDINGS);
487 
488  // get all the occupied nodes and demolish them
489  for (auto buildingCoords : TileManager::instance().getTargetCoordsOfTileID(origCornerPoint, tileID))
490  {
491  nodesToDemolish.push_back(buildingCoords);
492  }
493  }
494  }
495  // add the current coorindate
496  nodesToDemolish.push_back(currentCoordinate);
497  }
498  }
499 
500  std::vector<Point> updateNodes;
501  for (auto nodeCoordinate : nodesToDemolish)
502  {
503  getMapNode(nodeCoordinate).demolishNode(layer);
504  SignalMediator::instance().signalDemolish.emit(&getMapNode(nodeCoordinate));
505  // TODO: Play sound effect here
506  if (updateNeighboringTiles)
507  {
508  updateNodes.push_back(nodeCoordinate);
509  }
510  }
511 
512  if (!updateNodes.empty())
513  {
514  updateNodeNeighbors(updateNodes);
515  }
516 }
517 
518 void MapFunctions::getNodeInformation(const Point &isoCoordinates) const
519 {
520  const MapNode &mapNode = m_map->mapNodes[isoCoordinates.toIndex()];
521  const MapNodeData &mapNodeData = mapNode.getActiveMapNodeData();
522  const TileData *tileData = mapNodeData.tileData;
523  LOG(LOG_INFO) << "===== TILE at " << isoCoordinates.x << ", " << isoCoordinates.y << ", " << mapNode.getCoordinates().height
524  << "=====";
525  LOG(LOG_INFO) << "[Layer: TERRAIN] ID: " << mapNode.getMapNodeDataForLayer(Layer::TERRAIN).tileID;
526  LOG(LOG_INFO) << "[Layer: WATER] ID: " << mapNode.getMapNodeDataForLayer(Layer::WATER).tileID;
527  LOG(LOG_INFO) << "[Layer: BUILDINGS] ID: " << mapNode.getMapNodeDataForLayer(Layer::BUILDINGS).tileID;
528  LOG(LOG_INFO) << "Category: " << tileData->category;
529  LOG(LOG_INFO) << "FileName: " << tileData->tiles.fileName;
530  LOG(LOG_INFO) << "PickRandomTile: " << tileData->tiles.pickRandomTile;
531  LOG(LOG_INFO) << "TileMap: " << mapNodeData.tileMap;
532  LOG(LOG_INFO) << "TileIndex: " << mapNodeData.tileIndex;
533 }
534 
535 void MapFunctions::highlightNode(const Point &isoCoordinates, const SpriteRGBColor &rgbColor)
536 {
537  if (isoCoordinates.isWithinMapBoundaries())
538  {
539  const auto pSprite = getMapNode(isoCoordinates).getSprite();
540  pSprite->highlightColor = rgbColor;
541  pSprite->highlightSprite = true;
542  }
543 }
544 
545 std::string MapFunctions::getTileID(const Point &isoCoordinates, Layer layer)
546 {
547  return (isoCoordinates.isWithinMapBoundaries()) ? getMapNode(isoCoordinates).getTileID(layer) : "";
548 }
549 
550 void MapFunctions::unHighlightNode(const Point &isoCoordinates)
551 {
552  if (isoCoordinates.isWithinMapBoundaries())
553  {
554  getMapNode(isoCoordinates).getSprite()->highlightSprite = false;
555  }
556 }
557 
558 Point MapFunctions::findNodeInMap(const SDL_Point &screenCoordinates, const Layer &layer)
559 {
560  // calculate clicked column (x coordinate) without height taken into account.
561  const Point calculatedIsoCoords = calculateIsoCoordinates(screenCoordinates);
562  int isoX = calculatedIsoCoords.x;
563  int isoY = calculatedIsoCoords.y;
564 
565  // adjust calculated values that are outside of the map (which is legit, but they need to get pushed down)
566  // only y can be out of bounds on our map
567  if (isoY >= Settings::instance().mapSize)
568  {
569  int diff = isoY - Settings::instance().mapSize; // the diff to reset the value to the edge of the map
570  // travel the column downwards.
571  isoX += diff;
572  isoY -= diff;
573  }
574 
575  // Transverse a column form from calculated coordinates to the bottom of the map.
576  // It is necessary to include 2 neighbor nodes from both sides.
577  // Try to find map node in Z order.
578  // Node with the highest Z order has highest X and lowest Y coordinate, so search will be conducted in that order.
579  const int neighborReach = 2;
580  const int mapSize = Settings::instance().mapSize;
581 
582  // Max X will reach end of the map or Y will reach 0.
583  const int xMax = std::min(isoX + neighborReach + isoY, mapSize - 1);
584  // Min X will reach 0 or x -2 neighbor node.
585  const int xMin = std::max(isoX - neighborReach, 0);
586 
587  for (int x = xMax; x >= xMin; --x)
588  {
589  const int diff = x - isoX;
590  const int yMiddlePoint = isoY - diff;
591 
592  // Move y up and down 2 neighbors.
593  for (int y = std::max(yMiddlePoint - neighborReach, 0); (y <= yMiddlePoint + neighborReach) && (y < mapSize); ++y)
594  {
595  //get all coordinates for node at x,y
596  Point coordinate = getMapNode(Point(x, y)).getCoordinates();
597 
598  if (isClickWithinTile(screenCoordinates, coordinate, layer))
599  {
600  return coordinate;
601  }
602  }
603  }
604 
605  return Point{-1, -1, 0, 0};
606 }
607 
608 bool MapFunctions::isClickWithinTile(const SDL_Point &screenCoordinates, Point isoCoordinate,
609  const Layer &layer = Layer::NONE) const
610 {
611  if (!isoCoordinate.isWithinMapBoundaries())
612  {
613  return false;
614  }
615 
616  const auto &node = m_map->mapNodes[isoCoordinate.toIndex()];
617  auto pSprite = node.getSprite();
618  std::vector<Layer> layersToGoOver;
619 
620  // Layers ordered for hitcheck
621  if (layer == Layer::NONE)
622  {
623  std::vector<Layer> layersOrdered = {Layer::TERRAIN, Layer::WATER, Layer::UNDERGROUND, Layer::BLUEPRINT};
624  layersToGoOver.insert(layersToGoOver.begin(), layersOrdered.begin(), layersOrdered.end());
625  }
626  else
627  {
628  layersToGoOver.push_back(layer);
629  }
630 
631  for (const auto &curLayer : layersToGoOver)
632  {
633  if (!MapLayers::isLayerActive(curLayer))
634  {
635  continue;
636  }
637 
638  SDL_Rect spriteRect = pSprite->getDestRect(curLayer);
639  SDL_Rect clipRect = pSprite->getClipRect(curLayer);
640 
641  if (curLayer == Layer::TERRAIN)
642  {
643  clipRect.h += 1; //HACK: We need to increase clipRect height by one pixel to match the drawRect. Rounding issue?
644  }
645 
646  if (SDL_PointInRect(&screenCoordinates, &spriteRect))
647  {
648  std::string tileID = node.getMapNodeDataForLayer(curLayer).tileID;
649  assert(!tileID.empty());
650 
651  // Calculate the position of the clicked pixel within the surface and "un-zoom" the position to match the un-adjusted surface
652  const int pixelX = static_cast<int>((screenCoordinates.x - spriteRect.x) / Camera::instance().zoomLevel()) + clipRect.x;
653  const int pixelY = static_cast<int>((screenCoordinates.y - spriteRect.y) / Camera::instance().zoomLevel()) + clipRect.y;
654 
655  if ((curLayer == Layer::TERRAIN) && (node.getMapNodeDataForLayer(Layer::TERRAIN).tileMap == TileMap::SHORE))
656  {
657  tileID.append("_shore");
658  }
659 
660  // Check if the clicked Sprite is not transparent (we hit a point within the pixel)
661  if (ResourcesManager::instance().getColorOfPixelInSurface(tileID, pixelX, pixelY).a != SDL_ALPHA_TRANSPARENT)
662  {
663  return true;
664  }
665  }
666  }
667 
668  // Nothing found
669  return false;
670 }
671 void MapFunctions::newMap(bool generateTerrain)
672 {
673  const int mapSize = Settings::instance().mapSize;
674  Map *newMap = new Map(mapSize, mapSize, generateTerrain);
675 
676  if (newMap)
677  {
678  delete m_map;
679  m_map = newMap;
680  updateAllNodes();
681  }
682 }
683 
685 {
687 
688  if (jsonAsString.empty())
689  {
690  throw ConfigurationError(TRACE_INFO "Failed to load savegame file " + fileName);
691  }
692 
693  json saveGameJSON = json::parse(jsonAsString, nullptr, false);
694 
695  if (saveGameJSON.is_discarded())
696  throw ConfigurationError(TRACE_INFO "Could not parse savegame file " + fileName);
697 
698  size_t saveGameVersion = saveGameJSON.value("Savegame version", 0);
699 
700  if (saveGameVersion != SAVEGAME_VERSION)
701  {
702  /* @todo Check savegame version for compatibility and add upgrade functions here later if needed */
703  throw CytopiaError(TRACE_INFO "Trying to load a Savegame with version " + std::to_string(saveGameVersion) +
704  " but only save-games with version " + std::to_string(SAVEGAME_VERSION) + " are supported");
705  }
706 
707  int columns = saveGameJSON.value("columns", -1);
708  int rows = saveGameJSON.value("rows", -1);
709 
710  if (columns == -1 || rows == -1)
711  {
712  throw ConfigurationError(TRACE_INFO "Savegame file " + fileName + "is invalid!");
713  }
714 
715  Map *map = new Map(columns, rows);
716  map->mapNodes.reserve(columns * rows);
717 
718  for (const auto &it : saveGameJSON["mapNode"].items())
719  {
720  Point coordinates = json(it.value())["coordinates"].get<Point>();
721  // set coordinates (height) of the map
722  map->mapNodes.emplace_back(Point{coordinates.x, coordinates.y, coordinates.z, coordinates.height}, "");
723  // load back mapNodeData (tileIDs, Buildins, ...)
724  map->mapNodes.back().setMapNodeData(json(it.value())["mapNodeData"]);
725  }
726 
727  // Now put those newly created nodes in correct drawing order
728  for (int x = 0; x < columns; x++)
729  {
730  for (int y = columns - 1; y >= 0; y--)
731  {
732  map->mapNodesInDrawingOrder.emplace_back(&map->mapNodes[x * columns + y]);
733  }
734  }
735 
736  if (map)
737  {
738  delete m_map;
739  m_map = map;
740  }
741  updateAllNodes();
742  LOG(LOG_DEBUG) << "Load succesfully: " << CYTOPIA_SAVEGAME_DIR + fileName;
743 }
744 
746 {
747  // make sure savegame dir exists
749 
750  //create savegame json string
751  const json j = json{{"Savegame version", SAVEGAME_VERSION},
752  {"columns", m_map->m_columns},
753  {"rows", m_map->m_rows},
754  {"mapNode", m_map->mapNodes}};
755 
756 #ifdef DEBUG
757  // Write uncompressed savegame for easier debugging
758  fs::writeStringToFile(CYTOPIA_SAVEGAME_DIR + fileName + ".txt", j.dump());
759 #endif
761  LOG(LOG_DEBUG) << "Saved succesfully: " << CYTOPIA_SAVEGAME_DIR + fileName;
762 }
763 
765 {
766  if (m_map)
767  {
768  m_map->refresh();
769  }
770 }
CYTOPIA_SAVEGAME_DIR
const std::string CYTOPIA_SAVEGAME_DIR
Definition: Constants.hxx:18
MapFunctions::loadMapFromFile
void loadMapFromFile(const std::string &fileName)
Load Map from file.
Definition: MapFunctions.cxx:684
TRACE_INFO
#define TRACE_INFO
Definition: Exception.hxx:12
ConfigurationError
A configuration error.
Definition: Exception.hxx:36
MapFunctions::getNodeInformation
void getNodeInformation(const Point &isoCoordinates) const
Debug MapNodeData to Console.
Definition: MapFunctions.cxx:518
MapFunctions::getElevatedNeighborBitmask
unsigned char getElevatedNeighborBitmask(Point centerCoordinates)
Get elevated bit mask of the map node.
Definition: MapFunctions.cxx:310
isoMath.hxx
MapNode::getSprite
Sprite * getSprite() const
get Sprite
Definition: MapNode.hxx:54
Sprite::highlightSprite
bool highlightSprite
Definition: Sprite.hxx:55
Randomizer::choose
Iterator choose(Iterator begin, Iterator end)
Pick random item from container.
Definition: Randomizer.hxx:29
WATER
@ WATER
6- Water tiles
Definition: enums.hxx:17
Map::mapNodes
std::vector< MapNode > mapNodes
Definition: Map.hxx:66
MapFunctions::m_map
Map * m_map
Definition: MapFunctions.hxx:168
Point::toIndex
int toIndex() const
calculates the index (stride) that can be used to access in Map to access mapNodes vector.
Definition: Point.hxx:80
LOG
Definition: LOG.hxx:32
MapNode::getMapNodeDataForLayer
const MapNodeData & getMapNodeDataForLayer(Layer layer) const
Definition: MapNode.hxx:97
LOG_INFO
@ LOG_INFO
Definition: LOG.hxx:25
writeStringToFile
void writeStringToFile(const std::string &fileName, const std::string &stringToWrite, bool binaryMode)
Write a string to a file.
Definition: Filesystem.cxx:50
MapFunctions::updateHeight
bool updateHeight(Point coordinate, const bool elevate)
Change map node height.
Definition: MapFunctions.cxx:18
SignalMediator::registerCbSaveGame
void registerCbSaveGame(std::function< void(const std::string &)> const &cb)
Definition: SignalMediator.hxx:37
MapFunctions::demolishNode
void demolishNode(const std::vector< Point > &isoCoordinates, bool updateNeighboringTiles=false, Layer layer=Layer::NONE)
Demolish a node.
Definition: MapFunctions.cxx:465
calculateIsoCoordinates
Point calculateIsoCoordinates(const SDL_Point &screenCoordinates)
Calculates screen space coordinates to isometric space coordinates.
Definition: isoMath.cxx:11
MapFunctions::getNodeOrigCornerPoint
Point getNodeOrigCornerPoint(const Point &isoCoordinates, Layer layer=Layer::NONE)
Get original corner point of given point within building borders.
Definition: MapFunctions.cxx:326
MapNodeData::tileData
TileData * tileData
Definition: MapNode.hxx:20
TileManager::getTargetCoordsOfTileID
std::vector< Point > getTargetCoordsOfTileID(const Point &targetCoordinates, const std::string &tileID)
Return a vector of Points on a target node (origin corner) that would be occupied by a given tileID i...
Definition: TileManager.cxx:46
MapNode::getCoordinates
const Point & getCoordinates() const
get iso coordinates of this node
Definition: MapNode.hxx:59
TileData::category
std::string category
The category this item resides in. Categories are used for the building menu in-game and for sorting ...
Definition: tileData.hxx:140
SignalMediator::signalSetTileID
Signal::Signal< void(const MapNode &)> signalSetTileID
Definition: SignalMediator.hxx:27
POWERLINES
@ POWERLINES
9- Powerlines
Definition: enums.hxx:20
MapFunctions::updateAllNodes
void updateAllNodes()
Update all mapNodes.
Definition: MapFunctions.cxx:244
MapFunctions.hxx
NONE
@ NONE
0- this must be FIRST !!!
Definition: enums.hxx:11
Point::z
int z
The z coordinate.
Definition: Point.hxx:23
MapFunctions::updateNodeNeighbors
void updateNodeNeighbors(const std::vector< Point > &nodes)
Update the nodes and all affected node with the change.
Definition: MapFunctions.cxx:134
TileData::groundDecoration
std::vector< std::string > groundDecoration
tileID of the item that should be drawn on ground below sprite instead of terrain(grass,...
Definition: tileData.hxx:160
Camera.hxx
MapFunctions::refreshVisibleMap
void refreshVisibleMap()
Refresh the visible part of the map.
Definition: MapFunctions.cxx:764
MapFunctions::highlightNode
void highlightNode(const Point &isoCoordinates, const SpriteRGBColor &rgbColor)
Sets a node to be highlighted.
Definition: MapFunctions.cxx:535
MapFunctions::levelHeight
void levelHeight(const Point &startCoordinate, const std::vector< Point > levelArea)
level area of map nodes.
Definition: MapFunctions.cxx:63
LOG_DEBUG
@ LOG_DEBUG
Definition: LOG.hxx:26
MapNode::demolishLayer
void demolishLayer(const Layer &layer)
demolish specific layer of a Node.
Definition: MapNode.cxx:454
Point::y
int y
The y coordinate.
Definition: Point.hxx:20
MapFunctions::isPlacementOnNodeAllowed
bool isPlacementOnNodeAllowed(const Point &isoCoordinates, const std::string &tileID) const
check if Tile is occupied
Definition: MapFunctions.cxx:265
MapNode::updateTexture
void updateTexture(const Layer &layer=Layer::NONE)
Update texture.
Definition: MapNode.cxx:253
SettingsData::mapSize
int mapSize
the size of the map
Definition: Settings.hxx:34
MapNode::demolishNode
void demolishNode(const Layer &layer=Layer::NONE)
Demolish a node.
Definition: MapNode.cxx:465
Map::m_rows
int m_rows
Definition: Map.hxx:71
Point::x
int x
The x coordinate.
Definition: Point.hxx:14
MapFunctions::MapFunctions
MapFunctions()
Definition: MapFunctions.cxx:16
TileData::tiles
TileSetData tiles
Tile Spritesheet information.
Definition: tileData.hxx:148
MapFunctions::isPlacementOnAreaAllowed
bool isPlacementOnAreaAllowed(const std::vector< Point > &targetCoordinates, const std::string &tileID) const
check if Tile can be placed in an area
Definition: MapFunctions.cxx:270
MapFunctions::getNeighborNodes
std::vector< NeighborNode > getNeighborNodes(const Point &isoCoordinates, const bool includeCentralNode)
Get all neighbor nodes from provided map node.
Definition: MapFunctions.cxx:253
MapNode::isSlopeNode
bool isSlopeNode(void) const
check if current Node Terrain is Slope Terrain.
Definition: MapNode.cxx:430
MapNode
Class that holds map nodes.
Definition: MapNode.hxx:30
Map::refresh
void refresh()
Refresh all the map tile textures.
Definition: Map.cxx:114
MapFunctions::getTileID
std::string getTileID(const Point &isoCoordinates, Layer layer)
get Tile ID of specific layer of specific iso coordinates
Definition: MapFunctions.cxx:545
ZONE
@ ZONE
4- Optional layer, zones(Industrial/Residential/Commercial).
Definition: enums.hxx:15
BLUEPRINT
@ BLUEPRINT
1- Optional layer - Map Blueprint
Definition: enums.hxx:12
MapNode::getElevationBitmask
unsigned char getElevationBitmask() const
Definition: MapNode.hxx:82
SHORE
@ SHORE
Definition: TileManager.hxx:20
Sprite::setRenderFlag
void setRenderFlag(const Layer &layer, bool render)
enable/disable rendering for this sprite on a specific layer
Definition: Sprite.hxx:52
BUILDINGS
@ BUILDINGS
8- Buildings, Streets and everything that goes on the terrain
Definition: enums.hxx:19
TileManager::getTileLayer
Layer getTileLayer(const std::string &tileID) const
Get the Layer that is associated with a tileID. The Tile will be placed on this layer.
Definition: TileManager.cxx:104
Point::INVALID
static constexpr Point INVALID()
Definition: Point.hxx:35
Point::isWithinMapBoundaries
bool isWithinMapBoundaries() const
Definition: Point.hxx:37
MapFunctions::unHighlightNode
void unHighlightNode(const Point &isoCoordinates)
Sets a node to be unhighlighred.
Definition: MapFunctions.cxx:550
Camera::zoomLevel
const double & zoomLevel() const noexcept
Definition: Camera.cxx:97
PointFunctions::getNeighbors
static std::vector< Point > getNeighbors(const Point &isoCoordinates, const bool includeCentralNode, int distance=1)
Get all neighboring coordinates from provided map node isocoordinate.
Definition: PointFunctions.cxx:175
ResourcesManager.hxx
MapNode::getOrigCornerPoint
const Point & getOrigCornerPoint(Layer layer) const
Get the Origin Corner Point of a multitile building.
Definition: MapNode.hxx:136
Filesystem.hxx
PointFunctions.hxx
Map::m_columns
int m_columns
Definition: Map.hxx:70
MapNode::setElevationBitMask
void setElevationBitMask(const unsigned char bitMask)
Set elevation bit mask.
Definition: MapNode.hxx:160
FLORA
@ FLORA
10- Trees and other flora/Fauna tiles
Definition: enums.hxx:21
PointFunctions::getNeighborPositionToOrigin
static NeighborNodesPosition getNeighborPositionToOrigin(const Point &neighboringPoint, const Point &originPoint)
Get the position of the neighboring node to the originpoint (center of the neighborgroup).
Definition: PointFunctions.cxx:202
TileManager::getTileData
TileData * getTileData(const std::string &id) noexcept
Get the TileData struct for this tileID and all information associated with it.
Definition: TileManager.cxx:22
createDirectory
void createDirectory(const std::string &dir)
Definition: Filesystem.cxx:69
MapFunctions::newMap
void newMap(bool generateTerrain=true)
Creates a new map object with terrain gen.
Definition: MapFunctions.cxx:671
SpriteRGBColor
Definition: Sprite.hxx:18
ResourcesManager::getColorOfPixelInSurface
SDL_Color getColorOfPixelInSurface(const std::string &tileID, int x, int y)
Get the Color Of Pixel In Surface object at a given coordinate.
Definition: ResourcesManager.cxx:126
Sprite::highlightColor
SpriteRGBColor highlightColor
Definition: Sprite.hxx:56
MapLayers::isLayerActive
static bool isLayerActive(unsigned int layer)
Check if given Layer is being drawn.
Definition: MapLayers.hxx:34
MapLayers.hxx
TERRAIN
@ TERRAIN
3- Terrain tiles, decorations, ... - must always be a "full" tile
Definition: enums.hxx:14
Map::mapNodesInDrawingOrder
std::vector< MapNode * > mapNodesInDrawingOrder
Definition: Map.hxx:67
MapNodeData::tileMap
TileMap tileMap
Definition: MapNode.hxx:23
Point::height
int height
The height level.
Definition: Point.hxx:26
MapNodeData::tileIndex
int32_t tileIndex
Definition: MapNode.hxx:21
MapFunctions::findNodeInMap
Point findNodeInMap(const SDL_Point &screenCoordinates, const Layer &layer=Layer::NONE)
Returns the node at given screen coordinates.
Definition: MapFunctions.cxx:558
ROAD
@ ROAD
5- Optional layer, roads.
Definition: enums.hxx:16
MapNode::getTileID
const std::string & getTileID(Layer layer) const
get TileID of specific layer inside NodeData.
Definition: MapNode.hxx:89
Signal::slot
std::function< R(Args...)> slot(instance &object, R(Class::*method)(Args...))
This function creates a std::function by binding object to the member function pointer method.
Definition: Signal.hxx:167
JsonSerialization.hxx
SignalMediator::signalDemolish
Signal::Signal< void(MapNode *)> signalDemolish
Definition: SignalMediator.hxx:28
MapFunctions::calculateAutotileBitmask
std::vector< uint8_t > calculateAutotileBitmask(Point coordinate)
Get a bitmask that represents same-tile neighbors.
Definition: MapFunctions.cxx:336
MapFunctions::saveMapToFile
void saveMapToFile(const std::string &fileName)
Save Map to file.
Definition: MapFunctions.cxx:745
MapFunctions::getMapNode
MapNode & getMapNode(Point isoCoords)
Get pointer to a single mapNode at specific iso coordinates.
Definition: MapFunctions.hxx:40
SignalMediator.hxx
Map
Definition: Map.hxx:23
Point
Definition: Point.hxx:7
MapNodeData::tileID
std::string tileID
Definition: MapNode.hxx:19
allLayersOrdered
static Layer allLayersOrdered[]
This is a ordered list of all relevant layers we need to interact with.
Definition: enums.hxx:29
MapFunctions::isClickWithinTile
bool isClickWithinTile(const SDL_Point &screenCoordinates, Point isoCoordinate, const Layer &layer) const
Check if a click is within a non transparent part of a sprite.
Definition: MapFunctions.cxx:608
Singleton< SignalMediator >::instance
static SignalMediator & instance(void)
Get an instance of the singleton.
Definition: Singleton.hxx:15
LAYERS_COUNT
@ LAYERS_COUNT
this must be LAST !!!
Definition: enums.hxx:25
MapNode::changeHeight
bool changeHeight(const bool higher)
Change Height.
Definition: MapNode.cxx:26
MapFunctions::setTileID
bool setTileID(const std::string &tileID, Point coordinate)
Set the Tile ID Of Node object.
Definition: MapFunctions.cxx:378
Layer
Layer
All Layers we have.
Definition: enums.hxx:9
json
nlohmann::json json
Definition: Settings.hxx:12
string
std::string string
Definition: AudioConfig.hxx:14
MapNode::setAutotileBitMask
void setAutotileBitMask(std::vector< unsigned char > &&bitMask)
Set autotile bit mask.
Definition: MapNode.hxx:165
UNDERGROUND
@ UNDERGROUND
2- Optional layer - Pipes, Subway-pipes and so onn
Definition: enums.hxx:13
MapNode::getActiveMapNodeData
const MapNodeData & getActiveMapNodeData() const
Definition: MapNode.cxx:438
Constants.hxx
MapFunctions::changeHeight
void changeHeight(const Point &isoCoordinates, const bool elevate)
Change map node height.
Definition: MapFunctions.cxx:36
MapNodeData
Definition: MapNode.hxx:17
TileData
Holds all releavted information to this specific tile.
Definition: tileData.hxx:135
MapNode::setTileID
void setTileID(const std::string &tileType, const Point &origPoint)
Definition: MapNode.cxx:50
SAVEGAME_VERSION
constexpr const unsigned int SAVEGAME_VERSION
Definition: Constants.hxx:24
CytopiaError
A generic error in Cytopia.
Definition: Exception.hxx:28
MapNode::setCoordinates
void setCoordinates(const Point &newIsoCoordinates)
sets the iso coordinates of this node
Definition: MapNode.cxx:432
readCompressedFileAsString
std::string readCompressedFileAsString(const std::string &fileName)
Read contents from a file as string.
Definition: Filesystem.cxx:71
writeStringToFileCompressed
void writeStringToFileCompressed(const std::string &fileName, const std::string &stringToWrite)
Write a string to a file and compress it.
Definition: Filesystem.cxx:64