Rehti MMORPG  1.0.0
Rehti MMORPG is a free and open source MMORPG game.
MapLoader.hpp
Go to the documentation of this file.
1 #pragma once
2 
3 #include "rapidjson/document.h"
4 #include "rapidjson/prettywriter.h"
5 #include <exception>
6 #include <filesystem>
7 #include <stdlib.h>
8 
9 #include "RehtiUtils.hpp"
10 #include "Utils.hpp"
11 
12 // Object tile map symbols
13 const std::string OBJECT_TILE_MAP_CENTER = "X";
14 const std::string OBJECT_TILE_MAP_NORTH_BLOCK = "N";
15 const std::string OBJECT_TILE_MAP_EAST_BLOCK = "E";
16 const std::string OBJECT_TILE_MAP_SOUTH_BLOCK = "S";
17 const std::string OBJECT_TILE_MAP_WEST_BLOCK = "W";
18 const std::string OBJECT_TILE_MAP_FULL_BLOCK = "B";
19 const std::string OBJECT_TILE_MAP_NO_BLOCK = " ";
20 
21 const unsigned NON_OBJECT_ID = 255 * 255;
22 
28 static const std::vector<std::vector<std::string>> fetchAreaMap()
29 {
30  rapidjson::Document doc = readJson(Config.AREA_MAP_PATH);
31 
32  if (!doc.IsArray())
33  {
34  throw std::runtime_error("JSON is not an array");
35  }
36 
37  std::vector<std::vector<std::string>> areaMap;
38 
39  for (rapidjson::SizeType i = 0; i < doc.Size(); ++i)
40  {
41  if (!doc[i].IsArray())
42  {
43  throw std::runtime_error("Inner structure is not an array");
44  }
45 
46  std::vector<std::string> areaRow;
47  for (rapidjson::SizeType j = 0; j < doc[i].Size(); ++j)
48  {
49  if (!doc[i][j].IsString())
50  {
51  throw std::runtime_error("Element is not a string");
52  }
53 
54  areaRow.push_back(doc[i][j].GetString());
55  }
56  areaMap.push_back(areaRow);
57  }
58 
59  return areaMap;
60 }
61 
77 static void loadHeightAndTextureMap(const std::vector<std::vector<std::string>>& areaMap, std::vector<std::vector<int>>& heightMap, std::vector<std::vector<int>>& textureMap)
78 {
79  // Populate the height map with 0s.
80  populateMatrix(heightMap, areaMap, 0, Config.AREA_WIDTH, Config.AREA_HEIGHT);
81  populateMatrix(textureMap, areaMap, 0, Config.AREA_WIDTH, Config.AREA_HEIGHT);
82 
83  // Loop through the area map
84  for (unsigned currentAreaRowIndex = 0; currentAreaRowIndex < areaMap.size(); currentAreaRowIndex++)
85  {
86  std::vector<std::string> areaRow = areaMap[currentAreaRowIndex];
87 
88  // For each area read the image file and check the height of each pixel.
89  for (unsigned currentAreaColumnIndex = 0; currentAreaColumnIndex < areaRow.size(); currentAreaColumnIndex++)
90  {
91  const std::string& area = areaRow[currentAreaColumnIndex];
92  std::string areaFile = Config.AREA_FILES_PATH + area + ".png";
93  std::vector<unsigned char> image; // Represents image pixel map. "RGBARGBARGBA..."
94  unsigned width, height;
95  readPng(image, width, height, areaFile);
96  if (width != Config.AREA_WIDTH || height != Config.AREA_HEIGHT)
97  {
98  throw std::invalid_argument("Image size is not " + std::to_string(Config.AREA_WIDTH) + "x" + std::to_string(Config.AREA_HEIGHT));
99  }
100 
101  for (unsigned i = 0; i < height; ++i)
102  {
103  const unsigned indexY = currentAreaRowIndex * Config.AREA_HEIGHT + i;
104  for (unsigned j = 0; j < width; ++j)
105  {
106  unsigned pixelIndex = (i * width + j) * 4;
107  unsigned char r = image[pixelIndex + 0];
108  unsigned char g = image[pixelIndex + 1];
109  unsigned char b = image[pixelIndex + 2];
110 
111  // The height is calculated by G * B. But the Green values first bit is a sign bit.
112  int height = (g & 0x7F) * b;
113  if (g & 0x80)
114  {
115  height *= -1;
116  }
117 
118  const unsigned indexX = currentAreaColumnIndex * Config.AREA_WIDTH + j;
119  textureMap[indexY][indexX] = r;
120  heightMap[indexY][indexX] = height;
121  }
122  }
123  }
124  }
125 }
126 
140 static void insertObjectTileMap(std::vector<std::vector<std::string>>& objectBlockMap, std::vector<std::vector<std::string>>& objectTileMap, unsigned areaY, unsigned areaX, unsigned currentAreaRowIndex, unsigned currentAreaColumnIndex)
141 {
142  // First find the object's center tile
143  unsigned centerX, centerY;
144  unsigned centerXMap, centerYMap;
145  bool shouldBreak = false;
146  for (unsigned i = 0; i < objectTileMap.size(); i++)
147  {
148  for (unsigned j = 0; j < objectTileMap[i].size(); j++)
149  {
150  if (objectTileMap[i][j].find(OBJECT_TILE_MAP_CENTER) != std::string::npos)
151  {
152  // Set the center tile's position in the object tile map
153  centerX = j;
154  centerY = i;
155  // Calculate the center tile's position in the object block map
156  centerXMap = currentAreaColumnIndex * Config.AREA_WIDTH + areaX + j;
157  centerYMap = currentAreaRowIndex * Config.AREA_HEIGHT + areaY + i;
158  shouldBreak = true;
159  break;
160  }
161  }
162 
163  if (shouldBreak)
164  {
165  break;
166  }
167  }
168 
169  // Insert the object tile map to the object block map
170  for (unsigned i = 0; i < objectTileMap.size(); i++)
171  {
172  for (unsigned j = 0; j < objectTileMap[i].size(); j++)
173  {
174  const int xDiff = j - centerX;
175  const int yDiff = i - centerY;
176  const int x = centerXMap + xDiff;
177  const int y = centerYMap + yDiff;
178 
179  // Check if object is out of bounds (or it's tiles are out of bounds )
180  if (x < 0 || x >= objectBlockMap[0].size() || y < 0 || y >= objectBlockMap.size())
181  {
182  throw std::out_of_range("Object out of bounds");
183  }
184 
185  objectBlockMap[y][x] = objectTileMap[i][j];
186  }
187  }
188 }
189 
200 static void changeBlockDirection(std::vector<std::vector<std::string>>& objectTileMap, unsigned rotation)
201 {
203 
204  for (unsigned i = 0; i < objectTileMap.size(); i++)
205  {
206  for (unsigned j = 0; j < objectTileMap[i].size(); j++)
207  {
208  for (unsigned k = 0; k < symbols.size(); k++)
209  {
210  const auto index = objectTileMap[i][j].find(symbols[k]);
211  if (index != std::string::npos)
212  {
213  const unsigned newSymbolIndex = (k + rotation) % symbols.size();
214  objectTileMap[i][j].replace(index, symbols[k].size(), symbols[newSymbolIndex]);
215  break;
216  }
217  }
218  }
219  }
220 }
221 
240 static const std::vector<std::vector<std::string>> createObjectBlockMap(const std::vector<std::vector<std::string>>& areaMap, GameObjects gameObjects, const std::vector<std::vector<int>>& heightMap)
241 {
242  // Populate the object block map with non-blocked tiles
243  std::vector<std::vector<std::string>> objectBlockMap;
244  populateMatrix(objectBlockMap, areaMap, OBJECT_TILE_MAP_NO_BLOCK, Config.AREA_WIDTH, Config.AREA_HEIGHT);
245 
246  std::vector<ObjectLocation> objectsLocations;
247 
248  for (unsigned currentAreaRowIndex = 0; currentAreaRowIndex < areaMap.size(); currentAreaRowIndex++)
249  {
250  const std::vector<std::string> areaRow = areaMap[currentAreaRowIndex];
251  for (unsigned currentAreaColumnIndex = 0; currentAreaColumnIndex < areaRow.size(); currentAreaColumnIndex++)
252  {
253  const std::string& area = areaRow[currentAreaColumnIndex];
254  // Read the object tile map for the area.
255  std::vector<unsigned char> image; // Represents image pixel map. "RGBARGBARGBA..."
256  unsigned width, height;
257  std::string filepath = Config.AREA_FILES_PATH + area + "-obj.png";
258  readPng(image, width, height, filepath);
259  if (width != Config.AREA_WIDTH || height != Config.AREA_HEIGHT)
260  {
261  throw std::invalid_argument("Image size is not " + std::to_string(Config.AREA_WIDTH) + "x" + std::to_string(Config.AREA_HEIGHT));
262  }
263 
264  // Loop through the image and find all objects
265  for (unsigned i = 0; i < height; ++i)
266  {
267  for (unsigned j = 0; j < width; ++j)
268  {
269  unsigned pixelIndex = (i * width + j) * 4;
270  unsigned r = image[pixelIndex + 0];
271  unsigned g = image[pixelIndex + 1];
272  unsigned b = image[pixelIndex + 2];
273 
274  unsigned objectId = r * g;
275 
276  if (objectId == NON_OBJECT_ID)
277  {
278  continue;
279  }
280 
281  if (objectId)
282  {
283  unsigned rotation = b > 3 ? 0 : b; // Rotation is stored in the B-value. If B is 4 or more, the object is not rotated from its original position.
284 
285  std::vector<std::vector<std::string>> objectTileMap = gameObjects.getTileMap(objectId);
286 
287  if (objectTileMap.empty())
288  {
289  throw std::runtime_error("Object tile map not found for id: " + std::to_string(objectId));
290  }
291 
292  std::cout << "Object id: " << objectId << " read " << std::endl;
293 
294  if (rotation)
295  {
296  // Rotate the object tile map
297  rotateMatrix(objectTileMap, rotation);
298  changeBlockDirection(objectTileMap, rotation);
299  std::cout << "Object id: " << objectId << " rotated " << std::endl;
300  }
301  else
302  {
303  std::cout << "Object id: " << objectId << " not rotated " << std::endl;
304  }
305 
306  int x = currentAreaColumnIndex * Config.AREA_WIDTH + j;
307  int y = currentAreaRowIndex * Config.AREA_HEIGHT + i;
308  int z = heightMap[y][x];
309  ObjectLocation objLoc = {objectId, "0", x, y, z, rotation};
310  objLoc.instanceId = generateObjectInstanceId(objLoc);
311  objectsLocations.push_back(objLoc);
312 
313  insertObjectTileMap(objectBlockMap, objectTileMap, i, j, currentAreaRowIndex, currentAreaColumnIndex);
314  std::cout << "Object id: " << objectId << " inserted " << std::endl;
315  }
316  }
317  }
318  }
319  }
320 
321  // Write the object locations to a file
322  rapidjson::Document doc;
323  doc.SetObject();
324  rapidjson::Value objects(rapidjson::kArrayType);
325  for (const auto& objLoc : objectsLocations)
326  {
327  rapidjson::Value object(rapidjson::kObjectType);
328  object.AddMember("id", objLoc.id, doc.GetAllocator());
329  object.AddMember("x", objLoc.x, doc.GetAllocator());
330  object.AddMember("y", objLoc.y, doc.GetAllocator());
331  object.AddMember("z", objLoc.z, doc.GetAllocator());
332  object.AddMember("rotation", objLoc.rotation, doc.GetAllocator());
333  objects.PushBack(object, doc.GetAllocator());
334  }
335  doc.AddMember("objects", objects, doc.GetAllocator());
336 
337  const std::string str = createString(doc);
338 
339  std::ofstream objectsFile(Config.GENERATED_OBJECT_JSON_PATH);
340  objectsFile << str;
341  objectsFile.close();
342 
343  return objectBlockMap;
344 }
345 
368 static const std::vector<std::vector<unsigned>> generateAccessMap(const std::vector<std::vector<int>>& heightMap, const std::vector<std::vector<std::string>>& objectBlockMap)
369 {
370  std::vector<std::vector<unsigned>> accessMap;
371 
372  // Populate the access map with 1111s, meaning tiles are accessible from all directions.
373  const unsigned defaultValue = 0b1111;
374  populateMatrixFromReference(accessMap, heightMap, defaultValue);
375 
376  for (int i = 0; i < heightMap.size(); i++)
377  {
378  for (int j = 0; j < heightMap[i].size(); j++)
379  {
380  int height = heightMap[i][j];
381  unsigned& access = accessMap[i][j];
382 
383  // Check North
384  if (i <= 0 || heightMap[i - 1].size() <= j || std::abs(height - heightMap[i - 1][j]) > Config.MAX_PASSABLE_HEIGHT || objectBlockMap[i][j].find("N") != std::string::npos)
385  {
386  // Block the north bit
387  access = access & 0b1110;
388  }
389 
390  // Check East
391  if (heightMap[i].size() <= j + 1 || std::abs(height - heightMap[i][j + 1]) > Config.MAX_PASSABLE_HEIGHT || objectBlockMap[i][j].find("E") != std::string::npos)
392  {
393  // Block the east bit
394  access = access & 0b1101;
395  }
396 
397  // Check South
398  if (heightMap.size() <= i + 1 || heightMap[i + 1].size() <= j || std::abs(height - heightMap[i + 1][j]) > Config.MAX_PASSABLE_HEIGHT || objectBlockMap[i][j].find("S") != std::string::npos)
399  {
400  // Block the south bit
401  access = access & 0b1011;
402  }
403 
404  // Check West
405  if (j <= 0 || std::abs(height - heightMap[i][j - 1]) > Config.MAX_PASSABLE_HEIGHT || objectBlockMap[i][j].find("W") != std::string::npos)
406  {
407  // Block the west bit
408  access = access & 0b0111;
409  }
410 
411  // Check if the tile is completely blocked
412  if (objectBlockMap[i][j].find("B") != std::string::npos)
413  {
414  access = 0;
415  }
416  }
417  }
418 
419  return accessMap;
420 }
const std::string OBJECT_TILE_MAP_EAST_BLOCK
Definition: MapLoader.hpp:15
const std::string OBJECT_TILE_MAP_NO_BLOCK
Definition: MapLoader.hpp:19
const unsigned NON_OBJECT_ID
Definition: MapLoader.hpp:21
static const std::vector< std::vector< std::string > > fetchAreaMap()
Fetches the area map from the JSON file. Throws an exception if the file corrupted.
Definition: MapLoader.hpp:28
const std::string OBJECT_TILE_MAP_SOUTH_BLOCK
Definition: MapLoader.hpp:16
static void loadHeightAndTextureMap(const std::vector< std::vector< std::string >> &areaMap, std::vector< std::vector< int >> &heightMap, std::vector< std::vector< int >> &textureMap)
Loads a height map and map's texture map into given parameters. Loads them together to avoid reading ...
Definition: MapLoader.hpp:77
static const std::vector< std::vector< unsigned > > generateAccessMap(const std::vector< std::vector< int >> &heightMap, const std::vector< std::vector< std::string >> &objectBlockMap)
Generates access map from height map & object block map. Access map defines how tiles can or cannot b...
Definition: MapLoader.hpp:368
const std::string OBJECT_TILE_MAP_NORTH_BLOCK
Definition: MapLoader.hpp:14
const std::string OBJECT_TILE_MAP_WEST_BLOCK
Definition: MapLoader.hpp:17
static void changeBlockDirection(std::vector< std::vector< std::string >> &objectTileMap, unsigned rotation)
When object tile map is rotated, its direction needs to be rotated as well.
Definition: MapLoader.hpp:200
static const std::vector< std::vector< std::string > > createObjectBlockMap(const std::vector< std::vector< std::string >> &areaMap, GameObjects gameObjects, const std::vector< std::vector< int >> &heightMap)
Generate the object block map. The map defines how the objects block the tiles around itself.
Definition: MapLoader.hpp:240
static void insertObjectTileMap(std::vector< std::vector< std::string >> &objectBlockMap, std::vector< std::vector< std::string >> &objectTileMap, unsigned areaY, unsigned areaX, unsigned currentAreaRowIndex, unsigned currentAreaColumnIndex)
Inserts the object tile map to the map.
Definition: MapLoader.hpp:140
const std::string OBJECT_TILE_MAP_CENTER
Definition: MapLoader.hpp:13
const std::string OBJECT_TILE_MAP_FULL_BLOCK
Definition: MapLoader.hpp:18
std::string generateObjectInstanceId(ObjectLocation objectLocation)
Generates a unique id for an object instance.
Definition: ObjectReader.cpp:16
void rotateMatrix(std::vector< std::vector< T >> &matrix, uint8_t rotation)
Rotates the matrix 90 degrees counter-clockwise. Rotation is calculated by rotation-param * 90 degree...
Definition: rehtiLib/assets/loader/src/Utils.hpp:61
void readPng(std::vector< unsigned char > &image, unsigned &width, unsigned &height, std::string filepath)
Reads a PNG file and returns the image data, width and height.
Definition: rehtiLib/assets/loader/src/Utils.hpp:42
void populateMatrixFromReference(std::vector< std::vector< T >> &matrix, const std::vector< std::vector< K >> &referenceMatrix, T defaultValue)
Populates a matrix from a reference matrix. Uses a default value for empty cells. Reference matrix is...
Definition: rehtiLib/assets/loader/src/Utils.hpp:116
void populateMatrix(std::vector< std::vector< T >> &matrix, const std::vector< std::vector< std::string >> &areaMap, T defaultValue, unsigned AREA_WIDTH, unsigned AREA_HEIGHT)
Populates a matrix with a default value. The matrix is all the areas combined and expanded e....
Definition: rehtiLib/assets/loader/src/Utils.hpp:96
Contains all the objects defined in the objects.json file.
Definition: ObjectReader.hpp:101
std::vector< std::vector< std::string > > getTileMap(int id)
Definition: ObjectReader.hpp:133
Describes the location of an object on the map. This is used to spawn object instances on the map.
Definition: ObjectReader.hpp:17
std::string instanceId
Definition: ObjectReader.hpp:19