1 // _________ __ __
2 // / _____// |_____________ _/ |______ ____ __ __ ______
3 // \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
4 // / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
5 // /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
6 // \/ \/ \//_____/ \/
7 // ______________________ ______________________
8 // T H E W A R B E G I N S
9 // Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name map_template.cpp - The map template source file. */
12 //
13 // (c) Copyright 2018-2019 by Andrettin
14 //
15 // This program is free software; you can redistribute it and/or modify
16 // it under the terms of the GNU General Public License as published by
17 // the Free Software Foundation; only version 2 of the License.
18 //
19 // This program is distributed in the hope that it will be useful,
20 // but WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 // GNU General Public License for more details.
23 //
24 // You should have received a copy of the GNU General Public License
25 // along with this program; if not, write to the Free Software
26 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 // 02111-1307, USA.
28 //
29
30 /*----------------------------------------------------------------------------
31 -- Includes
32 ----------------------------------------------------------------------------*/
33
34 #include "stratagus.h"
35
36 #include "map/map_template.h"
37
38 #include <fstream>
39
40 #include "civilization.h"
41 #include "config.h"
42 #include "editor.h"
43 #include "game.h"
44 #include "iocompat.h"
45 #include "iolib.h"
46 #include "map/historical_location.h"
47 #include "map/map.h"
48 #include "map/map_layer.h"
49 #include "map/site.h"
50 #include "map/terrain_type.h"
51 #include "map/tile.h"
52 #include "map/tileset.h"
53 #include "plane.h"
54 #include "player.h"
55 #include "quest.h"
56 #include "settings.h"
57 #include "time/calendar.h"
58 #include "time/season_schedule.h"
59 #include "time/time_of_day_schedule.h"
60 #include "translate.h"
61 #include "unit/historical_unit.h"
62 #include "unit/unit.h"
63 #include "unit/unit_find.h"
64 #include "unit/unittype.h"
65 #include "video.h"
66 #include "world.h"
67
68 /*----------------------------------------------------------------------------
69 -- Variables
70 ----------------------------------------------------------------------------*/
71
72 std::vector<CMapTemplate *> CMapTemplate::MapTemplates;
73 std::map<std::string, CMapTemplate *> CMapTemplate::MapTemplatesByIdent;
74 const int MaxAdjacentTemplateDistance = 16;
75
76 /*----------------------------------------------------------------------------
77 -- Functions
78 ----------------------------------------------------------------------------*/
79
80 /**
81 ** @brief Destructor
82 */
~CMapTemplate()83 CMapTemplate::~CMapTemplate()
84 {
85 for (CGeneratedTerrain *generated_terrain : this->GeneratedTerrains) {
86 delete generated_terrain;
87 }
88 }
89
90 /**
91 ** @brief Get a map template
92 **
93 ** @param ident The map template's string identifier
94 ** @param should_find Whether it is an error if the map template could not be found; this is true by default
95 **
96 ** @return The map template if found, or null otherwise
97 */
GetMapTemplate(const std::string & ident)98 CMapTemplate *CMapTemplate::GetMapTemplate(const std::string &ident)
99 {
100 if (ident.empty()) {
101 return nullptr;
102 }
103
104 std::map<std::string, CMapTemplate *>::const_iterator find_iterator = MapTemplatesByIdent.find(ident);
105
106 if (find_iterator != MapTemplatesByIdent.end()) {
107 return find_iterator->second;
108 }
109
110 return nullptr;
111 }
112
113 /**
114 ** @brief Get or add a map template
115 **
116 ** @param ident The map template's string identifier
117 **
118 ** @return The map template if found, or a newly-created one otherwise
119 */
GetOrAddMapTemplate(const std::string & ident)120 CMapTemplate *CMapTemplate::GetOrAddMapTemplate(const std::string &ident)
121 {
122 CMapTemplate *map_template = GetMapTemplate(ident);
123
124 if (!map_template) {
125 map_template = new CMapTemplate;
126 map_template->Ident = ident;
127 MapTemplates.push_back(map_template);
128 MapTemplatesByIdent[ident] = map_template;
129 }
130
131 return map_template;
132 }
133
134 /**
135 ** @brief Remove the existing map templates
136 */
ClearMapTemplates()137 void CMapTemplate::ClearMapTemplates()
138 {
139 for (size_t i = 0; i < MapTemplates.size(); ++i) {
140 delete MapTemplates[i];
141 }
142 MapTemplates.clear();
143 }
144
145 /**
146 ** @brief Process data provided by a configuration file
147 **
148 ** @param config_data The configuration data
149 */
ProcessConfigData(const CConfigData * config_data)150 void CMapTemplate::ProcessConfigData(const CConfigData *config_data)
151 {
152 for (size_t i = 0; i < config_data->Properties.size(); ++i) {
153 std::string key = config_data->Properties[i].first;
154 std::string value = config_data->Properties[i].second;
155
156 if (key == "name") {
157 this->Name = value;
158 } else if (key == "plane") {
159 value = FindAndReplaceString(value, "_", "-");
160 CPlane *plane = CPlane::GetPlane(value);
161 if (plane) {
162 this->Plane = plane;
163 } else {
164 fprintf(stderr, "Plane \"%s\" does not exist.\n", value.c_str());
165 }
166 } else if (key == "world") {
167 value = FindAndReplaceString(value, "_", "-");
168 CWorld *world = CWorld::GetWorld(value);
169 if (world) {
170 this->World = world;
171 this->Plane = this->World->Plane;
172 } else {
173 fprintf(stderr, "World \"%s\" does not exist.\n", value.c_str());
174 }
175 } else if (key == "surface_layer") {
176 this->SurfaceLayer = std::stoi(value);
177 if (this->SurfaceLayer >= (int) UI.SurfaceLayerButtons.size()) {
178 UI.SurfaceLayerButtons.resize(this->SurfaceLayer + 1);
179 }
180 } else if (key == "terrain_file") {
181 this->TerrainFile = value;
182 } else if (key == "overlay_terrain_file") {
183 this->OverlayTerrainFile = value;
184 } else if (key == "terrain_image") {
185 this->TerrainImage = value;
186 } else if (key == "overlay_terrain_image") {
187 this->OverlayTerrainImage = value;
188 } else if (key == "width") {
189 this->Width = std::stoi(value);
190 } else if (key == "height") {
191 this->Height = std::stoi(value);
192 } else if (key == "scale") {
193 this->Scale = std::stoi(value);
194 } else if (key == "priority") {
195 this->Priority = std::stoi(value);
196 } else if (key == "pixel_tile_width") {
197 this->PixelTileSize.x = std::stoi(value);
198 } else if (key == "pixel_tile_height") {
199 this->PixelTileSize.y = std::stoi(value);
200 } else if (key == "min_x") {
201 this->MinPos.x = std::stoi(value);
202 } else if (key == "min_y") {
203 this->MinPos.y = std::stoi(value);
204 } else if (key == "max_x") {
205 this->MaxPos.x = std::stoi(value);
206 } else if (key == "max_y") {
207 this->MaxPos.y = std::stoi(value);
208 } else if (key == "main_template") {
209 value = FindAndReplaceString(value, "_", "-");
210 CMapTemplate *main_template = CMapTemplate::GetMapTemplate(value);
211 this->MainTemplate = main_template;
212 main_template->Subtemplates.push_back(this);
213 if (main_template->Plane) {
214 this->Plane = main_template->Plane;
215 }
216 if (main_template->World) {
217 this->World = main_template->World;
218 }
219 this->SurfaceLayer = main_template->SurfaceLayer;
220 } else if (key == "upper_template") {
221 value = FindAndReplaceString(value, "_", "-");
222 CMapTemplate *upper_template = CMapTemplate::GetMapTemplate(value);
223 if (upper_template) {
224 this->UpperTemplate = upper_template;
225 upper_template->LowerTemplate = this;
226 }
227 } else if (key == "lower_template") {
228 value = FindAndReplaceString(value, "_", "-");
229 CMapTemplate *lower_template = CMapTemplate::GetMapTemplate(value);
230 if (lower_template) {
231 this->LowerTemplate = lower_template;
232 lower_template->UpperTemplate = this;
233 }
234 } else if (key == "adjacent_template") {
235 value = FindAndReplaceString(value, "_", "-");
236 CMapTemplate *adjacent_template = CMapTemplate::GetMapTemplate(value);
237 if (adjacent_template) {
238 this->AdjacentTemplates.push_back(adjacent_template);
239 if (std::find(adjacent_template->AdjacentTemplates.begin(), adjacent_template->AdjacentTemplates.end(), this) == adjacent_template->AdjacentTemplates.end()) {
240 adjacent_template->AdjacentTemplates.push_back(this);
241 }
242 }
243 } else if (key == "overland") {
244 this->Overland = StringToBool(value);
245 } else if (key == "base_terrain_type") {
246 value = FindAndReplaceString(value, "_", "-");
247 CTerrainType *terrain_type = CTerrainType::GetTerrainType(value);
248 if (terrain_type) {
249 this->BaseTerrainType = terrain_type;
250 } else {
251 fprintf(stderr, "Terrain type \"%s\" does not exist.\n", value.c_str());
252 }
253 } else if (key == "base_overlay_terrain_type") {
254 value = FindAndReplaceString(value, "_", "-");
255 CTerrainType *terrain_type = CTerrainType::GetTerrainType(value);
256 if (terrain_type) {
257 this->BaseOverlayTerrainType = terrain_type;
258 } else {
259 fprintf(stderr, "Terrain type \"%s\" does not exist.\n", value.c_str());
260 }
261 } else if (key == "output_terrain_image") {
262 this->OutputTerrainImage = StringToBool(value);
263 } else {
264 fprintf(stderr, "Invalid map template property: \"%s\".\n", key.c_str());
265 }
266 }
267
268 for (const CConfigData *child_config_data : config_data->Children) {
269 if (child_config_data->Tag == "generated_neutral_unit" || child_config_data->Tag == "player_location_generated_neutral_unit") {
270 CUnitType *unit_type = nullptr;
271 int quantity = 1;
272
273 for (size_t j = 0; j < child_config_data->Properties.size(); ++j) {
274 std::string key = child_config_data->Properties[j].first;
275 std::string value = child_config_data->Properties[j].second;
276
277 if (key == "unit_type") {
278 value = FindAndReplaceString(value, "_", "-");
279 unit_type = UnitTypeByIdent(value);
280 if (!unit_type) {
281 fprintf(stderr, "Unit type \"%s\" doesn't exist.\n", value.c_str());
282 }
283 } else if (key == "quantity") {
284 quantity = std::stoi(value);
285 } else {
286 fprintf(stderr, "Invalid generated neutral unit property: \"%s\".\n", key.c_str());
287 }
288 }
289
290 if (!unit_type) {
291 fprintf(stderr, "Generated neutral unit has no unit type.\n");
292 continue;
293 }
294
295 if (child_config_data->Tag == "generated_neutral_unit") {
296 this->GeneratedNeutralUnits.push_back(std::pair<CUnitType *, int>(unit_type, quantity));
297 } else if (child_config_data->Tag == "player_location_generated_neutral_unit") {
298 this->PlayerLocationGeneratedNeutralUnits.push_back(std::pair<CUnitType *, int>(unit_type, quantity));
299 }
300 } else if (child_config_data->Tag == "generated_terrain") {
301 CGeneratedTerrain *generated_terrain = new CGeneratedTerrain;
302
303 generated_terrain->ProcessConfigData(child_config_data);
304
305 if (!generated_terrain->TerrainType) {
306 delete generated_terrain;
307 continue;
308 }
309
310 this->GeneratedTerrains.push_back(generated_terrain);
311 } else {
312 fprintf(stderr, "Invalid map template property: \"%s\".\n", child_config_data->Tag.c_str());
313 }
314 }
315
316 if (this->MainTemplate) { //if this is a subtemplate, re-sort the main template's subtemplates according to priority
317 std::sort(this->MainTemplate->Subtemplates.begin(), this->MainTemplate->Subtemplates.end(), [](CMapTemplate *a, CMapTemplate *b) {
318 if (a->Priority != b->Priority) {
319 return a->Priority > b->Priority;
320 } else {
321 return a->Ident < b->Ident;
322 }
323 });
324 }
325 }
326
ApplyTerrainFile(bool overlay,Vec2i template_start_pos,Vec2i map_start_pos,int z) const327 void CMapTemplate::ApplyTerrainFile(bool overlay, Vec2i template_start_pos, Vec2i map_start_pos, int z) const
328 {
329 std::string terrain_file;
330 if (overlay) {
331 terrain_file = this->OverlayTerrainFile;
332 } else {
333 terrain_file = this->TerrainFile;
334 }
335
336 if (terrain_file.empty()) {
337 return;
338 }
339
340 const std::string terrain_filename = LibraryFileName(terrain_file.c_str());
341
342 if (!CanAccessFile(terrain_filename.c_str())) {
343 fprintf(stderr, "File \"%s\" not found.\n", terrain_filename.c_str());
344 }
345
346 std::ifstream is_map(terrain_filename);
347
348 std::string line_str;
349 int y = 0;
350 while (std::getline(is_map, line_str))
351 {
352 if (y < template_start_pos.y || y >= (template_start_pos.y + Map.Info.MapHeights[z])) {
353 y += 1;
354 continue;
355 }
356 int x = 0;
357
358 for (unsigned int i = 0; i < line_str.length(); ++i) {
359 if (x < template_start_pos.x || x >= (template_start_pos.x + Map.Info.MapWidths[z])) {
360 x += 1;
361 continue;
362 }
363 std::string terrain_character = line_str.substr(i, 1);
364 CTerrainType *terrain = nullptr;
365 if (CTerrainType::TerrainTypesByCharacter.find(terrain_character) != CTerrainType::TerrainTypesByCharacter.end()) {
366 terrain = CTerrainType::TerrainTypesByCharacter.find(terrain_character)->second;
367 }
368 if (terrain) {
369 Vec2i real_pos(map_start_pos.x + x - template_start_pos.x, map_start_pos.y + y - template_start_pos.y);
370 Map.Field(real_pos, z)->SetTerrain(terrain);
371 }
372 x += 1;
373 }
374
375 y += 1;
376 }
377
378 // std::string filename = this->Ident;
379 // if (overlay) {
380 // filename += "-overlay";
381 // }
382 // filename += ".png";
383 // SaveMapTemplatePNG(filename.c_str(), this, overlay);
384 }
385
ApplyTerrainImage(bool overlay,Vec2i template_start_pos,Vec2i map_start_pos,int z) const386 void CMapTemplate::ApplyTerrainImage(bool overlay, Vec2i template_start_pos, Vec2i map_start_pos, int z) const
387 {
388 std::string terrain_file;
389 if (overlay) {
390 terrain_file = this->OverlayTerrainImage;
391 } else {
392 terrain_file = this->TerrainImage;
393 }
394
395 if (terrain_file.empty()) {
396 ApplyTerrainFile(overlay, template_start_pos, map_start_pos, z);
397 return;
398 }
399
400 const std::string terrain_filename = LibraryFileName(terrain_file.c_str());
401
402 if (!CanAccessFile(terrain_filename.c_str())) {
403 fprintf(stderr, "File \"%s\" not found.\n", terrain_filename.c_str());
404 }
405
406 CGraphic *terrain_image = CGraphic::New(terrain_filename);
407 terrain_image->Load();
408
409 SDL_LockSurface(terrain_image->Surface);
410 const SDL_PixelFormat *f = terrain_image->Surface->format;
411 const int bpp = terrain_image->Surface->format->BytesPerPixel;
412 Uint8 r, g, b;
413
414 for (int y = 0; y < terrain_image->Height; ++y) {
415 if (y < template_start_pos.y || y >= (template_start_pos.y + (Map.Info.MapHeights[z] / this->Scale))) {
416 continue;
417 }
418
419 for (int x = 0; x < terrain_image->Width; ++x) {
420 if (x < template_start_pos.x || x >= (template_start_pos.x + (Map.Info.MapWidths[z] / this->Scale))) {
421 continue;
422 }
423
424 Uint32 c = *reinterpret_cast<Uint32 *>(&reinterpret_cast<Uint8 *>(terrain_image->Surface->pixels)[x * 4 + y * terrain_image->Surface->pitch]);
425 Uint8 a;
426
427 Video.GetRGBA(c, terrain_image->Surface->format, &r, &g, &b, &a);
428
429 if (a == 0) { //transparent pixels means leaving the area as it is (e.g. if it is a subtemplate use the main template's terrain for this tile instead)
430 continue;
431 }
432
433 CTerrainType *terrain = nullptr;
434 short terrain_feature_id = -1;
435 if (TerrainFeatureColorToIndex.find(std::tuple<int, int, int>(r, g, b)) != TerrainFeatureColorToIndex.end()) {
436 terrain_feature_id = TerrainFeatureColorToIndex.find(std::tuple<int, int, int>(r, g, b))->second;
437 terrain = TerrainFeatures[terrain_feature_id]->TerrainType;
438 } else if (CTerrainType::TerrainTypesByColor.find(std::tuple<int, int, int>(r, g, b)) != CTerrainType::TerrainTypesByColor.end()) {
439 terrain = CTerrainType::TerrainTypesByColor.find(std::tuple<int, int, int>(r, g, b))->second;
440 }
441 for (int sub_y = 0; sub_y < this->Scale; ++sub_y) {
442 for (int sub_x = 0; sub_x < this->Scale; ++sub_x) {
443 Vec2i real_pos(map_start_pos.x + ((x - template_start_pos.x) * this->Scale) + sub_x, map_start_pos.y + ((y - template_start_pos.y) * this->Scale) + sub_y);
444
445 if (!Map.Info.IsPointOnMap(real_pos, z)) {
446 continue;
447 }
448
449 if (terrain) {
450 Map.Field(real_pos, z)->SetTerrain(terrain);
451
452 if (terrain_feature_id != -1) {
453 Map.Field(real_pos, z)->TerrainFeature = TerrainFeatures[terrain_feature_id];
454 }
455 } else {
456 if (r != 0 || g != 0 || b != 0 || !overlay) { //fully black pixels represent areas in overlay terrain files that don't have any overlays
457 fprintf(stderr, "Invalid map terrain: (%d, %d) (RGB: %d/%d/%d)\n", x, y, r, g, b);
458 } else if (overlay && Map.Field(real_pos, z)->OverlayTerrain) { //fully black pixel in overlay terrain map = no overlay
459 Map.Field(real_pos, z)->RemoveOverlayTerrain();
460 }
461 }
462 }
463 }
464 }
465 }
466 SDL_UnlockSurface(terrain_image->Surface);
467
468 CGraphic::Free(terrain_image);
469 }
470
Apply(Vec2i template_start_pos,Vec2i map_start_pos,int z) const471 void CMapTemplate::Apply(Vec2i template_start_pos, Vec2i map_start_pos, int z) const
472 {
473 if (SaveGameLoading) {
474 return;
475 }
476
477 if (template_start_pos.x < 0 || template_start_pos.x >= this->Width || template_start_pos.y < 0 || template_start_pos.y >= this->Height) {
478 fprintf(stderr, "Invalid map coordinate for map template \"%s\": (%d, %d)\n", this->Ident.c_str(), template_start_pos.x, template_start_pos.y);
479 return;
480 }
481
482 if (z >= (int) Map.MapLayers.size()) {
483 int width = std::min(this->Width * this->Scale, Map.Info.MapWidth);
484 int height = std::min(this->Height * this->Scale, Map.Info.MapHeight);
485 if (CurrentCampaign) {
486 //applies the map size set for the campaign for this map layer; for the first map layer that is already Map.Info.Width/Height, so it isn't necessary here
487 width = CurrentCampaign->MapSizes[z].x;
488 height = CurrentCampaign->MapSizes[z].y;
489 }
490
491 CMapLayer *map_layer = new CMapLayer(width, height);
492 map_layer->ID = Map.MapLayers.size();
493 Map.Info.MapWidths.push_back(map_layer->GetWidth());
494 Map.Info.MapHeights.push_back(map_layer->GetHeight());
495 map_layer->Plane = this->Plane;
496 map_layer->World = this->World;
497 map_layer->SurfaceLayer = this->SurfaceLayer;
498 map_layer->PixelTileSize = this->PixelTileSize;
499 map_layer->Overland = this->Overland;
500 Map.MapLayers.push_back(map_layer);
501 } else {
502 if (!this->IsSubtemplateArea()) {
503 Map.MapLayers[z]->Plane = this->Plane;
504 Map.MapLayers[z]->World = this->World;
505 Map.MapLayers[z]->SurfaceLayer = this->SurfaceLayer;
506 Map.MapLayers[z]->PixelTileSize = this->PixelTileSize;
507 Map.MapLayers[z]->Overland = this->Overland;
508 }
509 }
510
511 if (!this->IsSubtemplateArea()) {
512 if (Editor.Running == EditorNotRunning) {
513 if (this->World && this->World->SeasonSchedule) {
514 Map.MapLayers[z]->SeasonSchedule = this->World->SeasonSchedule;
515 } else if (!this->World && this->Plane && this->Plane->SeasonSchedule) {
516 Map.MapLayers[z]->SeasonSchedule = this->Plane->SeasonSchedule;
517 } else {
518 Map.MapLayers[z]->SeasonSchedule = CSeasonSchedule::DefaultSeasonSchedule;
519 }
520
521 Map.MapLayers[z]->SetSeasonByHours(CDate::CurrentTotalHours);
522
523 Map.MapLayers[z]->TimeOfDaySchedule = nullptr;
524 Map.MapLayers[z]->SetTimeOfDay(nullptr);
525
526 if (
527 this->SurfaceLayer == 0
528 && !GameSettings.Inside
529 && !GameSettings.NoTimeOfDay
530 ) {
531 if (this->World && this->World->TimeOfDaySchedule) {
532 Map.MapLayers[z]->TimeOfDaySchedule = this->World->TimeOfDaySchedule;
533 } else if (!this->World && this->Plane && this->Plane->TimeOfDaySchedule) {
534 Map.MapLayers[z]->TimeOfDaySchedule = this->Plane->TimeOfDaySchedule;
535 } else {
536 Map.MapLayers[z]->TimeOfDaySchedule = CTimeOfDaySchedule::DefaultTimeOfDaySchedule;
537 }
538
539 Map.MapLayers[z]->SetTimeOfDayByHours(CDate::CurrentTotalHours);
540 }
541 }
542 }
543
544 Vec2i map_end(std::min(Map.Info.MapWidths[z], map_start_pos.x + (this->Width * this->Scale)), std::min(Map.Info.MapHeights[z], map_start_pos.y + (this->Height * this->Scale)));
545 if (!Map.Info.IsPointOnMap(map_start_pos, z)) {
546 fprintf(stderr, "Invalid map coordinate for map template \"%s\": (%d, %d)\n", this->Ident.c_str(), map_start_pos.x, map_start_pos.y);
547 return;
548 }
549
550 bool has_base_map = !this->TerrainFile.empty() || !this->TerrainImage.empty();
551
552 ShowLoadProgress(_("Applying \"%s\" Map Template Terrain"), this->Name.c_str());
553
554 if (this->BaseTerrainType) {
555 for (int x = map_start_pos.x; x < map_end.x; ++x) {
556 for (int y = map_start_pos.y; y < map_end.y; ++y) {
557 Vec2i tile_pos(x, y);
558 Map.Field(tile_pos, z)->SetTerrain(this->BaseTerrainType);
559
560 if (this->BaseOverlayTerrainType) {
561 Map.Field(tile_pos, z)->SetTerrain(this->BaseOverlayTerrainType);
562 } else {
563 Map.Field(tile_pos, z)->RemoveOverlayTerrain();
564 }
565 }
566 }
567 }
568
569 this->ApplyTerrainImage(false, template_start_pos, map_start_pos, z);
570 this->ApplyTerrainImage(true, template_start_pos, map_start_pos, z);
571
572 if (this->OutputTerrainImage) {
573 std::string filename = this->Ident;
574 filename = FindAndReplaceString(filename, "-", "_");
575 filename += ".png";
576
577 std::string overlay_filename = this->Ident;
578 overlay_filename = FindAndReplaceString(overlay_filename, "-", "_");
579 overlay_filename += "_overlay";
580 overlay_filename += ".png";
581
582 SaveMapTemplatePNG(filename.c_str(), this, false);
583 SaveMapTemplatePNG(overlay_filename.c_str(), this, true);
584 }
585
586 if (CurrentCampaign) {
587 for (size_t i = 0; i < HistoricalTerrains.size(); ++i) {
588 Vec2i history_pos = std::get<0>(HistoricalTerrains[i]);
589 if (history_pos.x < template_start_pos.x || history_pos.x >= (template_start_pos.x + (Map.Info.MapWidths[z] / this->Scale)) || history_pos.y < template_start_pos.y || history_pos.y >= (template_start_pos.y + (Map.Info.MapHeights[z] / this->Scale))) {
590 continue;
591 }
592 if (CurrentCampaign->StartDate.ContainsDate(std::get<2>(HistoricalTerrains[i])) || std::get<2>(HistoricalTerrains[i]).Year == 0) {
593 CTerrainType *historical_terrain = std::get<1>(HistoricalTerrains[i]);
594
595 for (int sub_x = 0; sub_x < this->Scale; ++sub_x) {
596 for (int sub_y = 0; sub_y < this->Scale; ++sub_y) {
597 Vec2i real_pos(map_start_pos.x + ((history_pos.x - template_start_pos.x) * this->Scale) + sub_x, map_start_pos.y + ((history_pos.y - template_start_pos.y) * this->Scale) + sub_y);
598
599 if (!Map.Info.IsPointOnMap(real_pos, z)) {
600 continue;
601 }
602
603 if (historical_terrain) {
604 if (historical_terrain->Overlay && ((historical_terrain->Flags & MapFieldRoad) || (historical_terrain->Flags & MapFieldRailroad)) && !(Map.Field(real_pos, z)->Flags & MapFieldLandAllowed)) {
605 continue;
606 }
607 Map.Field(real_pos, z)->SetTerrain(historical_terrain);
608 } else { //if the terrain type is null, then that means a previously set overlay terrain should be removed
609 Map.Field(real_pos, z)->RemoveOverlayTerrain();
610 }
611 }
612 }
613 }
614 }
615 }
616
617 if (this->IsSubtemplateArea() && this->SurroundingTerrainType) {
618 Vec2i surrounding_start_pos(map_start_pos - Vec2i(1, 1));
619 Vec2i surrounding_end(map_end + Vec2i(1, 1));
620 for (int x = surrounding_start_pos.x; x < surrounding_end.x; ++x) {
621 for (int y = surrounding_start_pos.y; y < surrounding_end.y; y += (surrounding_end.y - surrounding_start_pos.y - 1)) {
622 Vec2i surrounding_pos(x, y);
623 if (!Map.Info.IsPointOnMap(surrounding_pos, z) || Map.IsPointInASubtemplateArea(surrounding_pos, z)) {
624 continue;
625 }
626 Map.Field(surrounding_pos, z)->SetTerrain(this->SurroundingTerrainType);
627 }
628 }
629 for (int x = surrounding_start_pos.x; x < surrounding_end.x; x += (surrounding_end.x - surrounding_start_pos.x - 1)) {
630 for (int y = surrounding_start_pos.y; y < surrounding_end.y; ++y) {
631 Vec2i surrounding_pos(x, y);
632 if (!Map.Info.IsPointOnMap(surrounding_pos, z) || Map.IsPointInASubtemplateArea(surrounding_pos, z)) {
633 continue;
634 }
635 Map.Field(surrounding_pos, z)->SetTerrain(this->SurroundingTerrainType);
636 }
637 }
638 }
639
640 if (CurrentCampaign && CurrentCampaign->Faction && !this->IsSubtemplateArea() && ThisPlayer->Faction != CurrentCampaign->Faction->ID) {
641 ThisPlayer->SetCivilization(CurrentCampaign->Faction->Civilization->ID);
642 ThisPlayer->SetFaction(CurrentCampaign->Faction);
643 ThisPlayer->Resources[CopperCost] = 2500; // give the player enough resources to start up
644 ThisPlayer->Resources[WoodCost] = 2500;
645 ThisPlayer->Resources[StoneCost] = 2500;
646 }
647
648 this->ApplySubtemplates(template_start_pos, map_start_pos, z, false);
649 this->ApplySubtemplates(template_start_pos, map_start_pos, z, true);
650
651 if (!has_base_map) {
652 ShowLoadProgress(_("Generating \"%s\" Map Template Random Terrain"), this->Name.c_str());
653
654 for (const CGeneratedTerrain *generated_terrain : this->GeneratedTerrains) {
655 int map_width = (map_end.x - map_start_pos.x);
656 int map_height = (map_end.y - map_start_pos.y);
657
658 Map.GenerateTerrain(generated_terrain, map_start_pos, map_end - Vec2i(1, 1), has_base_map, z);
659 }
660 }
661
662 if (!this->IsSubtemplateArea()) {
663 Map.AdjustTileMapIrregularities(false, map_start_pos, map_end, z);
664 Map.AdjustTileMapIrregularities(true, map_start_pos, map_end, z);
665 Map.AdjustTileMapTransitions(map_start_pos, map_end, z);
666 Map.AdjustTileMapIrregularities(false, map_start_pos, map_end, z);
667 Map.AdjustTileMapIrregularities(true, map_start_pos, map_end, z);
668 }
669
670 ShowLoadProgress(_("Applying \"%s\" Map Template Units"), this->Name.c_str());
671
672 for (std::map<std::pair<int, int>, std::tuple<CUnitType *, int, CUniqueItem *>>::const_iterator iterator = this->Resources.begin(); iterator != this->Resources.end(); ++iterator) {
673 Vec2i unit_raw_pos(iterator->first.first, iterator->first.second);
674 Vec2i unit_pos(map_start_pos.x + unit_raw_pos.x - template_start_pos.x, map_start_pos.y + unit_raw_pos.y - template_start_pos.y);
675 if (!Map.Info.IsPointOnMap(unit_pos, z)) {
676 continue;
677 }
678
679 const CUnitType *type = std::get<0>(iterator->second);
680
681 Vec2i unit_offset((type->TileSize - 1) / 2);
682
683 if (!OnTopDetails(*type, nullptr) && !UnitTypeCanBeAt(*type, unit_pos - unit_offset, z) && Map.Info.IsPointOnMap(unit_pos - unit_offset, z) && Map.Info.IsPointOnMap(unit_pos - unit_offset + Vec2i(type->TileSize - 1), z)) {
684 fprintf(stderr, "Unit \"%s\" should be placed on (%d, %d) for map template \"%s\", but it cannot be there.\n", type->Ident.c_str(), unit_raw_pos.x, unit_raw_pos.y, this->Ident.c_str());
685 }
686
687 CUnit *unit = CreateResourceUnit(unit_pos - unit_offset, *type, z);
688
689 if (std::get<1>(iterator->second)) {
690 unit->SetResourcesHeld(std::get<1>(iterator->second));
691 unit->Variable[GIVERESOURCE_INDEX].Value = std::get<1>(iterator->second);
692 unit->Variable[GIVERESOURCE_INDEX].Max = std::get<1>(iterator->second);
693 unit->Variable[GIVERESOURCE_INDEX].Enable = 1;
694 }
695
696 if (std::get<2>(iterator->second)) {
697 unit->SetUnique(std::get<2>(iterator->second));
698 }
699 }
700
701 if (CurrentCampaign != nullptr) {
702 this->ApplyConnectors(template_start_pos, map_start_pos, z);
703 }
704 this->ApplySites(template_start_pos, map_start_pos, z);
705 this->ApplyUnits(template_start_pos, map_start_pos, z);
706
707 if (has_base_map) {
708 ShowLoadProgress(_("Generating \"%s\" Map Template Random Terrain"), this->Name.c_str());
709
710 for (const CGeneratedTerrain *generated_terrain : this->GeneratedTerrains) {
711 int map_width = (map_end.x - map_start_pos.x);
712 int map_height = (map_end.y - map_start_pos.y);
713
714 Map.GenerateTerrain(generated_terrain, map_start_pos, map_end - Vec2i(1, 1), has_base_map, z);
715 }
716 }
717
718 if (!this->IsSubtemplateArea()) {
719 Map.AdjustTileMapIrregularities(false, map_start_pos, map_end, z);
720 Map.AdjustTileMapIrregularities(true, map_start_pos, map_end, z);
721 Map.AdjustTileMapTransitions(map_start_pos, map_end, z);
722 Map.AdjustTileMapIrregularities(false, map_start_pos, map_end, z);
723 Map.AdjustTileMapIrregularities(true, map_start_pos, map_end, z);
724 }
725
726 ShowLoadProgress(_("Generating \"%s\" Map Template Random Units"), this->Name.c_str());
727
728 // now, generate the units and heroes that were set to be generated at a random position (by having their position set to {-1, -1})
729 if (CurrentCampaign != nullptr) {
730 this->ApplyConnectors(template_start_pos, map_start_pos, z, true);
731 }
732 this->ApplySites(template_start_pos, map_start_pos, z, true);
733 this->ApplyUnits(template_start_pos, map_start_pos, z, true);
734
735 for (int i = 0; i < PlayerMax; ++i) {
736 if (Players[i].Type != PlayerPerson && Players[i].Type != PlayerComputer && Players[i].Type != PlayerRescueActive) {
737 continue;
738 }
739 if (Map.IsPointInASubtemplateArea(Players[i].StartPos, z)) {
740 continue;
741 }
742 if (Players[i].StartPos.x < map_start_pos.x || Players[i].StartPos.y < map_start_pos.y || Players[i].StartPos.x >= map_end.x || Players[i].StartPos.y >= map_end.y || Players[i].StartMapLayer != z) {
743 continue;
744 }
745 if (Players[i].StartPos.x == 0 && Players[i].StartPos.y == 0) {
746 continue;
747 }
748 // add five workers at the player's starting location
749 if (Players[i].NumTownHalls > 0) {
750 int worker_type_id = PlayerRaces.GetFactionClassUnitType(Players[i].Faction, GetUnitTypeClassIndexByName("worker"));
751 if (worker_type_id != -1 && Players[i].GetUnitTypeCount(UnitTypes[worker_type_id]) == 0) { //only create if the player doesn't have any workers created in another manner
752 Vec2i worker_unit_offset((UnitTypes[worker_type_id]->TileSize - 1) / 2);
753
754 Vec2i worker_pos(Players[i].StartPos);
755
756 bool start_pos_has_town_hall = false;
757 std::vector<CUnit *> table;
758 Select(worker_pos - Vec2i(4, 4), worker_pos + Vec2i(4, 4), table, z, HasSamePlayerAs(Players[i]));
759 for (size_t j = 0; j < table.size(); ++j) {
760 if (table[j]->Type->BoolFlag[TOWNHALL_INDEX].value) {
761 start_pos_has_town_hall = true;
762 break;
763 }
764 }
765
766 if (!start_pos_has_town_hall) { //if the start pos doesn't have a town hall, create the workers in the position of a town hall the player has
767 for (int j = 0; j < Players[i].GetUnitCount(); ++j) {
768 CUnit *town_hall_unit = &Players[i].GetUnit(j);
769 if (!town_hall_unit->Type->BoolFlag[TOWNHALL_INDEX].value) {
770 continue;
771 }
772 if (town_hall_unit->MapLayer->ID != z) {
773 continue;
774 }
775 worker_pos = town_hall_unit->tilePos;
776 }
777 }
778
779 for (int j = 0; j < 5; ++j) {
780 CUnit *worker_unit = CreateUnit(worker_pos, *UnitTypes[worker_type_id], &Players[i], Players[i].StartMapLayer);
781 }
782 }
783 }
784
785 if (Players[i].NumTownHalls > 0 || Players[i].Index == ThisPlayer->Index) {
786 for (size_t j = 0; j < this->PlayerLocationGeneratedNeutralUnits.size(); ++j) {
787 Map.GenerateNeutralUnits(this->PlayerLocationGeneratedNeutralUnits[j].first, this->PlayerLocationGeneratedNeutralUnits[j].second, Players[i].StartPos - Vec2i(8, 8), Players[i].StartPos + Vec2i(8, 8), true, z);
788 }
789 }
790 }
791
792 for (size_t i = 0; i < this->GeneratedNeutralUnits.size(); ++i) {
793 bool grouped = this->GeneratedNeutralUnits[i].first->GivesResource && this->GeneratedNeutralUnits[i].first->TileSize.x == 1 && this->GeneratedNeutralUnits[i].first->TileSize.y == 1; // group small resources
794 Map.GenerateNeutralUnits(this->GeneratedNeutralUnits[i].first, this->GeneratedNeutralUnits[i].second, map_start_pos, map_end - Vec2i(1, 1), grouped, z);
795 }
796 }
797
798 /**
799 ** @brief Apply the subtemplates to the map
800 **
801 ** @param template_start_pos The start position of the map relative to the map template
802 ** @param map_start_pos The start position of the map template relative to the map
803 ** @param z The map layer
804 ** @param random Whether it is subtemplates with a random position that should be applied, or ones with a fixed one
805 */
ApplySubtemplates(const Vec2i & template_start_pos,const Vec2i & map_start_pos,const int z,const bool random) const806 void CMapTemplate::ApplySubtemplates(const Vec2i &template_start_pos, const Vec2i &map_start_pos, const int z, const bool random) const
807 {
808 Vec2i map_end(std::min(Map.Info.MapWidths[z], map_start_pos.x + this->Width), std::min(Map.Info.MapHeights[z], map_start_pos.y + this->Height));
809
810 for (size_t i = 0; i < this->Subtemplates.size(); ++i) {
811 CMapTemplate *subtemplate = this->Subtemplates[i];
812 Vec2i subtemplate_pos(subtemplate->SubtemplatePosition - Vec2i((subtemplate->Width - 1) / 2, (subtemplate->Height - 1) / 2));
813 bool found_location = false;
814
815 if (subtemplate->UpperTemplate && (subtemplate_pos.x < 0 || subtemplate_pos.y < 0)) { //if has no given position, but has an upper template, use its coordinates instead
816 subtemplate_pos = Map.GetSubtemplatePos(subtemplate->UpperTemplate);
817 if (subtemplate_pos.x >= 0 && subtemplate_pos.y >= 0) {
818 found_location = true;
819 }
820 }
821
822 if (subtemplate->LowerTemplate && (subtemplate_pos.x < 0 || subtemplate_pos.y < 0)) { //if has no given position, but has a lower template, use its coordinates instead
823 subtemplate_pos = Map.GetSubtemplatePos(subtemplate->LowerTemplate);
824 if (subtemplate_pos.x >= 0 && subtemplate_pos.y >= 0) {
825 found_location = true;
826 }
827 }
828
829 if (!found_location) {
830 if (subtemplate_pos.x < 0 || subtemplate_pos.y < 0) {
831 if (!random) {
832 continue;
833 }
834 Vec2i min_pos(map_start_pos);
835 Vec2i max_pos(map_end.x - subtemplate->Width, map_end.y - subtemplate->Height);
836
837 if (subtemplate->MinPos.x != -1) {
838 min_pos.x += subtemplate->MinPos.x;
839 min_pos.x -= template_start_pos.x;
840 }
841 if (subtemplate->MinPos.y != -1) {
842 min_pos.y += subtemplate->MinPos.y;
843 min_pos.y -= template_start_pos.y;
844 }
845
846 if (subtemplate->MaxPos.x != -1) {
847 max_pos.x += subtemplate->MaxPos.x;
848 max_pos.x -= this->Width;
849 }
850 if (subtemplate->MaxPos.y != -1) {
851 max_pos.y += subtemplate->MaxPos.y;
852 max_pos.y -= this->Height;
853 }
854
855 //bound the minimum and maximum positions depending on which other templates should be adjacent to this one (if they have already been applied to the map)
856 for (const CMapTemplate *adjacent_template : subtemplate->AdjacentTemplates) {
857 Vec2i adjacent_template_pos = Map.GetSubtemplatePos(adjacent_template);
858
859 if (!Map.Info.IsPointOnMap(adjacent_template_pos, z)) {
860 continue;
861 }
862
863 Vec2i min_adjacency_pos = adjacent_template_pos - MaxAdjacentTemplateDistance - Vec2i(subtemplate->Width, subtemplate->Height);
864 Vec2i max_adjacency_pos = adjacent_template_pos + Vec2i(adjacent_template->Width, adjacent_template->Height) + MaxAdjacentTemplateDistance;
865 min_pos.x = std::max(min_pos.x, min_adjacency_pos.x);
866 min_pos.y = std::max(min_pos.y, min_adjacency_pos.y);
867 max_pos.x = std::min(max_pos.x, max_adjacency_pos.x);
868 max_pos.y = std::min(max_pos.y, max_adjacency_pos.y);
869 }
870
871 std::vector<Vec2i> potential_positions;
872 for (int x = min_pos.x; x <= max_pos.x; ++x) {
873 for (int y = min_pos.y; y <= max_pos.y; ++y) {
874 potential_positions.push_back(Vec2i(x, y));
875 }
876 }
877
878 while (!potential_positions.empty()) {
879 subtemplate_pos = potential_positions[SyncRand(potential_positions.size())];
880 potential_positions.erase(std::remove(potential_positions.begin(), potential_positions.end(), subtemplate_pos), potential_positions.end());
881
882 bool on_map = Map.Info.IsPointOnMap(subtemplate_pos, z) && Map.Info.IsPointOnMap(Vec2i(subtemplate_pos.x + subtemplate->Width - 1, subtemplate_pos.y + subtemplate->Height - 1), z);
883
884 bool on_subtemplate_area = false;
885 for (int x = 0; x < subtemplate->Width; ++x) {
886 for (int y = 0; y < subtemplate->Height; ++y) {
887 if (Map.IsPointInASubtemplateArea(subtemplate_pos + Vec2i(x, y), z)) {
888 on_subtemplate_area = true;
889 break;
890 }
891 }
892 if (on_subtemplate_area) {
893 break;
894 }
895 }
896
897 if (on_map && !on_subtemplate_area) {
898 found_location = true;
899 break;
900 }
901 }
902 }
903 else {
904 if (random) {
905 continue;
906 }
907 subtemplate_pos.x = map_start_pos.x + subtemplate_pos.x - template_start_pos.x;
908 subtemplate_pos.y = map_start_pos.y + subtemplate_pos.y - template_start_pos.y;
909 found_location = true;
910 }
911 }
912 else {
913 if (random) {
914 continue;
915 }
916 }
917
918 if (found_location) {
919 if (subtemplate_pos.x >= 0 && subtemplate_pos.y >= 0 && subtemplate_pos.x < Map.Info.MapWidths[z] && subtemplate_pos.y < Map.Info.MapHeights[z]) {
920 subtemplate->Apply(Vec2i(0, 0), subtemplate_pos, z);
921
922 Map.MapLayers[z]->SubtemplateAreas.push_back(std::tuple<Vec2i, Vec2i, CMapTemplate *>(subtemplate_pos, Vec2i(subtemplate_pos.x + subtemplate->Width - 1, subtemplate_pos.y + subtemplate->Height - 1), subtemplate));
923 }
924 }
925 }
926 }
927
928 /**
929 ** @brief Apply sites to the map
930 **
931 ** @param template_start_pos The start position of the map relative to the map template
932 ** @param map_start_pos The start position of the map template relative to the map
933 ** @param z The map layer
934 ** @param random Whether it is sites with a random position that should be applied, or ones with a fixed one
935 */
ApplySites(const Vec2i & template_start_pos,const Vec2i & map_start_pos,const int z,const bool random) const936 void CMapTemplate::ApplySites(const Vec2i &template_start_pos, const Vec2i &map_start_pos, const int z, const bool random) const
937 {
938 Vec2i map_end(std::min(Map.Info.MapWidths[z], map_start_pos.x + this->Width), std::min(Map.Info.MapHeights[z], map_start_pos.y + this->Height));
939
940 for (size_t site_index = 0; site_index < this->Sites.size(); ++site_index) {
941 CSite *site = this->Sites[site_index];
942
943 Vec2i site_raw_pos(site->Position);
944 Vec2i site_pos(map_start_pos + ((site_raw_pos - template_start_pos) * this->Scale));
945
946 Vec2i unit_offset((SettlementSiteUnitType->TileSize - 1) / 2);
947
948 if (random) {
949 if (site_raw_pos.x != -1 || site_raw_pos.y != -1) {
950 continue;
951 }
952 if (SettlementSiteUnitType) {
953 site_pos = Map.GenerateUnitLocation(SettlementSiteUnitType, nullptr, map_start_pos, map_end - Vec2i(1, 1), z);
954 site_pos += unit_offset;
955 }
956 } else {
957 if (site_raw_pos.x == -1 && site_raw_pos.y == -1) {
958 continue;
959 }
960 }
961
962 if (!Map.Info.IsPointOnMap(site_pos, z) || site_pos.x < map_start_pos.x || site_pos.y < map_start_pos.y) {
963 continue;
964 }
965
966 if (site->Major && SettlementSiteUnitType) { //add a settlement site for major sites
967 if (!UnitTypeCanBeAt(*SettlementSiteUnitType, site_pos - unit_offset, z) && Map.Info.IsPointOnMap(site_pos - unit_offset, z) && Map.Info.IsPointOnMap(site_pos - unit_offset + Vec2i(SettlementSiteUnitType->TileSize - 1), z)) {
968 fprintf(stderr, "The settlement site for \"%s\" should be placed on (%d, %d), but it cannot be there.\n", site->Ident.c_str(), site_raw_pos.x, site_raw_pos.y);
969 }
970 CUnit *unit = CreateUnit(site_pos - unit_offset, *SettlementSiteUnitType, &Players[PlayerNumNeutral], z, true);
971 unit->Settlement = site;
972 unit->Settlement->SiteUnit = unit;
973 Map.SiteUnits.push_back(unit);
974 }
975
976 for (size_t j = 0; j < site->HistoricalResources.size(); ++j) {
977 if (
978 (!CurrentCampaign && std::get<1>(site->HistoricalResources[j]).Year == 0 && std::get<1>(site->HistoricalResources[j]).Year == 0)
979 || (
980 CurrentCampaign && CurrentCampaign->StartDate.ContainsDate(std::get<0>(site->HistoricalResources[j]))
981 && (!CurrentCampaign->StartDate.ContainsDate(std::get<1>(site->HistoricalResources[j])) || std::get<1>(site->HistoricalResources[j]).Year == 0)
982 )
983 ) {
984 const CUnitType *type = std::get<2>(site->HistoricalResources[j]);
985 if (!type) {
986 fprintf(stderr, "Error in CMap::ApplySites (site ident \"%s\"): historical resource type is null.\n", site->Ident.c_str());
987 continue;
988 }
989 Vec2i unit_offset((type->TileSize - 1) / 2);
990 CUnit *unit = CreateResourceUnit(site_pos - unit_offset, *type, z, false); // don't generate unique resources when setting special properties, since for map templates unique resources are supposed to be explicitly indicated
991 if (std::get<3>(site->HistoricalResources[j])) {
992 unit->SetUnique(std::get<3>(site->HistoricalResources[j]));
993 }
994 int resource_quantity = std::get<4>(site->HistoricalResources[j]);
995 if (resource_quantity) { //set the resource_quantity after setting the unique unit, so that unique resources can be decreased in quantity over time
996 unit->SetResourcesHeld(resource_quantity);
997 unit->Variable[GIVERESOURCE_INDEX].Value = resource_quantity;
998 unit->Variable[GIVERESOURCE_INDEX].Max = resource_quantity;
999 unit->Variable[GIVERESOURCE_INDEX].Enable = 1;
1000 }
1001 }
1002 }
1003
1004 if (!CurrentCampaign) {
1005 continue;
1006 }
1007
1008 const CFaction *site_owner = nullptr;
1009 for (std::map<CDate, const CFaction *>::reverse_iterator owner_iterator = site->HistoricalOwners.rbegin(); owner_iterator != site->HistoricalOwners.rend(); ++owner_iterator) {
1010 if (CurrentCampaign->StartDate.ContainsDate(owner_iterator->first)) { // set the owner to the latest historical owner given the scenario's start date
1011 site_owner = owner_iterator->second;
1012 break;
1013 }
1014 }
1015
1016 if (!site_owner) {
1017 continue;
1018 }
1019
1020 CPlayer *player = GetOrAddFactionPlayer(site_owner);
1021
1022 if (!player) {
1023 continue;
1024 }
1025
1026 bool is_capital = false;
1027 for (int i = ((int) site_owner->HistoricalCapitals.size() - 1); i >= 0; --i) {
1028 if (CurrentCampaign->StartDate.ContainsDate(site_owner->HistoricalCapitals[i].first) || site_owner->HistoricalCapitals[i].first.Year == 0) {
1029 if (site_owner->HistoricalCapitals[i].second == site->Ident) {
1030 is_capital = true;
1031 }
1032 break;
1033 }
1034 }
1035
1036 if ((player->StartPos.x == 0 && player->StartPos.y == 0) || is_capital) {
1037 player->SetStartView(site_pos, z);
1038 }
1039
1040 CUnitType *pathway_type = nullptr;
1041 for (size_t j = 0; j < site->HistoricalBuildings.size(); ++j) {
1042 if (
1043 CurrentCampaign->StartDate.ContainsDate(std::get<0>(site->HistoricalBuildings[j]))
1044 && (!CurrentCampaign->StartDate.ContainsDate(std::get<1>(site->HistoricalBuildings[j])) || std::get<1>(site->HistoricalBuildings[j]).Year == 0)
1045 ) {
1046 int unit_type_id = -1;
1047 unit_type_id = PlayerRaces.GetFactionClassUnitType(site_owner->ID, std::get<2>(site->HistoricalBuildings[j]));
1048 if (unit_type_id == -1) {
1049 continue;
1050 }
1051 const CUnitType *type = UnitTypes[unit_type_id];
1052 if (type->TerrainType) {
1053 if ((type->TerrainType->Flags & MapFieldRoad) || (type->TerrainType->Flags & MapFieldRailroad)) {
1054 pathway_type = UnitTypes[unit_type_id];
1055 }
1056 }
1057 }
1058 }
1059
1060 bool first_building = true;
1061 for (size_t j = 0; j < site->HistoricalBuildings.size(); ++j) {
1062 if (
1063 CurrentCampaign->StartDate.ContainsDate(std::get<0>(site->HistoricalBuildings[j]))
1064 && (!CurrentCampaign->StartDate.ContainsDate(std::get<1>(site->HistoricalBuildings[j])) || std::get<1>(site->HistoricalBuildings[j]).Year == 0)
1065 ) {
1066 const CFaction *building_owner = std::get<4>(site->HistoricalBuildings[j]);
1067 int unit_type_id = -1;
1068 if (building_owner) {
1069 unit_type_id = PlayerRaces.GetFactionClassUnitType(building_owner->ID, std::get<2>(site->HistoricalBuildings[j]));
1070 } else {
1071 unit_type_id = PlayerRaces.GetFactionClassUnitType(site_owner->ID, std::get<2>(site->HistoricalBuildings[j]));
1072 }
1073 if (unit_type_id == -1) {
1074 continue;
1075 }
1076 const CUnitType *type = UnitTypes[unit_type_id];
1077 if (type->TerrainType) {
1078 continue;
1079 }
1080 if (type->BoolFlag[TOWNHALL_INDEX].value && !site->Major) {
1081 fprintf(stderr, "Error in CMap::ApplySites (site ident \"%s\"): site has a town hall, but isn't set as a major one.\n", site->Ident.c_str());
1082 continue;
1083 }
1084 Vec2i unit_offset((type->TileSize - 1) / 2);
1085 if (first_building) {
1086 if (!OnTopDetails(*type, nullptr) && !UnitTypeCanBeAt(*type, site_pos - unit_offset, z) && Map.Info.IsPointOnMap(site_pos - unit_offset, z) && Map.Info.IsPointOnMap(site_pos - unit_offset + Vec2i(type->TileSize - 1), z)) {
1087 fprintf(stderr, "The \"%s\" representing the minor site of \"%s\" should be placed on (%d, %d), but it cannot be there.\n", type->Ident.c_str(), site->Ident.c_str(), site_raw_pos.x, site_raw_pos.y);
1088 }
1089 }
1090 CUnit *unit = nullptr;
1091 if (building_owner) {
1092 CPlayer *building_player = GetOrAddFactionPlayer(building_owner);
1093 if (!building_player) {
1094 continue;
1095 }
1096 if (building_player->StartPos.x == 0 && building_player->StartPos.y == 0) {
1097 building_player->SetStartView(site_pos - unit_offset, z);
1098 }
1099 unit = CreateUnit(site_pos - unit_offset, *type, building_player, z, true);
1100 } else {
1101 unit = CreateUnit(site_pos - unit_offset, *type, player, z, true);
1102 }
1103 if (std::get<3>(site->HistoricalBuildings[j])) {
1104 unit->SetUnique(std::get<3>(site->HistoricalBuildings[j]));
1105 }
1106 if (first_building) {
1107 if (!type->BoolFlag[TOWNHALL_INDEX].value && !unit->Unique && (!building_owner || building_owner == site_owner)) { //if one building is representing a minor site, make it have the site's name
1108 unit->Name = site->GetCulturalName(site_owner->Civilization);
1109 }
1110 first_building = false;
1111 }
1112 if (type->BoolFlag[TOWNHALL_INDEX].value && (!building_owner || building_owner == site_owner)) {
1113 unit->UpdateBuildingSettlementAssignment();
1114 }
1115 if (pathway_type) {
1116 for (int x = unit->tilePos.x - 1; x < unit->tilePos.x + unit->Type->TileSize.x + 1; ++x) {
1117 for (int y = unit->tilePos.y - 1; y < unit->tilePos.y + unit->Type->TileSize.y + 1; ++y) {
1118 if (!Map.Info.IsPointOnMap(x, y, unit->MapLayer)) {
1119 continue;
1120 }
1121 CMapField &mf = *unit->MapLayer->Field(x, y);
1122 if (mf.Flags & MapFieldBuilding) { //this is a tile where the building itself is located, continue
1123 continue;
1124 }
1125 Vec2i pathway_pos(x, y);
1126 if (!UnitTypeCanBeAt(*pathway_type, pathway_pos, unit->MapLayer->ID)) {
1127 continue;
1128 }
1129
1130 mf.SetTerrain(pathway_type->TerrainType);
1131 }
1132 }
1133 }
1134 }
1135 }
1136
1137 for (size_t j = 0; j < site->HistoricalUnits.size(); ++j) {
1138 if (
1139 CurrentCampaign->StartDate.ContainsDate(std::get<0>(site->HistoricalUnits[j]))
1140 && (!CurrentCampaign->StartDate.ContainsDate(std::get<1>(site->HistoricalUnits[j])) || std::get<1>(site->HistoricalUnits[j]).Year == 0)
1141 ) {
1142 int unit_quantity = std::get<3>(site->HistoricalUnits[j]);
1143
1144 if (unit_quantity > 0) {
1145 const CUnitType *type = std::get<2>(site->HistoricalUnits[j]);
1146 if (type->BoolFlag[ORGANIC_INDEX].value) {
1147 unit_quantity = std::max(1, unit_quantity / PopulationPerUnit); //each organic unit represents 1,000 people
1148 }
1149
1150 CPlayer *unit_player = nullptr;
1151 const CFaction *unit_owner = std::get<4>(site->HistoricalUnits[j]);
1152 if (unit_owner) {
1153 unit_player = GetOrAddFactionPlayer(unit_owner);
1154 if (!unit_player) {
1155 continue;
1156 }
1157 if (unit_player->StartPos.x == 0 && unit_player->StartPos.y == 0) {
1158 unit_player->SetStartView(site_pos, z);
1159 }
1160 } else {
1161 unit_player = player;
1162 }
1163 Vec2i unit_offset((type->TileSize - 1) / 2);
1164
1165 for (int j = 0; j < unit_quantity; ++j) {
1166 CUnit *unit = CreateUnit(site_pos - unit_offset, *type, unit_player, z);
1167 if (!type->BoolFlag[HARVESTER_INDEX].value) { // make non-worker units not have an active AI
1168 unit->Active = 0;
1169 unit_player->ChangeUnitTypeAiActiveCount(type, -1);
1170 }
1171 }
1172 }
1173 }
1174 }
1175 }
1176 }
1177
ApplyConnectors(Vec2i template_start_pos,Vec2i map_start_pos,int z,bool random) const1178 void CMapTemplate::ApplyConnectors(Vec2i template_start_pos, Vec2i map_start_pos, int z, bool random) const
1179 {
1180 Vec2i map_end(std::min(Map.Info.MapWidths[z], map_start_pos.x + this->Width), std::min(Map.Info.MapHeights[z], map_start_pos.y + this->Height));
1181
1182 for (size_t i = 0; i < this->PlaneConnectors.size(); ++i) {
1183 const CUnitType *type = std::get<1>(this->PlaneConnectors[i]);
1184 Vec2i unit_raw_pos(std::get<0>(this->PlaneConnectors[i]));
1185 Vec2i unit_pos(map_start_pos + unit_raw_pos - template_start_pos);
1186 Vec2i unit_offset((type->TileSize - 1) / 2);
1187 if (random) {
1188 if (unit_raw_pos.x != -1 || unit_raw_pos.y != -1) {
1189 continue;
1190 }
1191 unit_pos = Map.GenerateUnitLocation(type, nullptr, map_start_pos, map_end - Vec2i(1, 1), z);
1192 unit_pos += unit_offset;
1193 }
1194 if (!Map.Info.IsPointOnMap(unit_pos, z) || unit_pos.x < map_start_pos.x || unit_pos.y < map_start_pos.y) {
1195 continue;
1196 }
1197
1198 if (!OnTopDetails(*type, nullptr) && !UnitTypeCanBeAt(*type, unit_pos - unit_offset, z) && Map.Info.IsPointOnMap(unit_pos - unit_offset, z) && Map.Info.IsPointOnMap(unit_pos - unit_offset + Vec2i(type->TileSize - 1), z)) {
1199 fprintf(stderr, "Unit \"%s\" should be placed on (%d, %d) for map template \"%s\", but it cannot be there.\n", type->Ident.c_str(), unit_raw_pos.x, unit_raw_pos.y, this->Ident.c_str());
1200 }
1201
1202 CUnit *unit = CreateUnit(unit_pos - unit_offset, *type, &Players[PlayerNumNeutral], z, true);
1203 if (std::get<3>(this->PlaneConnectors[i])) {
1204 unit->SetUnique(std::get<3>(this->PlaneConnectors[i]));
1205 }
1206 Map.MapLayers[z]->LayerConnectors.push_back(unit);
1207 for (size_t second_z = 0; second_z < Map.MapLayers.size(); ++second_z) {
1208 bool found_other_connector = false;
1209 if (Map.MapLayers[second_z]->Plane == std::get<2>(this->PlaneConnectors[i])) {
1210 for (size_t j = 0; j < Map.MapLayers[second_z]->LayerConnectors.size(); ++j) {
1211 if (Map.MapLayers[second_z]->LayerConnectors[j]->Type == unit->Type && Map.MapLayers[second_z]->LayerConnectors[j]->Unique == unit->Unique && Map.MapLayers[second_z]->LayerConnectors[j]->ConnectingDestination == nullptr) {
1212 Map.MapLayers[second_z]->LayerConnectors[j]->ConnectingDestination = unit;
1213 unit->ConnectingDestination = Map.MapLayers[second_z]->LayerConnectors[j];
1214 found_other_connector = true;
1215 break;
1216 }
1217 }
1218 }
1219 if (found_other_connector) {
1220 break;
1221 }
1222 }
1223 }
1224
1225 for (size_t i = 0; i < this->WorldConnectors.size(); ++i) {
1226 const CUnitType *type = std::get<1>(this->WorldConnectors[i]);
1227 Vec2i unit_raw_pos(std::get<0>(this->WorldConnectors[i]));
1228 Vec2i unit_pos(map_start_pos + unit_raw_pos - template_start_pos);
1229 Vec2i unit_offset((type->TileSize - 1) / 2);
1230 if (random) {
1231 if (unit_raw_pos.x != -1 || unit_raw_pos.y != -1) {
1232 continue;
1233 }
1234 unit_pos = Map.GenerateUnitLocation(type, nullptr, map_start_pos, map_end - Vec2i(1, 1), z);
1235 unit_pos += unit_offset;
1236 }
1237 if (!Map.Info.IsPointOnMap(unit_pos, z) || unit_pos.x < map_start_pos.x || unit_pos.y < map_start_pos.y) {
1238 continue;
1239 }
1240
1241 if (!OnTopDetails(*type, nullptr) && !UnitTypeCanBeAt(*type, unit_pos - unit_offset, z) && Map.Info.IsPointOnMap(unit_pos - unit_offset, z) && Map.Info.IsPointOnMap(unit_pos - unit_offset + Vec2i(type->TileSize - 1), z)) {
1242 fprintf(stderr, "Unit \"%s\" should be placed on (%d, %d) for map template \"%s\", but it cannot be there.\n", type->Ident.c_str(), unit_raw_pos.x, unit_raw_pos.y, this->Ident.c_str());
1243 }
1244
1245 CUnit *unit = CreateUnit(unit_pos - unit_offset, *type, &Players[PlayerNumNeutral], z, true);
1246 if (std::get<3>(this->WorldConnectors[i])) {
1247 unit->SetUnique(std::get<3>(this->WorldConnectors[i]));
1248 }
1249 Map.MapLayers[z]->LayerConnectors.push_back(unit);
1250 for (size_t second_z = 0; second_z < Map.MapLayers.size(); ++second_z) {
1251 bool found_other_connector = false;
1252 if (Map.MapLayers[second_z]->World == std::get<2>(this->WorldConnectors[i])) {
1253 for (size_t j = 0; j < Map.MapLayers[second_z]->LayerConnectors.size(); ++j) {
1254 if (Map.MapLayers[second_z]->LayerConnectors[j]->Type == unit->Type && Map.MapLayers[second_z]->LayerConnectors[j]->Unique == unit->Unique && Map.MapLayers[second_z]->LayerConnectors[j]->ConnectingDestination == nullptr) {
1255 Map.MapLayers[second_z]->LayerConnectors[j]->ConnectingDestination = unit;
1256 unit->ConnectingDestination = Map.MapLayers[second_z]->LayerConnectors[j];
1257 found_other_connector = true;
1258 break;
1259 }
1260 }
1261 }
1262 if (found_other_connector) {
1263 break;
1264 }
1265 }
1266 }
1267
1268 for (size_t i = 0; i < this->SurfaceLayerConnectors.size(); ++i) {
1269 const CUnitType *type = std::get<1>(this->SurfaceLayerConnectors[i]);
1270 const int surface_layer = std::get<2>(this->SurfaceLayerConnectors[i]);
1271 CUniqueItem *unique = std::get<3>(this->SurfaceLayerConnectors[i]);
1272 Vec2i unit_raw_pos(std::get<0>(this->SurfaceLayerConnectors[i]));
1273 Vec2i unit_pos(map_start_pos + unit_raw_pos - template_start_pos);
1274 Vec2i unit_offset((type->TileSize - 1) / 2);
1275
1276 //add the connecting destination
1277 const CMapTemplate *other_template = nullptr;
1278 if (surface_layer == (this->SurfaceLayer + 1)) {
1279 other_template = this->LowerTemplate;
1280 }
1281 else if (surface_layer == (this->SurfaceLayer - 1)) {
1282 other_template = this->UpperTemplate;
1283 }
1284 if (!other_template) {
1285 continue; //surface layer connectors must lead to an adjacent surface layer
1286 }
1287
1288 if (random) {
1289 if (unit_raw_pos.x != -1 || unit_raw_pos.y != -1) {
1290 continue;
1291 }
1292
1293 bool already_implemented = false; //the connector could already have been implemented if it inherited its position from the connector in the destination layer (if the destination layer's map template was applied first)
1294 std::vector<CUnit *> other_layer_connectors = Map.GetMapTemplateLayerConnectors(other_template);
1295 for (const CUnit *connector : other_layer_connectors) {
1296 if (connector->Type == type && connector->Unique == unique && connector->ConnectingDestination != nullptr && connector->ConnectingDestination->MapLayer->Plane == this->Plane && connector->ConnectingDestination->MapLayer->World == this->World && connector->ConnectingDestination->MapLayer->SurfaceLayer == this->SurfaceLayer) {
1297 already_implemented = true;
1298 break;
1299 }
1300 }
1301
1302 if (already_implemented) {
1303 continue;
1304 }
1305
1306 unit_pos = Map.GenerateUnitLocation(type, nullptr, map_start_pos, map_end - Vec2i(1, 1), z);
1307 unit_pos += unit_offset;
1308 } else {
1309 if (unit_raw_pos.x == -1 && unit_raw_pos.y == -1) {
1310 //if the upper/lower layer has a surface layer connector to this layer that has no connecting destination, and this connector leads to that surface layer, place this connection in the same position as the other one
1311 std::vector<CUnit *> other_layer_connectors = Map.GetMapTemplateLayerConnectors(other_template);
1312 for (const CUnit *potential_connector : other_layer_connectors) {
1313 if (potential_connector->Type == type && potential_connector->Unique == unique && potential_connector->ConnectingDestination == nullptr) {
1314 unit_pos = potential_connector->GetTileCenterPos();
1315 break;
1316 }
1317 }
1318 }
1319 }
1320 if (!Map.Info.IsPointOnMap(unit_pos, z) || unit_pos.x < map_start_pos.x || unit_pos.y < map_start_pos.y) {
1321 continue;
1322 }
1323
1324 if (!OnTopDetails(*type, nullptr) && !UnitTypeCanBeAt(*type, unit_pos - unit_offset, z) && Map.Info.IsPointOnMap(unit_pos - unit_offset, z) && Map.Info.IsPointOnMap(unit_pos - unit_offset + Vec2i(type->TileSize - 1), z) && unit_raw_pos.x != -1 && unit_raw_pos.y != -1) {
1325 fprintf(stderr, "Unit \"%s\" should be placed on (%d, %d) for map template \"%s\", but it cannot be there.\n", type->Ident.c_str(), unit_raw_pos.x, unit_raw_pos.y, this->Ident.c_str());
1326 }
1327
1328 CUnit *unit = CreateUnit(unit_pos - unit_offset, *type, &Players[PlayerNumNeutral], z, true);
1329 if (unique) {
1330 unit->SetUnique(unique);
1331 }
1332 Map.MapLayers[z]->LayerConnectors.push_back(unit);
1333
1334 //get the nearest compatible connector in the target map layer / template
1335 std::vector<CUnit *> other_layer_connectors = Map.GetMapTemplateLayerConnectors(other_template);
1336 CUnit *best_layer_connector = nullptr;
1337 int best_distance = -1;
1338 for (size_t j = 0; j < other_layer_connectors.size(); ++j) {
1339 CUnit *potential_connector = other_layer_connectors[j];
1340
1341 if (potential_connector->Type == type && potential_connector->Unique == unique && potential_connector->ConnectingDestination == nullptr) {
1342 int distance = potential_connector->MapDistanceTo(unit->GetTileCenterPos(), potential_connector->MapLayer->ID);
1343 if (best_distance == -1 || distance < best_distance) {
1344 best_layer_connector = potential_connector;
1345 best_distance = distance;
1346 if (distance == 0) {
1347 break;
1348 }
1349 }
1350 }
1351 }
1352
1353 if (best_layer_connector) {
1354 best_layer_connector->ConnectingDestination = unit;
1355 unit->ConnectingDestination = best_layer_connector;
1356 }
1357 }
1358 }
1359
ApplyUnits(const Vec2i & template_start_pos,const Vec2i & map_start_pos,const int z,const bool random) const1360 void CMapTemplate::ApplyUnits(const Vec2i &template_start_pos, const Vec2i &map_start_pos, const int z, const bool random) const
1361 {
1362 Vec2i map_end(std::min(Map.Info.MapWidths[z], map_start_pos.x + this->Width), std::min(Map.Info.MapHeights[z], map_start_pos.y + this->Height));
1363
1364 for (size_t i = 0; i < this->Units.size(); ++i) {
1365 Vec2i unit_raw_pos(std::get<0>(this->Units[i]));
1366 Vec2i unit_pos(map_start_pos + unit_raw_pos - template_start_pos);
1367 const CUnitType *type = std::get<1>(this->Units[i]);
1368 Vec2i unit_offset((type->TileSize - 1) / 2);
1369 if (random) {
1370 if (unit_raw_pos.x != -1 || unit_raw_pos.y != -1) {
1371 continue;
1372 }
1373 unit_pos = Map.GenerateUnitLocation(type, std::get<2>(this->Units[i]), map_start_pos, map_end - Vec2i(1, 1), z);
1374 unit_pos += unit_offset;
1375 }
1376 if (!Map.Info.IsPointOnMap(unit_pos, z) || unit_pos.x < map_start_pos.x || unit_pos.y < map_start_pos.y) {
1377 continue;
1378 }
1379
1380 if (
1381 (!CurrentCampaign && std::get<3>(this->Units[i]).Year == 0 && std::get<4>(this->Units[i]).Year == 0)
1382 || (
1383 CurrentCampaign && (std::get<3>(this->Units[i]).Year == 0 || CurrentCampaign->StartDate.ContainsDate(std::get<3>(this->Units[i])))
1384 && (std::get<4>(this->Units[i]).Year == 0 || (!CurrentCampaign->StartDate.ContainsDate(std::get<4>(this->Units[i]))))
1385 )
1386 ) {
1387 CPlayer *player = nullptr;
1388 if (std::get<2>(this->Units[i])) {
1389 if (!CurrentCampaign) { //only apply neutral units for when applying map templates for non-campaign/scenario maps
1390 continue;
1391 }
1392 player = GetOrAddFactionPlayer(std::get<2>(this->Units[i]));
1393 if (!player) {
1394 continue;
1395 }
1396 if (player->StartPos.x == 0 && player->StartPos.y == 0) {
1397 player->SetStartView(unit_pos, z);
1398 }
1399 } else {
1400 player = &Players[PlayerNumNeutral];
1401 }
1402
1403 CUnit *unit = CreateUnit(unit_pos - unit_offset, *std::get<1>(this->Units[i]), player, z, type->BoolFlag[BUILDING_INDEX].value && type->TileSize.x > 1 && type->TileSize.y > 1);
1404 if (!type->BoolFlag[BUILDING_INDEX].value && !type->BoolFlag[HARVESTER_INDEX].value) { // make non-building, non-harvester units not have an active AI
1405 unit->Active = 0;
1406 player->ChangeUnitTypeAiActiveCount(type, -1);
1407 }
1408 if (std::get<5>(this->Units[i])) {
1409 unit->SetUnique(std::get<5>(this->Units[i]));
1410 }
1411 }
1412 }
1413
1414 if (!CurrentCampaign) {
1415 return;
1416 }
1417
1418 for (size_t i = 0; i < this->Heroes.size(); ++i) {
1419 Vec2i unit_raw_pos(std::get<0>(this->Heroes[i]));
1420 Vec2i unit_pos(map_start_pos + unit_raw_pos - template_start_pos);
1421 CCharacter *hero = std::get<1>(this->Heroes[i]);
1422 Vec2i unit_offset((hero->Type->TileSize - 1) / 2);
1423 if (random) {
1424 if (unit_raw_pos.x != -1 || unit_raw_pos.y != -1) {
1425 continue;
1426 }
1427 unit_pos = Map.GenerateUnitLocation(hero->Type, std::get<2>(this->Heroes[i]), map_start_pos, map_end - Vec2i(1, 1), z);
1428 unit_pos += unit_offset;
1429 }
1430 if (!Map.Info.IsPointOnMap(unit_pos, z) || unit_pos.x < map_start_pos.x || unit_pos.y < map_start_pos.y) {
1431 continue;
1432 }
1433
1434 if ((!CurrentCampaign || std::get<3>(this->Heroes[i]).Year == 0 || CurrentCampaign->StartDate.ContainsDate(std::get<3>(this->Heroes[i]))) && (std::get<4>(this->Heroes[i]).Year == 0 || !CurrentCampaign->StartDate.ContainsDate(std::get<4>(this->Heroes[i])))) {
1435 CPlayer *player = nullptr;
1436 if (std::get<2>(this->Heroes[i])) {
1437 player = GetOrAddFactionPlayer(std::get<2>(this->Heroes[i]));
1438 if (!player) {
1439 continue;
1440 }
1441 if (player->StartPos.x == 0 && player->StartPos.y == 0) {
1442 player->SetStartView(unit_pos, z);
1443 }
1444 } else {
1445 player = &Players[PlayerNumNeutral];
1446 }
1447 CUnit *unit = CreateUnit(unit_pos - unit_offset, *hero->Type, player, z);
1448 unit->SetCharacter(hero->Ident);
1449 if (!unit->Type->BoolFlag[BUILDING_INDEX].value && !unit->Type->BoolFlag[HARVESTER_INDEX].value) { // make non-building, non-harvester units not have an active AI
1450 unit->Active = 0;
1451 player->ChangeUnitTypeAiActiveCount(hero->Type, -1);
1452 }
1453 }
1454 }
1455
1456 for (CHistoricalUnit *historical_unit : CHistoricalUnit::HistoricalUnits) {
1457 if (historical_unit->StartDate.Year == 0 || !CurrentCampaign->StartDate.ContainsDate(historical_unit->StartDate) || CurrentCampaign->StartDate.ContainsDate(historical_unit->EndDate)) { //historical units aren't implemented if their date isn't set
1458 continue;
1459 }
1460
1461 CFaction *unit_faction = historical_unit->Faction;
1462
1463 CPlayer *unit_player = unit_faction ? GetFactionPlayer(unit_faction) : nullptr;
1464
1465 bool in_another_map_template = false;
1466 Vec2i unit_pos = this->GetBestLocationMapPosition(historical_unit->HistoricalLocations, in_another_map_template, template_start_pos, map_start_pos, false);
1467
1468 if (in_another_map_template) {
1469 continue;
1470 }
1471
1472 if (unit_pos.x == -1 || unit_pos.y == -1) {
1473 if (!random) { //apply units whose position is that of a randomly-placed site, or that of their player's start position, together with randomly-placed units
1474 continue;
1475 }
1476
1477 unit_pos = this->GetBestLocationMapPosition(historical_unit->HistoricalLocations, in_another_map_template, template_start_pos, map_start_pos, true);
1478
1479 if (unit_pos.x == -1 || unit_pos.y == -1) {
1480 unit_pos = Map.GenerateUnitLocation(historical_unit->UnitType, unit_faction, map_start_pos, map_end - Vec2i(1, 1), z);
1481 unit_pos += historical_unit->UnitType->GetTileCenterPosOffset();
1482 }
1483 } else {
1484 if (random) {
1485 continue;
1486 }
1487 }
1488
1489 if (!Map.Info.IsPointOnMap(unit_pos, z) || unit_pos.x < map_start_pos.x || unit_pos.y < map_start_pos.y) { //units whose faction hasn't been created already and who don't have a valid historical location set won't be created
1490 continue;
1491 }
1492
1493 if (unit_faction) {
1494 unit_player = GetOrAddFactionPlayer(unit_faction);
1495 if (!unit_player) {
1496 continue;
1497 }
1498 if (unit_player->StartPos.x == 0 && unit_player->StartPos.y == 0) {
1499 unit_player->SetStartView(unit_pos, z);
1500 }
1501 } else {
1502 unit_player = &Players[PlayerNumNeutral];
1503 }
1504 for (int i = 0; i < historical_unit->Quantity; ++i) {
1505 CUnit *unit = CreateUnit(unit_pos - historical_unit->UnitType->GetTileCenterPosOffset(), *historical_unit->UnitType, unit_player, z);
1506 }
1507 }
1508
1509 for (CCharacter *character : CCharacter::Characters) {
1510 if (!character->CanAppear()) {
1511 continue;
1512 }
1513
1514 if (character->Faction == nullptr && !character->Type->BoolFlag[FAUNA_INDEX].value) { //only fauna "heroes" may have no faction
1515 continue;
1516 }
1517
1518 if (character->StartDate.Year == 0 || !CurrentCampaign->StartDate.ContainsDate(character->StartDate) || CurrentCampaign->StartDate.ContainsDate(character->DeathDate)) { //contrary to other elements, heroes aren't implemented if their date isn't set
1519 continue;
1520 }
1521
1522 CFaction *hero_faction = character->Faction;
1523 for (int i = ((int) character->HistoricalFactions.size() - 1); i >= 0; --i) {
1524 if (CurrentCampaign->StartDate.ContainsDate(character->HistoricalFactions[i].first)) {
1525 hero_faction = character->HistoricalFactions[i].second;
1526 break;
1527 }
1528 }
1529
1530 CPlayer *hero_player = hero_faction ? GetFactionPlayer(hero_faction) : nullptr;
1531
1532 bool in_another_map_template = false;
1533 Vec2i hero_pos = this->GetBestLocationMapPosition(character->HistoricalLocations, in_another_map_template, template_start_pos, map_start_pos, false);
1534
1535 if (in_another_map_template) {
1536 continue;
1537 }
1538
1539 if (hero_pos.x == -1 || hero_pos.y == -1) {
1540 if (!random) { //apply heroes whose position is that of a randomly-placed site, or that of their player's start position, together with randomly-placed units
1541 continue;
1542 }
1543
1544 hero_pos = this->GetBestLocationMapPosition(character->HistoricalLocations, in_another_map_template, template_start_pos, map_start_pos, true);
1545
1546 if ((hero_pos.x == -1 || hero_pos.y == -1) && hero_player && hero_player->StartMapLayer == z) {
1547 hero_pos = hero_player->StartPos;
1548 }
1549 } else {
1550 if (random) {
1551 continue;
1552 }
1553 }
1554
1555 if (!Map.Info.IsPointOnMap(hero_pos, z) || hero_pos.x < map_start_pos.x || hero_pos.y < map_start_pos.y) { //heroes whose faction hasn't been created already and who don't have a valid historical location set won't be created
1556 continue;
1557 }
1558
1559 if (hero_faction) {
1560 hero_player = GetOrAddFactionPlayer(hero_faction);
1561 if (!hero_player) {
1562 continue;
1563 }
1564 if (hero_player->StartPos.x == 0 && hero_player->StartPos.y == 0) {
1565 hero_player->SetStartView(hero_pos, z);
1566 }
1567 } else {
1568 hero_player = &Players[PlayerNumNeutral];
1569 }
1570 CUnit *unit = CreateUnit(hero_pos - character->Type->GetTileCenterPosOffset(), *character->Type, hero_player, z);
1571 unit->SetCharacter(character->Ident);
1572 unit->Active = 0;
1573 hero_player->ChangeUnitTypeAiActiveCount(character->Type, -1);
1574 }
1575 }
1576
1577 /**
1578 ** @brief Get whether this map template is a subtemplate area of another one
1579 **
1580 ** @return True if it is a subtemplate area, or false otherwise
1581 */
IsSubtemplateArea() const1582 bool CMapTemplate::IsSubtemplateArea() const
1583 {
1584 return this->MainTemplate != nullptr;
1585 }
1586
1587 /**
1588 ** @brief Get the top map template for this one
1589 **
1590 ** @return The topmost map template for this one (which can be itself if it isn't a subtemplate area)
1591 */
GetTopMapTemplate() const1592 const CMapTemplate *CMapTemplate::GetTopMapTemplate() const
1593 {
1594 if (this->MainTemplate != nullptr) {
1595 return this->MainTemplate->GetTopMapTemplate();
1596 } else {
1597 return this;
1598 }
1599 }
1600
1601 /**
1602 ** @brief Gets the best map position from a list of historical locations
1603 **
1604 ** @param historical_location_list The list of historical locations
1605 ** @param in_another_map_template This is set to true if there is a valid position, but it is in another map templa
1606 **
1607 ** @return The best position if found, or an invalid one otherwise
1608 */
GetBestLocationMapPosition(const std::vector<CHistoricalLocation * > & historical_location_list,bool & in_another_map_template,const Vec2i & template_start_pos,const Vec2i & map_start_pos,const bool random) const1609 Vec2i CMapTemplate::GetBestLocationMapPosition(const std::vector<CHistoricalLocation *> &historical_location_list, bool &in_another_map_template, const Vec2i &template_start_pos, const Vec2i &map_start_pos, const bool random) const
1610 {
1611 Vec2i pos(-1, -1);
1612 in_another_map_template = false;
1613
1614 for (int i = ((int) historical_location_list.size() - 1); i >= 0; --i) {
1615 CHistoricalLocation *historical_location = historical_location_list[i];
1616 if (CurrentCampaign->StartDate.ContainsDate(historical_location->Date)) {
1617 if (historical_location->MapTemplate == this) {
1618 if (historical_location->Position.x != -1 && historical_location->Position.y != -1) { //historical unit position, could also have been inherited from a site with a fixed position
1619 pos = map_start_pos + historical_location->Position - template_start_pos;
1620 } else if (random) {
1621 if (historical_location->Site != nullptr && historical_location->Site->SiteUnit != nullptr) { //sites with random positions will have no valid stored fixed position, but will have had a site unit randomly placed; use that site unit's position instead for this unit then
1622 pos = historical_location->Site->SiteUnit->GetTileCenterPos();
1623 }
1624 }
1625 } else {
1626 in_another_map_template = true;
1627 }
1628 break;
1629 }
1630 }
1631
1632 return pos;
1633 }
1634
1635 /**
1636 ** @brief Process data provided by a configuration file
1637 **
1638 ** @param config_data The configuration data
1639 */
ProcessConfigData(const CConfigData * config_data)1640 void CGeneratedTerrain::ProcessConfigData(const CConfigData *config_data)
1641 {
1642 for (size_t i = 0; i < config_data->Properties.size(); ++i) {
1643 std::string key = config_data->Properties[i].first;
1644 std::string value = config_data->Properties[i].second;
1645
1646 if (key == "terrain_type") {
1647 value = FindAndReplaceString(value, "_", "-");
1648 this->TerrainType = CTerrainType::GetTerrainType(value);
1649 } else if (key == "seed_count") {
1650 this->SeedCount = std::stoi(value);
1651 } else if (key == "expansion_chance") {
1652 this->ExpansionChance = std::stoi(value);
1653 } else if (key == "max_percent") {
1654 this->MaxPercent = std::stoi(value);
1655 } else if (key == "use_existing_as_seeds") {
1656 this->UseExistingAsSeeds = StringToBool(value);
1657 } else if (key == "use_subtemplate_borders_as_seeds") {
1658 this->UseSubtemplateBordersAsSeeds = StringToBool(value);
1659 } else if (key == "target_terrain_type") {
1660 value = FindAndReplaceString(value, "_", "-");
1661 const CTerrainType *target_terrain_type = CTerrainType::GetTerrainType(value);
1662 if (target_terrain_type) {
1663 this->TargetTerrainTypes.push_back(target_terrain_type);
1664 }
1665 } else {
1666 fprintf(stderr, "Invalid generated terrain property: \"%s\".\n", key.c_str());
1667 }
1668 }
1669
1670 if (!this->TerrainType) {
1671 fprintf(stderr, "Generated terrain has no terrain type.\n");
1672 }
1673 }
1674
1675 /**
1676 ** @brief Get whether the terrain generation can use the given tile as a seed
1677 **
1678 ** @param tile The tile
1679 **
1680 ** @return True if the tile can be used as a seed, or false otherwise
1681 */
CanUseTileAsSeed(const CMapField * tile) const1682 bool CGeneratedTerrain::CanUseTileAsSeed(const CMapField *tile) const
1683 {
1684 const CTerrainType *top_terrain = tile->GetTopTerrain();
1685
1686 if (top_terrain == this->TerrainType) { //top terrain is the same as the one for the generation, so the tile can be used as a seed
1687 return true;
1688 }
1689
1690 if (this->TerrainType == tile->Terrain && std::find(this->TargetTerrainTypes.begin(), this->TargetTerrainTypes.end(), top_terrain) == this->TargetTerrainTypes.end()) { //the tile's base terrain is the same as the one for the generation, and its overlay terrain is not a target for the generation
1691 return true;
1692 }
1693
1694 return false;
1695 }
1696
1697 /**
1698 ** @brief Get whether the terrain can be generated on the given tile
1699 **
1700 ** @param tile The tile
1701 **
1702 ** @return True if the terrain can be generated on the tile, or false otherwise
1703 */
CanGenerateOnTile(const CMapField * tile) const1704 bool CGeneratedTerrain::CanGenerateOnTile(const CMapField *tile) const
1705 {
1706 if (this->TerrainType->Overlay) {
1707 if (std::find(this->TargetTerrainTypes.begin(), this->TargetTerrainTypes.end(), tile->GetTopTerrain()) == this->TargetTerrainTypes.end()) { //disallow generating over terrains that aren't a target for the generation
1708 return false;
1709 }
1710 } else {
1711 if (
1712 std::find(this->TargetTerrainTypes.begin(), this->TargetTerrainTypes.end(), tile->GetTopTerrain()) == this->TargetTerrainTypes.end()
1713 && std::find(this->TargetTerrainTypes.begin(), this->TargetTerrainTypes.end(), tile->Terrain) == this->TargetTerrainTypes.end()
1714 ) {
1715 return false;
1716 }
1717
1718 if ( //don't allow generating the terrain on the tile if it is a base terrain, and putting it there would destroy an overlay terrain that isn't a target of the generation
1719 tile->OverlayTerrain
1720 && !this->CanRemoveTileOverlayTerrain(tile)
1721 && std::find(tile->OverlayTerrain->BaseTerrainTypes.begin(), tile->OverlayTerrain->BaseTerrainTypes.end(), this->TerrainType) == tile->OverlayTerrain->BaseTerrainTypes.end()
1722 ) {
1723 return false;
1724 }
1725
1726 if (std::find(this->TerrainType->BorderTerrains.begin(), this->TerrainType->BorderTerrains.end(), tile->Terrain) == this->TerrainType->BorderTerrains.end()) { //don't allow generating on the tile if it can't be a border terrain to the terrain we want to generate
1727 return false;
1728 }
1729 }
1730
1731 return true;
1732 }
1733
1734 /**
1735 ** @brief Get whether the tile can be a part of an expansion
1736 **
1737 ** @param tile The tile
1738 **
1739 ** @return True if the tile can be part of an expansion, or false otherwise
1740 */
CanTileBePartOfExpansion(const CMapField * tile) const1741 bool CGeneratedTerrain::CanTileBePartOfExpansion(const CMapField *tile) const
1742 {
1743 if (this->CanGenerateOnTile(tile)) {
1744 return true;
1745 }
1746
1747 if (this->TerrainType == tile->GetTopTerrain()) {
1748 return true;
1749 }
1750
1751 if (!this->TerrainType->Overlay) {
1752 if (this->TerrainType == tile->Terrain) {
1753 return true;
1754 }
1755 }
1756
1757 return false;
1758 }
1759
1760 /**
1761 ** @brief Get whether the terrain generation can remove the tile's overlay terrain
1762 **
1763 ** @param tile The tile
1764 **
1765 ** @return True if the terrain generation can remove the tile's overlay terrain, or false otherwise
1766 */
CanRemoveTileOverlayTerrain(const CMapField * tile) const1767 bool CGeneratedTerrain::CanRemoveTileOverlayTerrain(const CMapField *tile) const
1768 {
1769 if (std::find(this->TargetTerrainTypes.begin(), this->TargetTerrainTypes.end(), tile->OverlayTerrain) == this->TargetTerrainTypes.end()) {
1770 return false;
1771 }
1772
1773 return true;
1774 }
1775