1 /***************************************************************************
2 * Mechanized Assault and Exploration Reloaded Projectfile *
3 * *
4 * This program 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 * This program 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 this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
18 ***************************************************************************/
19
20 #include "game/data/map/map.h"
21
22 #include "game/data/units/building.h"
23 #include "utility/listhelpers.h"
24 #include "utility/files.h"
25 #include "utility/log.h"
26 #include "game/data/player/player.h"
27 #include "settings.h"
28 #include "game/data/units/vehicle.h"
29 #include "video.h"
30 #include "utility/position.h"
31 #include "utility/random.h"
32
33 #if 1 // TODO: [SDL2]: SDL_SetColors
SDL_SetColors(SDL_Surface * surface,SDL_Color * colors,int index,int size)34 inline void SDL_SetColors (SDL_Surface* surface, SDL_Color* colors, int index, int size)
35 {
36 SDL_SetPaletteColors (surface->format->palette, colors, index, size);
37 }
38 #endif
39
40
sTerrain()41 sTerrain::sTerrain() :
42 water (false),
43 coast (false),
44 blocked (false)
45 {}
46
sTerrain(sTerrain && other)47 sTerrain::sTerrain(sTerrain&& other) :
48 sf(std::move(other.sf)),
49 sf_org(std::move(other.sf_org)),
50 shw(std::move(other.shw)),
51 shw_org(std::move(other.shw_org)),
52 water(other.water),
53 coast(other.coast),
54 blocked(other.blocked)
55 {}
56
operator =(sTerrain && other)57 sTerrain& sTerrain::operator=(sTerrain&& other)
58 {
59 sf = std::move(other.sf);
60 sf_org = std::move(other.sf_org);
61 shw = std::move(other.shw);
62 shw_org = std::move(other.shw_org);
63 water = other.water;
64 coast = other.coast;
65 blocked = other.blocked;
66
67 return *this;
68 }
69
cMapField()70 cMapField::cMapField()
71 {}
72
getVehicle() const73 cVehicle* cMapField::getVehicle() const
74 {
75 if (vehicles.empty()) return nullptr;
76 return vehicles[0];
77 }
78
getPlane() const79 cVehicle* cMapField::getPlane() const
80 {
81 if (planes.empty()) return nullptr;
82 return planes[0];
83 }
84
getBuildings() const85 const std::vector<cBuilding*>& cMapField::getBuildings() const
86 {
87 return buildings;
88 }
89
getVehicles() const90 const std::vector<cVehicle*>& cMapField::getVehicles() const
91 {
92 return vehicles;
93 }
94
getPlanes() const95 const std::vector<cVehicle*>& cMapField::getPlanes() const
96 {
97 return planes;
98 }
99
getUnits(std::vector<cUnit * > & units) const100 void cMapField::getUnits (std::vector<cUnit*>& units) const
101 {
102 units.clear();
103 units.reserve (vehicles.size() + buildings.size() + planes.size());
104 units.insert (units.end(), vehicles.begin(), vehicles.end());
105 units.insert (units.end(), buildings.begin(), buildings.end());
106 units.insert (units.end(), planes.begin(), planes.end());
107 }
108
getBuilding() const109 cBuilding* cMapField::getBuilding() const
110 {
111 if (buildings.empty()) return nullptr;
112 return buildings[0];
113 }
114
getTopBuilding() const115 cBuilding* cMapField::getTopBuilding() const
116 {
117 if (buildings.empty()) return nullptr;
118 cBuilding* building = buildings[0];
119
120 if ((building->data.surfacePosition == sUnitData::SURFACE_POS_GROUND ||
121 building->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE) &&
122 building->getOwner())
123 return building;
124 return nullptr;
125 }
126
getBaseBuilding() const127 cBuilding* cMapField::getBaseBuilding() const
128 {
129 for (cBuilding* building : buildings)
130 {
131 if (building->data.surfacePosition != sUnitData::SURFACE_POS_GROUND &&
132 building->data.surfacePosition != sUnitData::SURFACE_POS_ABOVE &&
133 building->getOwner())
134 {
135 return building;
136 }
137 }
138 return nullptr;
139 }
140
getRubble() const141 cBuilding* cMapField::getRubble() const
142 {
143 for (cBuilding* building : buildings)
144 if (!building->getOwner())
145 return building;
146 return nullptr;
147 }
148
getMine() const149 cBuilding* cMapField::getMine() const
150 {
151 for (cBuilding* building : buildings)
152 if (building->data.explodesOnContact)
153 return building;
154 return nullptr;
155 }
156
addBuilding(cBuilding & building,size_t index)157 void cMapField::addBuilding (cBuilding& building, size_t index)
158 {
159 assert (index <= buildings.size());
160 buildings.insert (buildings.begin() + index, &building);
161
162 buildingsChanged();
163 unitsChanged();
164 }
addVehicle(cVehicle & vehicle,size_t index)165 void cMapField::addVehicle (cVehicle& vehicle, size_t index)
166 {
167 assert (index <= vehicles.size());
168 vehicles.insert (vehicles.begin() + index, &vehicle);
169
170 vehiclesChanged();
171 unitsChanged();
172 }
173
addPlane(cVehicle & plane,size_t index)174 void cMapField::addPlane (cVehicle& plane, size_t index)
175 {
176 assert (index <= planes.size());
177 planes.insert (planes.begin() + index, &plane);
178
179 planesChanged();
180 unitsChanged();
181 }
182
removeBuilding(const cBuilding & building)183 void cMapField::removeBuilding (const cBuilding& building)
184 {
185 Remove (buildings, &building);
186
187 buildingsChanged();
188 unitsChanged();
189 }
190
removeVehicle(const cVehicle & vehicle)191 void cMapField::removeVehicle (const cVehicle& vehicle)
192 {
193 Remove (vehicles, &vehicle);
194
195 vehiclesChanged();
196 unitsChanged();
197 }
198
removePlane(const cVehicle & plane)199 void cMapField::removePlane (const cVehicle& plane)
200 {
201 Remove (planes, &plane);
202
203 planesChanged();
204 unitsChanged();
205 }
206
removeAll()207 void cMapField::removeAll()
208 {
209 buildings.clear();
210 vehicles.clear();
211 planes.clear();
212
213 buildingsChanged();
214 vehiclesChanged();
215 planesChanged();
216 unitsChanged();
217 }
218
219 // cStaticMap //////////////////////////////////////////////////
220
cStaticMap()221 cStaticMap::cStaticMap() : size (0), terrains()
222 {
223 }
224
~cStaticMap()225 cStaticMap::~cStaticMap()
226 {
227 }
228
getTerrain(const cPosition & position) const229 const sTerrain& cStaticMap::getTerrain (const cPosition& position) const
230 {
231 return terrains[Kacheln[getOffset (position)]];
232 }
233
isBlocked(const cPosition & position) const234 bool cStaticMap::isBlocked (const cPosition& position) const
235 {
236 return getTerrain (position).blocked;
237 }
238
isCoast(const cPosition & position) const239 bool cStaticMap::isCoast (const cPosition& position) const
240 {
241 return getTerrain (position).coast;
242 }
243
isWater(const cPosition & position) const244 bool cStaticMap::isWater (const cPosition& position) const
245 {
246 return getTerrain (position).water;
247 }
248
isValidPosition(const cPosition & position) const249 bool cStaticMap::isValidPosition (const cPosition& position) const
250 {
251 return 0 <= position.x() && position.x() < size && 0 <= position.y() && position.y() < size;
252 }
253
clear()254 void cStaticMap::clear()
255 {
256 filename.clear();
257 size = 0;
258 terrains.clear();
259 Kacheln.clear();
260 }
261
isValid() const262 bool cStaticMap::isValid() const
263 {
264 return !filename.empty();
265 }
266
267 /** Loads a map file */
loadMap(const std::string & filename_)268 bool cStaticMap::loadMap (const std::string& filename_)
269 {
270 clear();
271 // Open File
272 filename = filename_;
273 Log.write ("Loading map \"" + filename_ + "\"", cLog::eLOG_TYPE_DEBUG);
274
275 // first try in the factory maps directory
276 std::string fullFilename = cSettings::getInstance().getMapsPath() + PATH_DELIMITER + filename;
277 SDL_RWops* fpMapFile = SDL_RWFromFile (fullFilename.c_str(), "rb");
278 if (fpMapFile == nullptr)
279 {
280 // now try in the user's map directory
281 std::string userMapsDir = getUserMapsDir();
282 if (!userMapsDir.empty())
283 {
284 fullFilename = userMapsDir + filename;
285 fpMapFile = SDL_RWFromFile (fullFilename.c_str(), "rb");
286 }
287 }
288 if (fpMapFile == nullptr)
289 {
290 Log.write ("Cannot load map file: \"" + filename + "\"", cLog::eLOG_TYPE_WARNING);
291 clear();
292 return false;
293 }
294 char szFileTyp[4];
295
296 // check for typ
297 SDL_RWread (fpMapFile, &szFileTyp, 1, 3);
298 szFileTyp[3] = '\0';
299 // WRL - interplays original mapformat
300 // WRX - mapformat from russian mapeditor
301 // DMO - for some reason some original maps have this filetype
302 if (strcmp (szFileTyp, "WRL") != 0 && strcmp (szFileTyp, "WRX") != 0 && strcmp (szFileTyp, "DMO") != 0)
303 {
304 Log.write ("Wrong file format: \"" + filename + "\"", cLog::eLOG_TYPE_WARNING);
305 SDL_RWclose (fpMapFile);
306 clear();
307 return false;
308 }
309 SDL_RWseek (fpMapFile, 2, SEEK_CUR);
310
311 // Read informations and get positions from the map-file
312 const short sWidth = SDL_ReadLE16 (fpMapFile);
313 Log.write ("SizeX: " + iToStr (sWidth), cLog::eLOG_TYPE_DEBUG);
314 const short sHeight = SDL_ReadLE16 (fpMapFile);
315 Log.write ("SizeY: " + iToStr (sHeight), cLog::eLOG_TYPE_DEBUG);
316 SDL_RWseek (fpMapFile, sWidth * sHeight, SEEK_CUR); // Ignore Mini-Map
317 const int iDataPos = SDL_RWtell (fpMapFile); // Map-Data
318 SDL_RWseek (fpMapFile, sWidth * sHeight * 2, SEEK_CUR);
319 const int iNumberOfTerrains = SDL_ReadLE16 (fpMapFile); // Read PicCount
320 Log.write ("Number of terrains: " + iToStr (iNumberOfTerrains), cLog::eLOG_TYPE_DEBUG);
321 const int iGraphicsPos = SDL_RWtell (fpMapFile); // Terrain Graphics
322 const int iPalettePos = iGraphicsPos + iNumberOfTerrains * 64 * 64; // Palette
323 const int iInfoPos = iPalettePos + 256 * 3; // Special informations
324
325 if (sWidth != sHeight)
326 {
327 Log.write ("Map must be quadratic!: \"" + filename + "\"", cLog::eLOG_TYPE_WARNING);
328 SDL_RWclose (fpMapFile);
329 clear();
330 return false;
331 }
332
333 // Generate new Map
334 this->size = std::max<int> (16, sWidth);
335 Kacheln.resize (size * size, 0);
336 terrains.resize(iNumberOfTerrains);
337
338 // Load Color Palette
339 SDL_RWseek (fpMapFile, iPalettePos, SEEK_SET);
340 for (int i = 0; i < 256; i++)
341 {
342 SDL_RWread (fpMapFile, palette + i, 3, 1);
343 }
344 //generate palette for terrains with fog
345 for (int i = 0; i < 256; i++)
346 {
347 palette_shw[i].r = (unsigned char) (palette[i].r * 0.6f);
348 palette_shw[i].g = (unsigned char) (palette[i].g * 0.6f);
349 palette_shw[i].b = (unsigned char) (palette[i].b * 0.6f);
350 palette[i].a = 255;
351 palette_shw[i].a = 255;
352 }
353
354 // Load necessary Terrain Graphics
355 for (int iNum = 0; iNum < iNumberOfTerrains; iNum++)
356 {
357 unsigned char cByte; // one Byte
358 // load terrain type info
359 SDL_RWseek (fpMapFile, iInfoPos + iNum, SEEK_SET);
360 SDL_RWread (fpMapFile, &cByte, 1, 1);
361
362 switch (cByte)
363 {
364 case 0:
365 //normal terrain without special property
366 break;
367 case 1:
368 terrains[iNum].water = true;
369 break;
370 case 2:
371 terrains[iNum].coast = true;
372 break;
373 case 3:
374 terrains[iNum].blocked = true;
375 break;
376 default:
377 Log.write ("unknown terrain type " + iToStr (cByte) + " on tile " + iToStr (iNum) + " found. Handled as blocked!", cLog::eLOG_TYPE_WARNING);
378 terrains[iNum].blocked = true;
379 //SDL_RWclose (fpMapFile);
380 //return false;
381 }
382
383 //load terrain graphic
384 AutoSurface surface (cStaticMap::loadTerrGraph (fpMapFile, iGraphicsPos, palette, iNum));
385 if (surface == nullptr)
386 {
387 Log.write ("EOF while loading terrain number " + iToStr (iNum), cLog::eLOG_TYPE_WARNING);
388 SDL_RWclose (fpMapFile);
389 clear();
390 return false;
391 }
392 copySrfToTerData (*surface, iNum);
393 }
394
395 // Load map data
396 SDL_RWseek (fpMapFile, iDataPos, SEEK_SET);
397 for (int iY = 0; iY < size; iY++)
398 {
399 for (int iX = 0; iX < size; iX++)
400 {
401 int Kachel = SDL_ReadLE16 (fpMapFile);
402 if (Kachel >= iNumberOfTerrains)
403 {
404 Log.write ("a map field referred to a nonexisting terrain: " + iToStr (Kachel), cLog::eLOG_TYPE_WARNING);
405 SDL_RWclose (fpMapFile);
406 clear();
407 return false;
408 }
409 Kacheln[iY * size + iX] = Kachel;
410 }
411 }
412 SDL_RWclose (fpMapFile);
413 return true;
414 }
415
loadTerrGraph(SDL_RWops * fpMapFile,int iGraphicsPos,const SDL_Color (& colors)[256],int iNum)416 /*static*/AutoSurface cStaticMap::loadTerrGraph (SDL_RWops* fpMapFile, int iGraphicsPos, const SDL_Color (&colors)[256], int iNum)
417 {
418 // Create new surface and copy palette
419 AutoSurface surface (SDL_CreateRGBSurface (0, 64, 64, 8, 0, 0, 0, 0));
420 surface->pitch = surface->w;
421
422 SDL_SetPaletteColors (surface->format->palette, colors, 0, 256);
423
424 // Go to position of filedata
425 SDL_RWseek (fpMapFile, iGraphicsPos + 64 * 64 * (iNum), SEEK_SET);
426
427 // Read pixel data and write to surface
428 if (SDL_RWread (fpMapFile, surface->pixels, 1, 64 * 64) != 64 * 64) return 0;
429 return surface;
430 }
431
loadMapPreview(const std::string & mapName,int * mapSize)432 /*static*/AutoSurface cStaticMap::loadMapPreview (const std::string& mapName, int* mapSize)
433 {
434 std::string mapPath = cSettings::getInstance().getMapsPath() + PATH_DELIMITER + mapName;
435 // if no factory map of that name exists, try the custom user maps
436
437 SDL_RWops* mapFile = SDL_RWFromFile (mapPath.c_str(), "rb");
438 if (mapFile == nullptr && !getUserMapsDir().empty())
439 {
440 mapPath = getUserMapsDir() + mapName;
441 mapFile = SDL_RWFromFile (mapPath.c_str(), "rb");
442 }
443
444 if (mapFile == nullptr) return nullptr;
445
446 SDL_RWseek (mapFile, 5, SEEK_SET);
447 int size = SDL_ReadLE16 (mapFile);
448 struct { unsigned char cBlue, cGreen, cRed; } Palette[256];
449 short sGraphCount;
450 SDL_RWseek (mapFile, 2 + size * size * 3, SEEK_CUR);
451 sGraphCount = SDL_ReadLE16 (mapFile);
452 SDL_RWseek (mapFile, 64 * 64 * sGraphCount, SEEK_CUR);
453 SDL_RWread (mapFile, &Palette, 3, 256);
454
455 AutoSurface mapSurface (SDL_CreateRGBSurface (0, size, size, 8, 0, 0, 0, 0));
456 mapSurface->pitch = mapSurface->w;
457
458 mapSurface->format->palette->ncolors = 256;
459 for (int j = 0; j < 256; j++)
460 {
461 mapSurface->format->palette->colors[j].r = Palette[j].cBlue;
462 mapSurface->format->palette->colors[j].g = Palette[j].cGreen;
463 mapSurface->format->palette->colors[j].b = Palette[j].cRed;
464 }
465 SDL_RWseek (mapFile, 9, SEEK_SET);
466 const int byteReadCount = SDL_RWread (mapFile, mapSurface->pixels, 1, size * size);
467 SDL_RWclose (mapFile);
468
469 if (byteReadCount != size * size)
470 {
471 // error.
472 return nullptr;
473 }
474 const int MAPWINSIZE = 112;
475 if (mapSurface->w != MAPWINSIZE || mapSurface->h != MAPWINSIZE) // resize map
476 {
477 mapSurface = AutoSurface (scaleSurface (mapSurface.get(), nullptr, MAPWINSIZE, MAPWINSIZE));
478 }
479
480 if (mapSize != nullptr) *mapSize = size;
481 return mapSurface;
482 }
483
copySrfToTerData(SDL_Surface & surface,int iNum)484 void cStaticMap::copySrfToTerData (SDL_Surface& surface, int iNum)
485 {
486 //before the surfaces are copied, the colortable of both surfaces has to be equal
487 //This is needed to make sure, that the pixeldata is copied 1:1
488
489 //copy the normal terrains
490 terrains[iNum].sf_org = AutoSurface (SDL_CreateRGBSurface (0, 64, 64, 8, 0, 0, 0, 0));
491 SDL_SetPaletteColors (terrains[iNum].sf_org->format->palette, surface.format->palette->colors, 0, 256);
492 SDL_BlitSurface (&surface, nullptr, terrains[iNum].sf_org.get(), nullptr);
493
494 terrains[iNum].sf = AutoSurface (SDL_CreateRGBSurface (0, 64, 64, 8, 0, 0, 0, 0));
495 SDL_SetPaletteColors (terrains[iNum].sf->format->palette, surface.format->palette->colors, 0, 256);
496 SDL_BlitSurface (&surface, nullptr, terrains[iNum].sf.get(), nullptr);
497
498 //copy the terrains with fog
499 terrains[iNum].shw_org = AutoSurface (SDL_CreateRGBSurface (0, 64, 64, 8, 0, 0, 0, 0));
500 SDL_SetColors (terrains[iNum].shw_org.get(), surface.format->palette->colors, 0, 256);
501 SDL_BlitSurface (&surface, nullptr, terrains[iNum].shw_org.get(), nullptr);
502
503 terrains[iNum].shw = AutoSurface (SDL_CreateRGBSurface (0, 64, 64, 8, 0, 0, 0, 0));
504 SDL_SetColors (terrains[iNum].shw.get(), surface.format->palette->colors, 0, 256);
505 SDL_BlitSurface (&surface, nullptr, terrains[iNum].shw.get(), nullptr);
506
507 //now set the palette for the fog terrains
508 SDL_SetColors (terrains[iNum].shw_org.get(), palette_shw, 0, 256);
509 SDL_SetColors (terrains[iNum].shw.get(), palette_shw, 0, 256);
510 }
511
scaleSurfaces(int pixelSize)512 void cStaticMap::scaleSurfaces (int pixelSize)
513 {
514 for (sTerrain& t : terrains)
515 {
516 scaleSurface (t.sf_org.get(), t.sf.get(), pixelSize, pixelSize);
517 scaleSurface (t.shw_org.get(), t.shw.get(), pixelSize, pixelSize);
518 }
519 }
520
generateNextAnimationFrame()521 void cStaticMap::generateNextAnimationFrame()
522 {
523 //change palettes to display next frame
524 SDL_Color temp = palette[127];
525 memmove (palette + 97, palette + 96, 32 * sizeof (SDL_Color));
526 palette[96] = palette[103];
527 palette[103] = palette[110];
528 palette[110] = palette[117];
529 palette[117] = palette[123];
530 palette[123] = temp;
531
532 temp = palette_shw[127];
533 memmove (palette_shw + 97, palette_shw + 96, 32 * sizeof (SDL_Color));
534 palette_shw[96] = palette_shw[103];
535 palette_shw[103] = palette_shw[110];
536 palette_shw[110] = palette_shw[117];
537 palette_shw[117] = palette_shw[123];
538 palette_shw[123] = temp;
539
540 //set the new palette for all terrain surfaces
541 for (sTerrain& terrain : terrains)
542 {
543 SDL_SetColors (terrain.sf.get(), palette + 96, 96, 127);
544 //SDL_SetColors (TerrainInUse[i]->sf_org, palette + 96, 96, 127);
545 SDL_SetColors (terrain.shw.get(), palette_shw + 96, 96, 127);
546 //SDL_SetColors (TerrainInUse[i]->shw_org, palette_shw + 96, 96, 127);
547 }
548 }
549
createBigSurface(int sizex,int sizey) const550 AutoSurface cStaticMap::createBigSurface (int sizex, int sizey) const
551 {
552 AutoSurface mapSurface (SDL_CreateRGBSurface (0, sizex, sizey, Video.getColDepth(), 0, 0, 0, 0));
553
554 if (SDL_MUSTLOCK (mapSurface.get())) SDL_LockSurface (mapSurface.get());
555 for (int x = 0; x < mapSurface->w; ++x)
556 {
557 const int terrainx = std::min ((x * size) / mapSurface->w, size - 1);
558 const int offsetx = ((x * size) % mapSurface->w) * 64 / mapSurface->w;
559
560 for (int y = 0; y < mapSurface->h; y++)
561 {
562 const int terrainy = std::min ((y * size) / mapSurface->h, size - 1);
563 const int offsety = ((y * size) % mapSurface->h) * 64 / mapSurface->h;
564
565 const sTerrain& t = this->getTerrain (cPosition (terrainx, terrainy));
566 unsigned int ColorNr = * (static_cast<const unsigned char*> (t.sf_org->pixels) + (offsetx + offsety * 64));
567
568 unsigned char* pixel = reinterpret_cast<unsigned char*> (&static_cast<Uint32*> (mapSurface->pixels) [x + y * mapSurface->w]);
569 pixel[0] = palette[ColorNr].b;
570 pixel[1] = palette[ColorNr].g;
571 pixel[2] = palette[ColorNr].r;
572 }
573 }
574 if (SDL_MUSTLOCK (mapSurface.get())) SDL_UnlockSurface (mapSurface.get());
575 return mapSurface;
576 }
577
578 // Funktionen der Map-Klasse /////////////////////////////////////////////////
cMap(std::shared_ptr<cStaticMap> staticMap_)579 cMap::cMap (std::shared_ptr<cStaticMap> staticMap_) :
580 staticMap (std::move (staticMap_))
581 {
582 const int size = staticMap->getSize().x() * staticMap->getSize().y();
583 fields = new cMapField[size];
584 Resources.resize (size);
585 }
586
~cMap()587 cMap::~cMap()
588 {
589 delete [] fields;
590 }
591
operator [](unsigned int offset) const592 cMapField& cMap::operator[] (unsigned int offset) const
593 {
594 return fields[offset];
595 }
596
getField(const cPosition & position)597 cMapField& cMap::getField (const cPosition& position)
598 {
599 return fields[getOffset (position)];
600 }
601
getField(const cPosition & position) const602 const cMapField& cMap::getField (const cPosition& position) const
603 {
604 return fields[getOffset (position)];
605 }
606
isWaterOrCoast(const cPosition & position) const607 bool cMap::isWaterOrCoast (const cPosition& position) const
608 {
609 const sTerrain& terrainType = staticMap->getTerrain (position);
610 return terrainType.water | terrainType.coast;
611 }
612
assignRessources(const cMap & rhs)613 void cMap::assignRessources (const cMap& rhs)
614 {
615 Resources = rhs.Resources;
616 }
617
618 //--------------------------------------------------------------------------
getHexValue(unsigned char byte)619 static std::string getHexValue (unsigned char byte)
620 {
621 std::string str = "";
622 const char hexChars[] = "0123456789ABCDEF";
623 const unsigned char high = (byte >> 4) & 0x0F;
624 const unsigned char low = byte & 0x0F;
625
626 str += hexChars[high];
627 str += hexChars[low];
628 return str;
629 }
630
631 //--------------------------------------------------------------------------
resourcesToString() const632 std::string cMap::resourcesToString() const
633 {
634 std::string str;
635 str.reserve (4 * Resources.size() + 1);
636 for (size_t i = 0; i != Resources.size(); ++i)
637 {
638 str += getHexValue (Resources[i].typ);
639 str += getHexValue (Resources[i].value);
640 }
641 return str;
642 }
643
644 //--------------------------------------------------------------------------
getByteValue(const std::string & str,int index)645 static unsigned char getByteValue (const std::string& str, int index)
646 {
647 unsigned char first = str[index + 0] - '0';
648 unsigned char second = str[index + 1] - '0';
649
650 if (first >= 'A' - '0') first -= 'A' - '0' - 10;
651 if (second >= 'A' - '0') second -= 'A' - '0' - 10;
652 return (first * 16 + second);
653 }
654
655 //--------------------------------------------------------------------------
setResourcesFromString(const std::string & str)656 void cMap::setResourcesFromString (const std::string& str)
657 {
658 for (size_t i = 0; i != Resources.size(); ++i)
659 {
660 Resources[i].typ = getByteValue (str, 4 * i);
661 Resources[i].value = getByteValue (str, 4 * i + 2);
662 }
663 }
664
placeRessources(const std::vector<cPosition> & landingPositions,const cGameSettings & gameSetting)665 void cMap::placeRessources (const std::vector<cPosition>& landingPositions, const cGameSettings& gameSetting)
666 {
667 std::fill (Resources.begin(), Resources.end(), sResources());
668
669 std::vector<int> resSpotTypes(landingPositions.size(), RES_METAL);
670 std::vector<T_2<int>> resSpots;
671 for (const auto& position : landingPositions)
672 {
673 resSpots.push_back(T_2<int> ((position.x() & ~1) + (RES_METAL % 2), (position.y() & ~1) + ((RES_METAL / 2) % 2)));
674 }
675
676 const eGameSettingsResourceDensity density = gameSetting.getResourceDensity();
677 eGameSettingsResourceAmount frequencies[RES_COUNT];
678
679 frequencies[RES_METAL] = gameSetting.getMetalAmount();
680 frequencies[RES_OIL] = gameSetting.getOilAmount();
681 frequencies[RES_GOLD] = gameSetting.getGoldAmount();
682
683 const std::size_t resSpotCount = (std::size_t) (getSize().x() * getSize().y() * 0.003f * (1.5f + getResourceDensityFactor (density)));
684 const std::size_t playerCount = landingPositions.size();
685 // create remaining resource positions
686 while (resSpots.size() < resSpotCount)
687 {
688 T_2<int> pos;
689
690 pos.x = 2 + random (getSize().x() - 4);
691 pos.y = 2 + random (getSize().y() - 4);
692 resSpots.push_back(pos);
693 }
694 resSpotTypes.resize (resSpotCount);
695 // Resourcen gleichmässiger verteilen
696 for (std::size_t j = 0; j < 3; j++)
697 {
698 for (std::size_t i = playerCount; i < resSpotCount; i++)
699 {
700 T_2<float> d;
701 for (std::size_t j = 0; j < resSpotCount; j++)
702 {
703 if (i == j) continue;
704
705 int diffx1 = resSpots[i].x - resSpots[j].x;
706 int diffx2 = diffx1 + (getSize().x() - 4);
707 int diffx3 = diffx1 - (getSize().x() - 4);
708 int diffy1 = resSpots[i].y - resSpots[j].y;
709 int diffy2 = diffy1 + (getSize().y() - 4);
710 int diffy3 = diffy1 - (getSize().y() - 4);
711 if (abs (diffx2) < abs (diffx1)) diffx1 = diffx2;
712 if (abs (diffx3) < abs (diffx1)) diffx1 = diffx3;
713 if (abs (diffy2) < abs (diffy1)) diffy1 = diffy2;
714 if (abs (diffy3) < abs (diffy1)) diffy1 = diffy3;
715 T_2<float> diff (diffx1, diffy1);
716 if (diff == T_2<float>::Zero)
717 {
718 diff.x += 1;
719 }
720 const float dist = diff.dist();
721 d += diff * (10.f / (dist * dist));
722
723 }
724 resSpots[i] += T_2<int> (Round (d.x), Round (d.y));
725 if (resSpots[i].x < 2) resSpots[i].x += getSize().x() - 4;
726 if (resSpots[i].y < 2) resSpots[i].y += getSize().y() - 4;
727 if (resSpots[i].x > getSize().x() - 3) resSpots[i].x -= getSize().x() - 4;
728 if (resSpots[i].y > getSize().y() - 3) resSpots[i].y -= getSize().y() - 4;
729
730 }
731 }
732 // Resourcen Typ bestimmen
733 for (std::size_t i = playerCount; i < resSpotCount; i++)
734 {
735 float amount[RES_COUNT] = {0.f, 0.f, 0.f, 0.f};
736 for (std::size_t j = 0; j < i; j++)
737 {
738 const float maxDist = 40.f;
739 float dist = sqrtf (resSpots[i].distSqr (resSpots[j]));
740 if (dist < maxDist) amount[resSpotTypes[j]] += 1 - sqrtf (dist / maxDist);
741 }
742
743 amount[RES_METAL] /= 1.0f;
744 amount[RES_OIL] /= 0.8f;
745 amount[RES_GOLD] /= 0.4f;
746
747 int type = RES_METAL;
748 if (amount[RES_OIL] < amount[type]) type = RES_OIL;
749 if (amount[RES_GOLD] < amount[type]) type = RES_GOLD;
750
751 resSpots[i].x &= ~1;
752 resSpots[i].y &= ~1;
753 resSpots[i].x += type % 2;
754 resSpots[i].y += (type / 2) % 2;
755
756 resSpotTypes[i] = ((resSpots[i].y % 2) * 2) + (resSpots[i].x % 2);
757 }
758 // Resourcen platzieren
759 for (std::size_t i = 0; i < resSpotCount; i++)
760 {
761 T_2<int> pos = resSpots[i];
762 T_2<int> p;
763 bool hasGold = random (100) < 40;
764 const int minx = std::max (pos.x - 1, 0);
765 const int maxx = std::min (pos.x + 1, getSize().x() - 1);
766 const int miny = std::max (pos.y - 1, 0);
767 const int maxy = std::min (pos.y + 1, getSize().y() - 1);
768 for (p.y = miny; p.y <= maxy; ++p.y)
769 {
770 for (p.x = minx; p.x <= maxx; ++p.x)
771 {
772 T_2<int> absPos = p;
773 int type = (absPos.y % 2) * 2 + (absPos.x % 2);
774
775 int index = getOffset (cPosition (absPos.x, absPos.y));
776 if (type != RES_NONE &&
777 ((hasGold && i >= playerCount) || resSpotTypes[i] == RES_GOLD || type != RES_GOLD) &&
778 !isBlocked (cPosition (absPos.x, absPos.y)))
779 {
780 Resources[index].typ = type;
781 if (i >= playerCount)
782 {
783 Resources[index].value = 1 + random (2 + getResourceAmountFactor (frequencies[type]) * 2);
784 if (p == pos) Resources[index].value += 3 + random (4 + getResourceAmountFactor (frequencies[type]) * 2);
785 }
786 else
787 {
788 Resources[index].value = 1 + 4 + getResourceAmountFactor (frequencies[type]);
789 if (p == pos) Resources[index].value += 3 + 2 + getResourceAmountFactor (frequencies[type]);
790 }
791
792 Resources[index].value = std::min<unsigned char> (16, Resources[index].value);
793 }
794 }
795 }
796 }
797 }
798
getMapLevel(const cBuilding & building)799 /* static */ int cMap::getMapLevel (const cBuilding& building)
800 {
801 const sUnitData& data = building.data;
802
803 if (data.surfacePosition == sUnitData::SURFACE_POS_BENEATH_SEA) return 9; // seamine
804 if (data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_SEA) return 7; // bridge
805 if (data.surfacePosition == sUnitData::SURFACE_POS_BASE && data.canBeOverbuild) return 6; // platform
806 if (data.surfacePosition == sUnitData::SURFACE_POS_BASE) return 5; // road
807 if (!building.getOwner()) return 4; // rubble
808 if (data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_BASE) return 3; // landmine
809
810 return 1; // other buildings
811 }
812
getMapLevel(const cVehicle & vehicle)813 /* static */ int cMap::getMapLevel (const cVehicle& vehicle)
814 {
815 if (vehicle.data.factorSea > 0 && vehicle.data.factorGround == 0) return 8; // ships
816 if (vehicle.data.factorAir > 0) return 0; // planes
817
818 return 2; // other vehicles
819 }
820
addBuilding(cBuilding & building,const cPosition & position)821 void cMap::addBuilding (cBuilding& building, const cPosition& position)
822 {
823 //big base building are not implemented
824 if (building.data.surfacePosition != sUnitData::SURFACE_POS_GROUND && building.data.isBig && building.getOwner()) return;
825
826 const int mapLevel = cMap::getMapLevel (building);
827 size_t i = 0;
828
829 if (building.data.isBig)
830 {
831 auto& field = getField (position);
832 i = 0;
833 while (i < field.getBuildings().size() && cMap::getMapLevel (*field.getBuildings()[i]) < mapLevel) i++;
834 field.addBuilding (building, i);
835
836 auto& fieldEast = getField (position + cPosition (1, 0));
837 i = 0;
838 while (i < fieldEast.getBuildings().size() && cMap::getMapLevel (*fieldEast.getBuildings()[i]) < mapLevel) i++;
839 fieldEast.addBuilding (building, i);
840
841 auto& fieldSouth = getField (position + cPosition (0, 1));
842 i = 0;
843 while (i < fieldSouth.getBuildings().size() && cMap::getMapLevel (*fieldSouth.getBuildings()[i]) < mapLevel) i++;
844 fieldSouth.addBuilding (building, i);
845
846 auto& fieldSouthEast = getField (position + cPosition (1, 1));
847 i = 0;
848 while (i < fieldSouthEast.getBuildings().size() && cMap::getMapLevel (*fieldSouthEast.getBuildings()[i]) < mapLevel) i++;
849 fieldSouthEast.addBuilding (building, i);
850 }
851 else
852 {
853 auto& field = getField (position);
854
855 while (i < field.getBuildings().size() && cMap::getMapLevel (*field.getBuildings()[i]) < mapLevel) i++;
856 field.addBuilding (building, i);
857 }
858 addedUnit (building);
859 }
860
addVehicle(cVehicle & vehicle,const cPosition & position)861 void cMap::addVehicle (cVehicle& vehicle, const cPosition& position)
862 {
863 auto& field = getField (position);
864 if (vehicle.data.factorAir > 0)
865 {
866 field.addPlane (vehicle, 0);
867 }
868 else
869 {
870 field.addVehicle (vehicle, 0);
871 }
872 addedUnit (vehicle);
873 }
874
deleteBuilding(const cBuilding & building)875 void cMap::deleteBuilding (const cBuilding& building)
876 {
877 getField (building.getPosition()).removeBuilding (building);
878
879 if (building.data.isBig)
880 {
881 getField (building.getPosition() + cPosition (1, 0)).removeBuilding (building);
882 getField (building.getPosition() + cPosition (1, 1)).removeBuilding (building);
883 getField (building.getPosition() + cPosition (0, 1)).removeBuilding (building);
884 }
885 removedUnit (building);
886 }
887
deleteVehicle(const cVehicle & vehicle)888 void cMap::deleteVehicle (const cVehicle& vehicle)
889 {
890 if (vehicle.data.factorAir > 0)
891 {
892 getField (vehicle.getPosition()).removePlane (vehicle);
893 }
894 else
895 {
896 getField (vehicle.getPosition()).removeVehicle (vehicle);
897
898 if (vehicle.data.isBig)
899 {
900 getField (vehicle.getPosition() + cPosition (1, 0)).removeVehicle (vehicle);
901 getField (vehicle.getPosition() + cPosition (1, 1)).removeVehicle (vehicle);
902 getField (vehicle.getPosition() + cPosition (0, 1)).removeVehicle (vehicle);
903 }
904 }
905 removedUnit (vehicle);
906 }
907
deleteUnit(const cUnit & unit)908 void cMap::deleteUnit (const cUnit& unit)
909 {
910 if (unit.isABuilding())
911 {
912 deleteBuilding (static_cast<const cBuilding&> (unit));
913 }
914 else
915 {
916 assert (unit.isAVehicle());
917 deleteVehicle (static_cast<const cVehicle&> (unit));
918 }
919 }
920
moveVehicle(cVehicle & vehicle,const cPosition & position,int height)921 void cMap::moveVehicle (cVehicle& vehicle, const cPosition& position, int height)
922 {
923 const auto oldPosition = vehicle.getPosition();
924
925 vehicle.setPosition (position);
926
927 if (vehicle.data.factorAir > 0)
928 {
929 getField (oldPosition).removePlane (vehicle);
930 height = std::min<int> (getField (position).getPlanes().size(), height);
931 getField (position).addPlane (vehicle, height);
932 }
933 else
934 {
935 getField (oldPosition).removeVehicle (vehicle);
936
937 //check, whether the vehicle is centered on 4 map fields
938 if (vehicle.data.isBig)
939 {
940 getField (oldPosition + cPosition (1, 0)).removeVehicle (vehicle);
941 getField (oldPosition + cPosition (1, 1)).removeVehicle (vehicle);
942 getField (oldPosition + cPosition (0, 1)).removeVehicle (vehicle);
943
944 vehicle.data.isBig = false;
945 }
946
947 getField (position).addVehicle (vehicle, 0);
948 }
949 movedVehicle (vehicle, oldPosition);
950 }
951
moveVehicleBig(cVehicle & vehicle,const cPosition & position)952 void cMap::moveVehicleBig (cVehicle& vehicle, const cPosition& position)
953 {
954 if (vehicle.data.isBig)
955 {
956 Log.write ("Calling moveVehicleBig on a big vehicle", cLog::eLOG_TYPE_NET_ERROR);
957 //calling this function twice is always an error.
958 //nevertheless try to proceed by resetting the data.isBig flag
959 moveVehicle (vehicle, position);
960 }
961
962 const auto oldPosition = vehicle.getPosition();
963
964 getField (oldPosition).removeVehicle (vehicle);
965
966 vehicle.setPosition (position);
967
968 getField (position).addVehicle (vehicle, 0);
969 getField (position + cPosition (1, 0)).addVehicle (vehicle, 0);
970 getField (position + cPosition (1, 1)).addVehicle (vehicle, 0);
971 getField (position + cPosition (0, 1)).addVehicle (vehicle, 0);
972
973 vehicle.data.isBig = true;
974
975 movedVehicle (vehicle, oldPosition);
976 }
977
possiblePlace(const cVehicle & vehicle,const cPosition & position,bool checkPlayer) const978 bool cMap::possiblePlace (const cVehicle& vehicle, const cPosition& position, bool checkPlayer) const
979 {
980 return possiblePlaceVehicle (vehicle.data, position, vehicle.getOwner(), checkPlayer);
981 }
982
possiblePlaceVehicle(const sUnitData & vehicleData,const cPosition & position,const cPlayer * player,bool checkPlayer) const983 bool cMap::possiblePlaceVehicle (const sUnitData& vehicleData, const cPosition& position, const cPlayer* player, bool checkPlayer) const
984 {
985 if (isValidPosition (position) == false) return false;
986
987 const std::vector<cBuilding*>& buildings = getField (position).getBuildings();
988 std::vector<cBuilding*>::const_iterator b_it = buildings.begin();
989 std::vector<cBuilding*>::const_iterator b_end = buildings.end();
990
991 //search first building, that is not a connector
992 if (b_it != b_end && (*b_it)->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE) ++b_it;
993
994 if (vehicleData.factorAir > 0)
995 {
996 if (checkPlayer && player && !player->canSeeAt (position)) return true;
997 //only one plane per field for now
998 if (getField (position).getPlanes().size() >= MAX_PLANES_PER_FIELD) return false;
999 }
1000 if (vehicleData.factorGround > 0)
1001 {
1002 if (isBlocked (position)) return false;
1003
1004 if ((isWater (position) && vehicleData.factorSea == 0) ||
1005 (isCoast (position) && vehicleData.factorCoast == 0))
1006 {
1007 if (checkPlayer && player && !player->canSeeAt (position)) return false;
1008
1009 //vehicle can drive on water, if there is a bridge, platform or road
1010 if (b_it == b_end) return false;
1011 if ((*b_it)->data.surfacePosition != sUnitData::SURFACE_POS_ABOVE_SEA &&
1012 (*b_it)->data.surfacePosition != sUnitData::SURFACE_POS_BASE &&
1013 (*b_it)->data.surfacePosition != sUnitData::SURFACE_POS_ABOVE_BASE) return false;
1014 }
1015 //check for enemy mines
1016 if (player && b_it != b_end && (*b_it)->getOwner() != player &&
1017 (*b_it)->data.explodesOnContact &&
1018 ((*b_it)->isDetectedByPlayer (player) || checkPlayer))
1019 return false;
1020
1021 if (checkPlayer && player && !player->canSeeAt (position)) return true;
1022
1023 if (getField (position).getVehicles().empty() == false) return false;
1024 if (b_it != b_end)
1025 {
1026 // only base buildings and rubble is allowed on the same field with a vehicle
1027 // (connectors have been skiped, so doesn't matter here)
1028 if ((*b_it)->data.surfacePosition != sUnitData::SURFACE_POS_ABOVE_SEA &&
1029 (*b_it)->data.surfacePosition != sUnitData::SURFACE_POS_BASE &&
1030 (*b_it)->data.surfacePosition != sUnitData::SURFACE_POS_ABOVE_BASE &&
1031 (*b_it)->data.surfacePosition != sUnitData::SURFACE_POS_BENEATH_SEA &&
1032 (*b_it)->getOwner()) return false;
1033 }
1034 }
1035 else if (vehicleData.factorSea > 0)
1036 {
1037 if (isBlocked (position)) return false;
1038
1039 if (!isWater (position) &&
1040 (!isCoast (position) || vehicleData.factorCoast == 0)) return false;
1041
1042 //check for enemy mines
1043 if (player && b_it != b_end && (*b_it)->getOwner() != player &&
1044 (*b_it)->data.explodesOnContact && (*b_it)->isDetectedByPlayer (player))
1045 return false;
1046
1047 if (checkPlayer && player && !player->canSeeAt (position)) return true;
1048
1049 if (getField (position).getVehicles().empty() == false) return false;
1050
1051 //only bridge and sea mine are allowed on the same field with a ship (connectors have been skiped, so doesn't matter here)
1052 if (b_it != b_end &&
1053 (*b_it)->data.surfacePosition != sUnitData::SURFACE_POS_ABOVE_SEA &&
1054 (*b_it)->data.surfacePosition != sUnitData::SURFACE_POS_BENEATH_SEA)
1055 {
1056 // if the building is a landmine, we have to check whether it's on a bridge or not
1057 if ((*b_it)->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_BASE)
1058 {
1059 ++b_it;
1060 if (b_it == b_end || (*b_it)->data.surfacePosition != sUnitData::SURFACE_POS_ABOVE_SEA) return false;
1061 }
1062 else return false;
1063 }
1064 }
1065 return true;
1066 }
1067
1068 // can't place it too near to the map border
possiblePlaceBuildingWithMargin(const sUnitData & buildingData,const cPosition & position,int margin,const cVehicle * vehicle) const1069 bool cMap::possiblePlaceBuildingWithMargin (const sUnitData& buildingData, const cPosition& position, int margin, const cVehicle* vehicle) const
1070 {
1071 if (position.x() < margin || position.x() >= getSize().x() - margin || position.y() < margin || position.y() >= getSize().y() - margin) return false;
1072 return possiblePlaceBuilding (buildingData, position, vehicle);
1073 }
1074
possiblePlaceBuilding(const sUnitData & buildingData,const cPosition & position,const cVehicle * vehicle) const1075 bool cMap::possiblePlaceBuilding (const sUnitData& buildingData, const cPosition& position, const cVehicle* vehicle) const
1076 {
1077 if (!isValidPosition (position)) return false;
1078 if (isBlocked (position)) return false;
1079 const cMapField& field = getField (position);
1080
1081 // Check all buildings in this field for a building of the same type. This
1082 // will prevent roads, connectors and water platforms from building on top
1083 // of themselves.
1084 const std::vector<cBuilding*>& buildings = field.getBuildings();
1085 for (const cBuilding* building : buildings)
1086 {
1087 if (building->data.ID == buildingData.ID)
1088 {
1089 return false;
1090 }
1091 }
1092
1093 // Determine terrain type
1094 bool water = isWater (position);
1095 bool coast = isCoast (position);
1096 bool ground = !water && !coast;
1097
1098 for (const cBuilding* building : buildings)
1099 {
1100 if (buildingData.surfacePosition == building->data.surfacePosition &&
1101 building->data.canBeOverbuild == sUnitData::OVERBUILD_TYPE_NO) return false;
1102 switch (building->data.surfacePosition)
1103 {
1104 case sUnitData::SURFACE_POS_GROUND:
1105 case sUnitData::SURFACE_POS_ABOVE_SEA: // bridge
1106 if (buildingData.surfacePosition != sUnitData::SURFACE_POS_ABOVE &&
1107 buildingData.surfacePosition != sUnitData::SURFACE_POS_BASE && // mine can be placed on bridge
1108 buildingData.surfacePosition != sUnitData::SURFACE_POS_BENEATH_SEA && // seamine can be placed under bridge
1109 building->data.canBeOverbuild == sUnitData::OVERBUILD_TYPE_NO) return false;
1110 break;
1111 case sUnitData::SURFACE_POS_BENEATH_SEA: // seamine
1112 case sUnitData::SURFACE_POS_ABOVE_BASE: // landmine
1113 // mine must be removed first
1114 if (buildingData.surfacePosition != sUnitData::SURFACE_POS_ABOVE) return false;
1115 break;
1116 case sUnitData::SURFACE_POS_BASE: // platform, road
1117 water = coast = false;
1118 ground = true;
1119 break;
1120 case sUnitData::SURFACE_POS_ABOVE: // connector
1121 break;
1122 }
1123 }
1124 if ((water && buildingData.factorSea == 0) ||
1125 (coast && buildingData.factorCoast == 0) ||
1126 (ground && buildingData.factorGround == 0)) return false;
1127
1128 //can not build on rubble
1129 if (field.getRubble() &&
1130 buildingData.surfacePosition != sUnitData::SURFACE_POS_ABOVE &&
1131 buildingData.surfacePosition != sUnitData::SURFACE_POS_ABOVE_BASE) return false;
1132
1133 if (field.getVehicles().empty() == false)
1134 {
1135 if (!vehicle) return false;
1136 if (vehicle != field.getVehicles()[0]) return false;
1137 }
1138 return true;
1139 }
reset()1140 void cMap::reset()
1141 {
1142 for (int i = 0; i < getSize().x() * getSize().y(); i++)
1143 {
1144 fields[i].removeAll();
1145 }
1146 }
1147
getResourceDensityFactor(eGameSettingsResourceDensity density)1148 /*static*/ int cMap::getResourceDensityFactor (eGameSettingsResourceDensity density)
1149 {
1150 switch (density)
1151 {
1152 case eGameSettingsResourceDensity::Sparse:
1153 return 0;
1154 case eGameSettingsResourceDensity::Normal:
1155 return 1;
1156 case eGameSettingsResourceDensity::Dense:
1157 return 2;
1158 case eGameSettingsResourceDensity::TooMuch:
1159 return 3;
1160 }
1161 assert (false);
1162 return 0;
1163 }
1164
getResourceAmountFactor(eGameSettingsResourceAmount amount)1165 /*static*/int cMap::getResourceAmountFactor (eGameSettingsResourceAmount amount)
1166 {
1167 switch (amount)
1168 {
1169 case eGameSettingsResourceAmount::Limited:
1170 return 0;
1171 case eGameSettingsResourceAmount::Normal:
1172 return 1;
1173 case eGameSettingsResourceAmount::High:
1174 return 2;
1175 case eGameSettingsResourceAmount::TooMuch:
1176 return 3;
1177 }
1178 assert (false);
1179 return 0;
1180 }
1181