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