1 /*
2 	C-Dogs SDL
3 	A port of the legendary (and fun) action/arcade cdogs.
4 	Copyright (C) 1995 Ronny Wester
5 	Copyright (C) 2003 Jeremy Chin
6 	Copyright (C) 2003-2007 Lucas Martin-King
7 
8 	This program is free software; you can redistribute it and/or modify
9 	it under the terms of the GNU General Public License as published by
10 	the Free Software Foundation; either version 2 of the License, or
11 	(at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 	GNU General Public License for more details.
17 
18 	You should have received a copy of the GNU General Public License
19 	along with this program; if not, write to the Free Software
20 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 
22 	This file incorporates work covered by the following copyright and
23 	permission notice:
24 
25 	Copyright (c) 2013-2014, 2017-2021 Cong Xu
26 	All rights reserved.
27 
28 	Redistribution and use in source and binary forms, with or without
29 	modification, are permitted provided that the following conditions are met:
30 
31 	Redistributions of source code must retain the above copyright notice, this
32 	list of conditions and the following disclaimer.
33 	Redistributions in binary form must reproduce the above copyright notice,
34 	this list of conditions and the following disclaimer in the documentation
35 	and/or other materials provided with the distribution.
36 
37 	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38 	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40 	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
41 	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43 	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44 	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45 	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46 	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47 	POSSIBILITY OF SUCH DAMAGE.
48 */
49 #include "map_build.h"
50 
51 #include "actors.h"
52 #include "collision/collision.h"
53 #include "door.h"
54 #include "log.h"
55 #include "map_cave.h"
56 #include "map_classic.h"
57 #include "map_interior.h"
58 #include "map_static.h"
59 #include "net_util.h"
60 #include "objs.h"
61 
62 #define COLLECTABLE_W 4
63 #define COLLECTABLE_H 3
64 #define EXIT_WIDTH 8
65 #define EXIT_HEIGHT 8
66 
67 static void MapSetupTilesAndWalls(MapBuilder *mb);
68 static void MapSetupDoors(MapBuilder *mb);
69 static void MapAddDrains(MapBuilder *mb);
70 static void MapGenerateRandomExitArea(Map *map, const int mission);
MapBuild(Map * m,const Mission * mission,const Campaign * co,const int missionIndex)71 void MapBuild(
72 	Map *m, const Mission *mission, const Campaign *co, const int missionIndex)
73 {
74 	MapBuilder mb;
75 	MapBuilderInit(&mb, m, mission, co);
76 	MapInit(mb.Map, mb.mission->Size);
77 
78 	// Re-seed RNG so results are consistent
79 	CampaignSeedRandom(co);
80 
81 	switch (mb.mission->Type)
82 	{
83 	case MAPTYPE_CLASSIC:
84 		MapClassicLoad(&mb);
85 		break;
86 	case MAPTYPE_STATIC:
87 		MapStaticLoad(&mb);
88 		break;
89 	case MAPTYPE_CAVE:
90 		MapCaveLoad(&mb);
91 		break;
92 	case MAPTYPE_INTERIOR:
93 		MapInteriorLoad(&mb, missionIndex);
94 		break;
95 	default:
96 		CASSERT(false, "unknown map type");
97 		break;
98 	}
99 	CArrayCopy(&mb.Map->access, &mb.access);
100 
101 	MapSetupTilesAndWalls(&mb);
102 	MapSetupDoors(&mb);
103 	MapPrintDebug(mb.Map);
104 
105 	// Set exit now since we have set up all the tiles
106 	switch (mb.mission->Type)
107 	{
108 	case MAPTYPE_CLASSIC:
109 		MapAddDrains(&mb);
110 		if (HasExit(gCampaign.Entry.Mode) && mb.mission->u.Classic.ExitEnabled)
111 		{
112 			MapGenerateRandomExitArea(mb.Map, missionIndex);
113 		}
114 		break;
115 	case MAPTYPE_STATIC:
116 		break;
117 	case MAPTYPE_CAVE:
118 		if (HasExit(gCampaign.Entry.Mode) && mb.mission->u.Cave.ExitEnabled)
119 		{
120 			MapGenerateRandomExitArea(mb.Map, missionIndex);
121 		}
122 		break;
123 	case MAPTYPE_INTERIOR:
124 		MapAddDrains(&mb);
125 		break;
126 	default:
127 		CASSERT(false, "unknown map type");
128 		break;
129 	}
130 
131 	// Count total number of reachable tiles, for explored %
132 	mb.Map->NumExplorableTiles = 0;
133 	struct vec2i v;
134 	for (v.y = 0; v.y < mb.Map->Size.y; v.y++)
135 	{
136 		for (v.x = 0; v.x < mb.Map->Size.x; v.x++)
137 		{
138 			if (TileCanWalk(MapGetTile(mb.Map, v)))
139 			{
140 				mb.Map->NumExplorableTiles++;
141 			}
142 		}
143 	}
144 
145 	if (!co->IsClient)
146 	{
147 		MapLoadDynamic(&mb);
148 		ActorsPilotVehicles();
149 	}
150 	MapBuilderTerminate(&mb);
151 }
SetupWallTileClasses(PicManager * pm,const TileClass * base)152 void SetupWallTileClasses(PicManager *pm, const TileClass *base)
153 {
154 	for (int i = 0; i < WALL_TYPE_COUNT; i++)
155 	{
156 		PicManagerGenerateMaskedStylePic(
157 			pm, "wall", base->Style, IntWallType(i), base->Mask, base->MaskAlt,
158 			false);
159 		TileClassesAdd(
160 			&gTileClasses, pm, base, base->Style, IntWallType(i), base->Mask,
161 			base->MaskAlt);
162 	}
163 }
SetupFloorTileClasses(PicManager * pm,const TileClass * base)164 void SetupFloorTileClasses(PicManager *pm, const TileClass *base)
165 {
166 	for (int i = 0; i < FLOOR_TYPES; i++)
167 	{
168 		PicManagerGenerateMaskedStylePic(
169 			pm, "tile", base->Style, IntTileType(i), base->Mask, base->MaskAlt,
170 			false);
171 		TileClassesAdd(
172 			&gTileClasses, pm, base, base->Style, IntTileType(i), base->Mask,
173 			base->MaskAlt);
174 	}
175 }
SetupDoorTileClasses(PicManager * pm,const TileClass * base)176 void SetupDoorTileClasses(PicManager *pm, const TileClass *base)
177 {
178 	const char *doorStyles[] = {"yellow", "green", "blue",	"red",
179 								"open",	  "wall",  "normal"};
180 	for (int i = 0; i < (int)(sizeof doorStyles / sizeof doorStyles[0]); i++)
181 	{
182 		for (DoorType type = DOORTYPE_H; type < DOORTYPE_COUNT; type++)
183 		{
184 			DoorAddClass(&gTileClasses, pm, base, doorStyles[i], type);
185 		}
186 	}
187 }
188 static bool MapBuilderIsDoor(const MapBuilder *mb, const struct vec2i v);
189 static int MapGetAccessFlags(const MapBuilder *mb, const struct vec2i v);
MapSetupDoors(MapBuilder * mb)190 static void MapSetupDoors(MapBuilder *mb)
191 {
192 	// Mark doors as set up as we go
193 	CArray doorsSetup;
194 	CArrayInitFillZero(&doorsSetup, sizeof(bool), mb->Map->Size.x * mb->Map->Size.y);
195 
196 	RECT_FOREACH(Rect2iNew(svec2i_zero(), mb->Map->Size))
197 	// Check if this door tile hasn't been set up yet
198 	const int idx = _v.x + _v.y * mb->Map->Size.x;
199 	if (MapBuilderIsDoor(mb, _v) && !*(bool *)CArrayGet(&doorsSetup, idx))
200 	{
201 		const struct vec2i groupSize = MapAddDoorGroup(mb, _v, MapGetAccessFlags(mb, _v));
202 		for (int dx = 0; dx < groupSize.x; dx++)
203 		{
204 			for (int dy = 0; dy < groupSize.y; dy++)
205 			{
206 				const int idx2 = _v.x + dx + (_v.y + dy) * mb->Map->Size.x;
207 				CArraySet(&doorsSetup, idx2, &gTrue);
208 			}
209 		}
210 	}
211 	RECT_FOREACH_END()
212 
213 	CArrayTerminate(&doorsSetup);
214 }
MapBuilderIsDoor(const MapBuilder * mb,const struct vec2i v)215 static bool MapBuilderIsDoor(const MapBuilder *mb, const struct vec2i v)
216 {
217 	const TileClass *t = MapBuilderGetTile(mb, v);
218 	return t != NULL && t->Type == TILE_CLASS_DOOR;
219 }
MapGetAccessFlags(const MapBuilder * mb,const struct vec2i v)220 static int MapGetAccessFlags(const MapBuilder *mb, const struct vec2i v)
221 {
222 	int flags = 0;
223 	flags = MAX(flags, AccessCodeToFlags(MapBuildGetAccess(mb, v)));
224 	flags = MAX(
225 		flags, AccessCodeToFlags(MapBuildGetAccess(mb, svec2i(v.x - 1, v.y))));
226 	flags = MAX(
227 		flags, AccessCodeToFlags(MapBuildGetAccess(mb, svec2i(v.x + 1, v.y))));
228 	flags = MAX(
229 		flags, AccessCodeToFlags(MapBuildGetAccess(mb, svec2i(v.x, v.y - 1))));
230 	flags = MAX(
231 		flags, AccessCodeToFlags(MapBuildGetAccess(mb, svec2i(v.x, v.y + 1))));
232 	return flags;
233 }
234 
MapBuilderInit(MapBuilder * mb,Map * m,const Mission * mission,const Campaign * co)235 void MapBuilderInit(
236 	MapBuilder *mb, Map *m, const Mission *mission, const Campaign *co)
237 {
238 	memset(mb, 0, sizeof *mb);
239 	mb->Map = m;
240 	mb->mission = mission;
241 	mb->co = co;
242 
243 	const int mapSize = mission->Size.x * mission->Size.y;
244 	CArrayInitFillZero(&mb->access, sizeof(uint16_t), mapSize);
245 	CArrayInitFill(&mb->tiles, sizeof(TileClass), mapSize, &gTileNothing);
246 	CArrayInitFillZero(&mb->leaveFree, sizeof(bool), mapSize);
247 }
MapBuilderTerminate(MapBuilder * mb)248 void MapBuilderTerminate(MapBuilder *mb)
249 {
250 	CArrayTerminate(&mb->access);
251 	CArrayTerminate(&mb->tiles);
252 	CArrayTerminate(&mb->leaveFree);
253 }
254 
MapBuildGetAccess(const MapBuilder * mb,const struct vec2i pos)255 uint16_t MapBuildGetAccess(const MapBuilder *mb, const struct vec2i pos)
256 {
257 	if (!MapIsTileIn(mb->Map, pos))
258 	{
259 		return 0;
260 	}
261 	return *(uint16_t *)CArrayGet(
262 		&mb->access, pos.y * mb->Map->Size.x + pos.x);
263 }
MapBuildSetAccess(MapBuilder * mb,struct vec2i pos,const uint16_t v)264 void MapBuildSetAccess(MapBuilder *mb, struct vec2i pos, const uint16_t v)
265 {
266 	*(uint16_t *)CArrayGet(&mb->access, pos.y * mb->Map->Size.x + pos.x) = v;
267 }
MapBuilderGetTile(const MapBuilder * mb,const struct vec2i pos)268 const TileClass *MapBuilderGetTile(
269 	const MapBuilder *mb, const struct vec2i pos)
270 {
271 	if (!MapIsTileIn(mb->Map, pos))
272 	{
273 		return NULL;
274 	}
275 	return CArrayGet(&mb->tiles, pos.y * mb->Map->Size.x + pos.x);
276 }
MapBuilderSetTile(MapBuilder * mb,struct vec2i pos,const TileClass * t)277 void MapBuilderSetTile(MapBuilder *mb, struct vec2i pos, const TileClass *t)
278 {
279 	if (t != NULL)
280 	{
281 		*(TileClass *)CArrayGet(&mb->tiles, pos.y * mb->Map->Size.x + pos.x) =
282 			*t;
283 	}
284 }
285 
286 static bool IsTileOKStrict(
287 	const MapObject *obj, const Tile *tile, const Tile *tileAbove,
288 	const Tile *tileBelow, const bool isLeaveFree, const int numWallsAdjacent,
289 	const int numWallsAround);
290 static int MapGetNumWallsAdjacentTile(const Map *map, const struct vec2i v);
291 static int MapGetNumWallsAroundTile(const Map *map, const struct vec2i v);
MapTryPlaceOneObject(MapBuilder * mb,const struct vec2i v,const MapObject * mo,const int extraFlags,const bool isStrictMode)292 bool MapTryPlaceOneObject(
293 	MapBuilder *mb, const struct vec2i v, const MapObject *mo,
294 	const int extraFlags, const bool isStrictMode)
295 {
296 	// Don't place ammo spawners if ammo is disabled
297 	if (!gCampaign.Setting.Ammo &&
298 		mo->Type == MAP_OBJECT_TYPE_PICKUP_SPAWNER &&
299 		mo->u.PickupClass->Type == PICKUP_AMMO)
300 	{
301 		return false;
302 	}
303 	const Tile *t = MapGetTile(mb->Map, v);
304 	const Tile *tAbove = MapGetTile(mb->Map, svec2i(v.x, v.y - 1));
305 	const Tile *tBelow = MapGetTile(mb->Map, svec2i(v.x, v.y + 1));
306 	if (isStrictMode &&
307 		!IsTileOKStrict(
308 			mo, t, tAbove, tBelow, MapBuilderIsLeaveFree(mb, v),
309 			MapGetNumWallsAdjacentTile(mb->Map, v),
310 			MapGetNumWallsAroundTile(mb->Map, v)))
311 	{
312 		return false;
313 	}
314 	else if (!MapObjectIsTileOK(mo, t, tAbove))
315 	{
316 		return false;
317 	}
318 
319 	if (mo->Flags & (1 << PLACEMENT_FREE_IN_FRONT))
320 	{
321 		MapBuilderSetLeaveFree(mb, svec2i(v.x, v.y + 1), true);
322 	}
323 
324 	NMapObjectAdd amo = NMapObjectAdd_init_default;
325 	amo.UID = ObjsGetNextUID();
326 	strcpy(amo.MapObjectClass, mo->Name);
327 	amo.has_Pos = true;
328 	amo.Pos = Vec2ToNet(MapObjectGetPlacementPos(mo, v));
329 	amo.ThingFlags = MapObjectGetFlags(mo) | extraFlags;
330 	amo.Health = mo->Health;
331 	ObjAdd(amo);
332 	return true;
333 }
IsTileOKStrict(const MapObject * obj,const Tile * tile,const Tile * tileAbove,const Tile * tileBelow,const bool isLeaveFree,const int numWallsAdjacent,const int numWallsAround)334 static bool IsTileOKStrict(
335 	const MapObject *obj, const Tile *tile, const Tile *tileAbove,
336 	const Tile *tileBelow, const bool isLeaveFree, const int numWallsAdjacent,
337 	const int numWallsAround)
338 {
339 	if (!MapObjectIsTileOK(obj, tile, tileAbove))
340 	{
341 		return false;
342 	}
343 	if (isLeaveFree)
344 	{
345 		return false;
346 	}
347 
348 	if (obj->Flags & (1 << PLACEMENT_OUTSIDE) && tile->Class->IsRoom)
349 	{
350 		return false;
351 	}
352 	if ((obj->Flags & (1 << PLACEMENT_INSIDE)) && !tile->Class->IsRoom)
353 	{
354 		return false;
355 	}
356 	if ((obj->Flags & (1 << PLACEMENT_NO_WALLS)) && numWallsAround != 0)
357 	{
358 		return false;
359 	}
360 	if ((obj->Flags & (1 << PLACEMENT_ONE_WALL)) && numWallsAdjacent != 1)
361 	{
362 		return false;
363 	}
364 	if ((obj->Flags & (1 << PLACEMENT_ONE_OR_MORE_WALLS)) &&
365 		numWallsAdjacent < 1)
366 	{
367 		return false;
368 	}
369 	if ((obj->Flags & (1 << PLACEMENT_FREE_IN_FRONT)) &&
370 		!TileIsClear(tileBelow))
371 	{
372 		return false;
373 	}
374 
375 	return true;
376 }
377 // Adjacent means to the left, right, above or below
MapGetNumWallsAdjacentTile(const Map * map,const struct vec2i v)378 static int MapGetNumWallsAdjacentTile(const Map *map, const struct vec2i v)
379 {
380 	int count = 0;
381 	if (v.x > 0 && v.y > 0 && v.x < map->Size.x - 1 && v.y < map->Size.y - 1)
382 	{
383 		if (!TileCanWalk(MapGetTile(map, svec2i(v.x - 1, v.y))))
384 		{
385 			count++;
386 		}
387 		if (!TileCanWalk(MapGetTile(map, svec2i(v.x + 1, v.y))))
388 		{
389 			count++;
390 		}
391 		if (!TileCanWalk(MapGetTile(map, svec2i(v.x, v.y - 1))))
392 		{
393 			count++;
394 		}
395 		if (!TileCanWalk(MapGetTile(map, svec2i(v.x, v.y + 1))))
396 		{
397 			count++;
398 		}
399 	}
400 	return count;
401 }
402 // Around means the 8 tiles surrounding the tile
MapGetNumWallsAroundTile(const Map * map,const struct vec2i v)403 static int MapGetNumWallsAroundTile(const Map *map, const struct vec2i v)
404 {
405 	int count = MapGetNumWallsAdjacentTile(map, v);
406 	if (v.x > 0 && v.y > 0 && v.x < map->Size.x - 1 && v.y < map->Size.y - 1)
407 	{
408 		// Having checked the adjacencies, check the diagonals
409 		if (!TileCanWalk(MapGetTile(map, svec2i(v.x - 1, v.y - 1))))
410 		{
411 			count++;
412 		}
413 		if (!TileCanWalk(MapGetTile(map, svec2i(v.x + 1, v.y + 1))))
414 		{
415 			count++;
416 		}
417 		if (!TileCanWalk(MapGetTile(map, svec2i(v.x + 1, v.y - 1))))
418 		{
419 			count++;
420 		}
421 		if (!TileCanWalk(MapGetTile(map, svec2i(v.x - 1, v.y + 1))))
422 		{
423 			count++;
424 		}
425 	}
426 	return count;
427 }
428 
429 static void AddObjectives(MapBuilder *mb);
430 static void AddKeys(MapBuilder *mb);
MapLoadDynamic(MapBuilder * mb)431 void MapLoadDynamic(MapBuilder *mb)
432 {
433 	if (mb->mission->Type == MAPTYPE_STATIC)
434 	{
435 		MapStaticLoadDynamic(mb);
436 	}
437 
438 	// Add map objects
439 	CA_FOREACH(const MapObjectDensity, mod, mb->mission->MapObjectDensities)
440 	for (int j = 0;
441 		 j < (mod->Density * mb->Map->Size.x * mb->Map->Size.y) / 1000; j++)
442 	{
443 		MapTryPlaceOneObject(mb, MapGetRandomTile(mb->Map), mod->M, 0, true);
444 	}
445 	CA_FOREACH_END()
446 
447 	if (HasObjectives(gCampaign.Entry.Mode))
448 	{
449 		AddObjectives(mb);
450 	}
451 
452 	if (AreKeysAllowed(gCampaign.Entry.Mode))
453 	{
454 		AddKeys(mb);
455 	}
456 }
457 static bool MapTryPlaceBlowup(MapBuilder *mb, const int objective);
458 static int MapTryPlaceCollectible(MapBuilder *mb, const int objective);
AddObjectives(MapBuilder * mb)459 static void AddObjectives(MapBuilder *mb)
460 {
461 	// Try to add the objectives
462 	// If we are unable to place them all, make sure to reduce the totals
463 	// in case we create missions that are impossible to complete
464 	CA_FOREACH(Objective, o, mb->mission->Objectives)
465 	if (o->Type != OBJECTIVE_COLLECT && o->Type != OBJECTIVE_DESTROY)
466 	{
467 		continue;
468 	}
469 	if (o->Type == OBJECTIVE_COLLECT)
470 	{
471 		for (int i = o->placed; i < o->Count; i++)
472 		{
473 			if (MapTryPlaceCollectible(mb, _ca_index))
474 			{
475 				o->placed++;
476 			}
477 		}
478 	}
479 	else if (o->Type == OBJECTIVE_DESTROY)
480 	{
481 		for (int i = o->placed; i < o->Count; i++)
482 		{
483 			if (MapTryPlaceBlowup(mb, _ca_index))
484 			{
485 				o->placed++;
486 			}
487 		}
488 	}
489 	o->Count = o->placed;
490 	if (o->Count < o->Required)
491 	{
492 		o->Required = o->Count;
493 	}
494 	CA_FOREACH_END()
495 }
MapTryPlaceCollectible(MapBuilder * mb,const int objective)496 static int MapTryPlaceCollectible(MapBuilder *mb, const int objective)
497 {
498 	const Objective *o = CArrayGet(&mb->mission->Objectives, objective);
499 	const bool hasLockedRooms =
500 		(o->Flags & OBJECTIVE_HIACCESS) && MapHasLockedRooms(mb->Map);
501 	const bool noaccess = o->Flags & OBJECTIVE_NOACCESS;
502 	int i = (noaccess || hasLockedRooms) ? 1000 : 100;
503 
504 	while (i)
505 	{
506 		const struct vec2 v = MapGetRandomPos(mb->Map);
507 		const struct vec2i size = svec2i(COLLECTABLE_W, COLLECTABLE_H);
508 		if (!IsCollisionWithWall(v, size))
509 		{
510 			if ((!hasLockedRooms || MapPosIsInLockedRoom(mb->Map, v)) &&
511 				(!noaccess || !MapPosIsInLockedRoom(mb->Map, v)))
512 			{
513 				MapPlaceCollectible(mb->mission, objective, v);
514 				return 1;
515 			}
516 		}
517 		i--;
518 	}
519 	return 0;
520 }
521 static void MapPlaceCard(
522 	MapBuilder *mb, const int keyIndex, const int mapAccess);
AddKeys(MapBuilder * mb)523 static void AddKeys(MapBuilder *mb)
524 {
525 	if (mb->Map->keyAccessCount >= 5)
526 	{
527 		MapPlaceCard(mb, 3, MAP_ACCESS_BLUE);
528 	}
529 	if (mb->Map->keyAccessCount >= 4)
530 	{
531 		MapPlaceCard(mb, 2, MAP_ACCESS_GREEN);
532 	}
533 	if (mb->Map->keyAccessCount >= 3)
534 	{
535 		MapPlaceCard(mb, 1, MAP_ACCESS_YELLOW);
536 	}
537 	if (mb->Map->keyAccessCount >= 2)
538 	{
539 		MapPlaceCard(mb, 0, 0);
540 	}
541 }
MapPlaceCard(MapBuilder * mb,const int keyIndex,const int mapAccess)542 static void MapPlaceCard(
543 	MapBuilder *mb, const int keyIndex, const int mapAccess)
544 {
545 	for (;;)
546 	{
547 		const struct vec2i v = MapGetRandomTile(mb->Map);
548 		const Tile *t = MapGetTile(mb->Map, v);
549 		if (t->Class->IsRoom && TileIsClear(t) && TileCanWalk(t) &&
550 			MapBuildGetAccess(mb, v) == mapAccess &&
551 			// Ensure keys are visible, not hidden behind walls
552 			TileIsClear(MapGetTile(mb->Map, svec2i(v.x, v.y + 1))))
553 		{
554 			MapPlaceKey(mb, v, keyIndex);
555 			return;
556 		}
557 	}
558 }
559 typedef struct
560 {
561 	const Objective *o;
562 	int objective;
563 } TryPlaceOneBlowupData;
564 static bool TryPlaceOneBlowup(
565 	MapBuilder *mb, const struct vec2i tilePos, void *data);
MapTryPlaceBlowup(MapBuilder * mb,const int objective)566 static bool MapTryPlaceBlowup(MapBuilder *mb, const int objective)
567 {
568 	TryPlaceOneBlowupData data;
569 	data.o = CArrayGet(&mb->mission->Objectives, objective);
570 	const PlacementAccessFlags paFlags =
571 		ObjectiveGetPlacementAccessFlags(data.o);
572 	data.objective = objective;
573 	return MapPlaceRandomTile(mb, paFlags, TryPlaceOneBlowup, &data);
574 }
TryPlaceOneBlowup(MapBuilder * mb,const struct vec2i tilePos,void * data)575 static bool TryPlaceOneBlowup(
576 	MapBuilder *mb, const struct vec2i tilePos, void *data)
577 {
578 	const TryPlaceOneBlowupData *pData = data;
579 	return MapTryPlaceOneObject(
580 		mb, tilePos, pData->o->u.MapObject, ObjectiveToThing(pData->objective),
581 		true);
582 }
583 
MapBuilderSetLeaveFree(MapBuilder * mb,const struct vec2i tile,const bool value)584 void MapBuilderSetLeaveFree(
585 	MapBuilder *mb, const struct vec2i tile, const bool value)
586 {
587 	if (!MapIsTileIn(mb->Map, tile))
588 		return;
589 	CArraySet(&mb->leaveFree, tile.y * mb->Map->Size.x + tile.x, &value);
590 }
MapBuilderIsLeaveFree(const MapBuilder * mb,const struct vec2i tile)591 bool MapBuilderIsLeaveFree(const MapBuilder *mb, const struct vec2i tile)
592 {
593 	if (!MapIsTileIn(mb->Map, tile))
594 		return false;
595 	return *(bool *)CArrayGet(
596 		&mb->leaveFree, tile.y * mb->Map->Size.x + tile.x);
597 }
598 
599 static void MapSetupTile(MapBuilder *mb, const struct vec2i pos);
600 
MapBuildTile(MapBuilder * mb,const struct vec2i pos,const TileClass * tile)601 void MapBuildTile(
602 	MapBuilder *mb, const struct vec2i pos, const TileClass *tile)
603 {
604 	// Load tiles from +2 perimeter
605 	RECT_FOREACH(Rect2iNew(svec2i_subtract(pos, svec2i(2, 2)), svec2i(5, 5)))
606 	MapStaticLoadTile(mb, _v);
607 	RECT_FOREACH_END()
608 	// Update the tile as well, plus neighbours as they may be affected
609 	// by shadows etc. especially walls
610 	MapBuilderSetTile(mb, pos, tile);
611 	MapSetupTile(mb, pos);
612 	RECT_FOREACH(Rect2iNew(svec2i_subtract(pos, svec2i(1, 1)), svec2i(3, 3)))
613 	MapSetupTile(mb, _v);
614 	RECT_FOREACH_END()
615 	CArrayCopy(&mb->Map->access, &mb->access);
616 }
617 
MapTileIsNormalFloor(const MapBuilder * mb,const struct vec2i pos)618 static bool MapTileIsNormalFloor(const MapBuilder *mb, const struct vec2i pos)
619 {
620 	// Normal floor tiles can be replaced randomly with
621 	// special floor tiles such as drainage
622 	const TileClass *tile = MapBuilderGetTile(mb, pos);
623 	if (tile->Type != TILE_CLASS_FLOOR || tile->IsRoom)
624 	{
625 		return false;
626 	}
627 	const TileClass *tAbove = MapBuilderGetTile(mb, svec2i(pos.x, pos.y - 1));
628 	if (!tAbove || tAbove->Type != TILE_CLASS_FLOOR || tAbove->IsRoom)
629 	{
630 		return false;
631 	}
632 	return true;
633 }
634 
MapSetupTilesAndWalls(MapBuilder * mb)635 static void MapSetupTilesAndWalls(MapBuilder *mb)
636 {
637 	RECT_FOREACH(Rect2iNew(svec2i_zero(), mb->Map->Size))
638 	MapSetupTile(mb, _v);
639 	RECT_FOREACH_END()
640 
641 	if (mb->mission->Type != MAPTYPE_STATIC || mb->mission->u.Static.AltFloorsEnabled)
642 	{
643 		// Randomly change normal floor tiles to alternative floor tiles
644 		for (int i = 0; i < mb->Map->Size.x * mb->Map->Size.y / 22; i++)
645 		{
646 			const struct vec2i pos = MapGetRandomTile(mb->Map);
647 			if (MapTileIsNormalFloor(mb, pos))
648 			{
649 				Tile *t = MapGetTile(mb->Map, pos);
650 				t->Class = TileClassesGetMaskedTile(
651 					t->Class, t->Class->Style, "alt1", t->Class->Mask,
652 					t->Class->MaskAlt);
653 			}
654 		}
655 		for (int i = 0; i < mb->Map->Size.x * mb->Map->Size.y / 16; i++)
656 		{
657 			const struct vec2i pos = MapGetRandomTile(mb->Map);
658 			if (MapTileIsNormalFloor(mb, pos))
659 			{
660 				Tile *t = MapGetTile(mb->Map, pos);
661 				t->Class = TileClassesGetMaskedTile(
662 					t->Class, t->Class->Style, "alt2", t->Class->Mask,
663 					t->Class->MaskAlt);
664 			}
665 		}
666 	}
667 }
668 static const char *MapGetWallPic(const MapBuilder *m, const struct vec2i pos);
669 // Set tile properties for a map tile
MapSetupTile(MapBuilder * mb,const struct vec2i pos)670 static void MapSetupTile(MapBuilder *mb, const struct vec2i pos)
671 {
672 	if (!MapIsTileIn(mb->Map, pos))
673 		return;
674 	const Tile *tAbove = MapGetTile(mb->Map, svec2i(pos.x, pos.y - 1));
675 	const bool canSeeTileAbove = !(tAbove != NULL && TileIsOpaque(tAbove));
676 	Tile *t = MapGetTile(mb->Map, pos);
677 	if (!t)
678 	{
679 		return;
680 	}
681 	const TileClass *tc = MapBuilderGetTile(mb, pos);
682 	if (tc->Type == TILE_CLASS_FLOOR)
683 	{
684 		t->Class = TileClassesGetMaskedTile(
685 			tc, tc->Style, canSeeTileAbove ? "normal" : "shadow", tc->Mask,
686 			tc->MaskAlt);
687 	}
688 	else if (tc->Type == TILE_CLASS_WALL)
689 	{
690 		t->Class = TileClassesGetMaskedTile(
691 			tc, tc->Style, MapGetWallPic(mb, pos), tc->Mask, tc->MaskAlt);
692 	}
693 	else if (tc->Type == TILE_CLASS_DOOR)
694 	{
695 		t->Class = TileClassesGetMaskedTile(
696 			tc, tc->Style, "normal_h", tc->Mask, tc->MaskAlt);
697 	}
698 	else if (tc->Type == TILE_CLASS_NOTHING)
699 	{
700 		t->Class = &gTileNothing;
701 	}
702 	else
703 	{
704 		CASSERT(false, "cannot setup tile");
705 		t->Class = &gTileNothing;
706 	}
707 }
708 static bool W(const MapBuilder *mb, const int x, const int y);
MapGetWallPic(const MapBuilder * m,const struct vec2i pos)709 static const char *MapGetWallPic(const MapBuilder *m, const struct vec2i pos)
710 {
711 	const int x = pos.x;
712 	const int y = pos.y;
713 	if (W(m, x - 1, y) && W(m, x + 1, y) && W(m, x, y + 1) && W(m, x, y - 1))
714 	{
715 		return "x";
716 	}
717 	if (W(m, x - 1, y) && W(m, x + 1, y) && W(m, x, y + 1))
718 	{
719 		return "nt";
720 	}
721 	if (W(m, x - 1, y) && W(m, x + 1, y) && W(m, x, y - 1))
722 	{
723 		return "st";
724 	}
725 	if (W(m, x - 1, y) && W(m, x, y + 1) && W(m, x, y - 1))
726 	{
727 		return "et";
728 	}
729 	if (W(m, x + 1, y) && W(m, x, y + 1) && W(m, x, y - 1))
730 	{
731 		return "wt";
732 	}
733 	if (W(m, x + 1, y) && W(m, x, y + 1))
734 	{
735 		return "nw";
736 	}
737 	if (W(m, x + 1, y) && W(m, x, y - 1))
738 	{
739 		return "sw";
740 	}
741 	if (W(m, x - 1, y) && W(m, x, y + 1))
742 	{
743 		return "ne";
744 	}
745 	if (W(m, x - 1, y) && W(m, x, y - 1))
746 	{
747 		return "se";
748 	}
749 	if (W(m, x - 1, y) && W(m, x + 1, y))
750 	{
751 		return "h";
752 	}
753 	if (W(m, x, y + 1) && W(m, x, y - 1))
754 	{
755 		return "v";
756 	}
757 	if (W(m, x, y + 1))
758 	{
759 		return "n";
760 	}
761 	if (W(m, x, y - 1))
762 	{
763 		return "s";
764 	}
765 	if (W(m, x + 1, y))
766 	{
767 		return "w";
768 	}
769 	if (W(m, x - 1, y))
770 	{
771 		return "e";
772 	}
773 	return "o";
774 }
W(const MapBuilder * mb,const int x,const int y)775 static bool W(const MapBuilder *mb, const int x, const int y)
776 {
777 	const struct vec2i v = svec2i(x, y);
778 	if (!MapIsTileIn(mb->Map, v))
779 		return false;
780 	const TileClass *tc = MapBuilderGetTile(mb, v);
781 	return tc->Type == TILE_CLASS_WALL;
782 }
783 
MapIsValidStartForWall(const MapBuilder * mb,const struct vec2i pos,const bool isRoom,const int pad)784 static bool MapIsValidStartForWall(
785 	const MapBuilder *mb, const struct vec2i pos, const bool isRoom,
786 	const int pad)
787 {
788 	if (!MapIsTileIn(mb->Map, pos) || pos.x == 0 || pos.y == 0 ||
789 		pos.x == mb->Map->Size.x - 1 || pos.y == mb->Map->Size.y - 1)
790 	{
791 		return false;
792 	}
793 	struct vec2i d;
794 	for (d.x = pos.x - pad; d.x <= pos.x + pad; d.x++)
795 	{
796 		for (d.y = pos.y - pad; d.y <= pos.y + pad; d.y++)
797 		{
798 			const TileClass *t = MapBuilderGetTile(mb, d);
799 			if (t == NULL || t->Type != TILE_CLASS_FLOOR ||
800 				t->IsRoom != isRoom)
801 			{
802 				return false;
803 			}
804 		}
805 	}
806 	return true;
807 }
808 
MapGetRoomSize(const RoomParams r,const int doorMin)809 struct vec2i MapGetRoomSize(const RoomParams r, const int doorMin)
810 {
811 	// Work out dimensions of room
812 	// make sure room is large enough to accommodate doors
813 	const int roomMin = MAX(r.Min, doorMin + 2);
814 	const int roomMax = MAX(r.Max, doorMin + 2);
815 	return svec2i(RAND_INT(roomMin, roomMax), RAND_INT(roomMin, roomMax));
816 }
817 
818 static bool MapBuilderGetIsRoom(const MapBuilder *mb, const struct vec2i pos);
MapMakeRoom(MapBuilder * mb,const struct vec2i pos,const struct vec2i size,const bool walls,const TileClass * wall,const TileClass * room,const bool removeInterRoomWalls)819 void MapMakeRoom(
820 	MapBuilder *mb, const struct vec2i pos, const struct vec2i size,
821 	const bool walls, const TileClass *wall, const TileClass *room,
822 	const bool removeInterRoomWalls)
823 {
824 	struct vec2i v;
825 	// Set the perimeter walls and interior
826 	// If the tile is a room interior already, do not turn it into a wall
827 	// This is due to overlapping rooms
828 	for (v.y = pos.y; v.y < pos.y + size.y; v.y++)
829 	{
830 		for (v.x = pos.x; v.x < pos.x + size.x; v.x++)
831 		{
832 			if (v.y == pos.y || v.y == pos.y + size.y - 1 || v.x == pos.x ||
833 				v.x == pos.x + size.x - 1)
834 			{
835 				const TileClass *t = MapBuilderGetTile(mb, v);
836 				if (walls && t != NULL && !t->IsRoom)
837 				{
838 					MapBuilderSetTile(mb, v, wall);
839 				}
840 			}
841 			else
842 			{
843 				MapBuilderSetTile(mb, v, room);
844 			}
845 		}
846 	}
847 	if (removeInterRoomWalls)
848 	{
849 
850 		// Check perimeter again; if there are walls where both sides contain
851 		// rooms, remove the wall as the rooms have merged
852 		for (v.y = pos.y; v.y < pos.y + size.y; v.y++)
853 		{
854 			for (v.x = pos.x; v.x < pos.x + size.x; v.x++)
855 			{
856 				if (v.y == pos.y || v.y == pos.y + size.y - 1 ||
857 					v.x == pos.x || v.x == pos.x + size.x - 1)
858 				{
859 					if ((MapBuilderGetIsRoom(mb, svec2i(v.x + 1, v.y)) &&
860 						 MapBuilderGetIsRoom(mb, svec2i(v.x - 1, v.y))) ||
861 						(MapBuilderGetIsRoom(mb, svec2i(v.x, v.y + 1)) &&
862 						 MapBuilderGetIsRoom(mb, svec2i(v.x, v.y - 1))))
863 					{
864 						MapBuilderSetTile(mb, v, room);
865 					}
866 				}
867 			}
868 		}
869 	}
870 }
MapBuilderGetIsRoom(const MapBuilder * mb,const struct vec2i pos)871 static bool MapBuilderGetIsRoom(const MapBuilder *mb, const struct vec2i pos)
872 {
873 	const TileClass *t = MapBuilderGetTile(mb, pos);
874 	return t != NULL && t->Type == TILE_CLASS_FLOOR && t->IsRoom;
875 }
876 
MapMakeRoomWalls(MapBuilder * mb,const RoomParams r,const TileClass * wall,const Rect2i room)877 void MapMakeRoomWalls(
878 	MapBuilder *mb, const RoomParams r, const TileClass *wall, const Rect2i room)
879 {
880 	int count = 0;
881 	for (int i = 0; i < 100 && count < r.Walls; i++)
882 	{
883 		if (!MapTryBuildWall(mb, true, MAX(r.WallPad, 1), r.WallLength, wall, room))
884 		{
885 			continue;
886 		}
887 		count++;
888 	}
889 }
890 
891 static void MapGrowWall(
892 	MapBuilder *mb, struct vec2i pos, const bool isRoom, const int pad,
893 	const int d, int length, const TileClass *wall);
MapTryBuildWall(MapBuilder * mb,const bool isRoom,const int pad,const int wallLength,const TileClass * wall,const Rect2i r)894 bool MapTryBuildWall(
895 	MapBuilder *mb, const bool isRoom, const int pad, const int wallLength,
896 	const TileClass *wall, const Rect2i r)
897 {
898 	const struct vec2i v =
899 		Rect2iIsZero(r) ?
900 		MapGetRandomTile(mb->Map) :
901 		svec2i_add(r.Pos, svec2i(rand() % r.Size.x, rand() % r.Size.y));
902 	if (MapIsValidStartForWall(mb, v, isRoom, pad))
903 	{
904 		MapBuilderSetTile(mb, v, wall);
905 		MapGrowWall(mb, v, isRoom, pad, rand() & 3, wallLength, wall);
906 		return true;
907 	}
908 	return false;
909 }
910 static bool MapGrowWallCheck(
911 	const MapBuilder *mb, const struct vec2i v, const bool isRoom);
MapGrowWall(MapBuilder * mb,struct vec2i pos,const bool isRoom,const int pad,const int d,int length,const TileClass * wall)912 static void MapGrowWall(
913 	MapBuilder *mb, struct vec2i pos, const bool isRoom, const int pad,
914 	const int d, int length, const TileClass *wall)
915 {
916 	int l;
917 	struct vec2i v;
918 
919 	if (length <= 0)
920 		return;
921 
922 	switch (d)
923 	{
924 	case 0:
925 		if (pos.y < 2 + pad)
926 		{
927 			return;
928 		}
929 		// Check tiles above
930 		// xxxxx
931 		//  xxx
932 		//   o
933 		for (v.y = pos.y - 2; v.y > pos.y - 2 - pad; v.y--)
934 		{
935 			int level = v.y - (pos.y - 2);
936 			for (v.x = pos.x - 1 - level; v.x <= pos.x + 1 + level; v.x++)
937 			{
938 				if (!MapGrowWallCheck(mb, v, isRoom))
939 					return;
940 			}
941 		}
942 		pos.y--;
943 		break;
944 	case 1:
945 		// Check tiles to the right
946 		//   x
947 		//  xx
948 		// oxx
949 		//  xx
950 		//   x
951 		for (v.x = pos.x + 2; v.x < pos.x + 2 + pad; v.x++)
952 		{
953 			int level = v.x - (pos.x + 2);
954 			for (v.y = pos.y - 1 - level; v.y <= pos.y + 1 + level; v.y++)
955 			{
956 				if (!MapGrowWallCheck(mb, v, isRoom))
957 					return;
958 			}
959 		}
960 		pos.x++;
961 		break;
962 	case 2:
963 		// Check tiles below
964 		//   o
965 		//  xxx
966 		// xxxxx
967 		for (v.y = pos.y + 2; v.y < pos.y + 2 + pad; v.y++)
968 		{
969 			int level = v.y - (pos.y + 2);
970 			for (v.x = pos.x - 1 - level; v.x <= pos.x + 1 + level; v.x++)
971 			{
972 				if (!MapGrowWallCheck(mb, v, isRoom))
973 					return;
974 			}
975 		}
976 		pos.y++;
977 		break;
978 	case 4:
979 		if (pos.x < 2 + pad)
980 		{
981 			return;
982 		}
983 		// Check tiles to the left
984 		// x
985 		// xx
986 		// xxo
987 		// xx
988 		// x
989 		for (v.x = pos.x - 2; v.x > pos.x - 2 - pad; v.x--)
990 		{
991 			int level = v.x - (pos.x - 2);
992 			for (v.y = pos.y - 1 - level; v.y <= pos.y + 1 + level; v.y++)
993 			{
994 				if (!MapGrowWallCheck(mb, v, isRoom))
995 					return;
996 			}
997 		}
998 		pos.x--;
999 		break;
1000 	}
1001 	MapBuilderSetTile(mb, pos, wall);
1002 	length--;
1003 	if (length > 0 && (rand() & 3) == 0)
1004 	{
1005 		// Randomly try to grow the wall in a different direction
1006 		l = rand() % length;
1007 		MapGrowWall(mb, pos, isRoom, pad, rand() & 3, l, wall);
1008 		length -= l;
1009 	}
1010 	// Keep growing wall in same direction
1011 	MapGrowWall(mb, pos, isRoom, pad, d, length, wall);
1012 }
MapGrowWallCheck(const MapBuilder * mb,const struct vec2i v,const bool isRoom)1013 static bool MapGrowWallCheck(
1014 	const MapBuilder *mb, const struct vec2i v, const bool isRoom)
1015 {
1016 	if (!MapIsTileIn(mb->Map, v))
1017 		return true;
1018 	const TileClass *t = MapBuilderGetTile(mb, v);
1019 	if (t == NULL || t->Type != TILE_CLASS_FLOOR || t->IsRoom != isRoom)
1020 	{
1021 		return false;
1022 	}
1023 	return true;
1024 }
1025 
MapSetRoomAccessMask(MapBuilder * mb,const Rect2i r,const uint16_t accessMask)1026 void MapSetRoomAccessMask(
1027 	MapBuilder *mb, const Rect2i r, const uint16_t accessMask)
1028 {
1029 	RECT_FOREACH(r)
1030 	MapBuildSetAccess(mb, _v, accessMask);
1031 	RECT_FOREACH_END()
1032 }
1033 
1034 static void AddOverlapRooms(
1035 	MapBuilder *mb, const Rect2i room, CArray *overlapRooms, CArray *rooms,
1036 	const uint16_t accessMask);
MapSetRoomAccessMaskOverlap(MapBuilder * mb,CArray * rooms,const uint16_t accessMask)1037 void MapSetRoomAccessMaskOverlap(
1038 	MapBuilder *mb, CArray *rooms, const uint16_t accessMask)
1039 {
1040 	CArray overlapRooms;
1041 	CArrayInit(&overlapRooms, sizeof(Rect2i));
1042 	const Rect2i room = *(const Rect2i *)CArrayGet(rooms, 0);
1043 	CArrayPushBack(&overlapRooms, &room);
1044 	CA_FOREACH(const Rect2i, r, overlapRooms)
1045 	AddOverlapRooms(mb, *r, &overlapRooms, rooms, accessMask);
1046 	CA_FOREACH_END()
1047 }
AddOverlapRooms(MapBuilder * mb,const Rect2i room,CArray * overlapRooms,CArray * rooms,const uint16_t accessMask)1048 static void AddOverlapRooms(
1049 	MapBuilder *mb, const Rect2i room, CArray *overlapRooms, CArray *rooms,
1050 	const uint16_t accessMask)
1051 {
1052 	// Find all rooms that overlap with a room, and move it to the overlap
1053 	// rooms array, setting access mask as we go
1054 	CA_FOREACH(const Rect2i, r, *rooms)
1055 	if (Rect2iOverlap(room, *r))
1056 	{
1057 		LOG(LM_MAP, LL_TRACE,
1058 			"Room overlap {%d, %d (%dx%d)} {%d, %d (%dx%d)} access(%d)",
1059 			room.Pos.x, room.Pos.y, room.Size.x, room.Size.y, r->Pos.x,
1060 			r->Pos.y, r->Size.x, r->Size.y, accessMask);
1061 		MapSetRoomAccessMask(mb, *r, accessMask);
1062 		CArrayPushBack(overlapRooms, r);
1063 		CArrayDelete(rooms, _ca_index);
1064 		_ca_index--;
1065 	}
1066 	CA_FOREACH_END()
1067 }
1068 
1069 static void PlaceDoors(MapBuilder *mb, const int doorSize, const int roomDim, const struct vec2i doorStart, const struct vec2i d, const bool randomPos, const TileClass *tile);
MapPlaceDoors(MapBuilder * mb,const Rect2i r,const bool hasDoors,const bool doors[4],const int doorMin,const int doorMax,const uint16_t accessMask,const bool randomPos,const TileClass * door,const TileClass * floor)1070 void MapPlaceDoors(
1071 	MapBuilder *mb, const Rect2i r, const bool hasDoors, const bool doors[4],
1072 	const int doorMin, const int doorMax, const uint16_t accessMask, const bool randomPos,
1073 	const TileClass *door, const TileClass *floor)
1074 {
1075 	const TileClass *tile = hasDoors ? door : floor;
1076 
1077 	MapSetRoomAccessMask(mb, r, accessMask);
1078 
1079 	// Set the doors
1080 	for (int i = 0; i < 4; i++)
1081 	{
1082 		if (!doors[i])
1083 		{
1084 			continue;
1085 		}
1086 		const int doorSize = doorMax > doorMin ? RAND_INT(doorMin, doorMax) : doorMin;
1087 		int roomDim;
1088 		struct vec2i d;
1089 		struct vec2i doorStart;
1090 		switch (i)
1091 		{
1092 			case 0:
1093 				// left
1094 				roomDim = r.Size.y;
1095 				d = svec2i(1, 0);
1096 				doorStart = r.Pos;
1097 				break;
1098 			case 1:
1099 				// right
1100 				roomDim = r.Size.y;
1101 				d = svec2i(1, 0);
1102 				doorStart = svec2i(r.Pos.x + r.Size.x - 1, r.Pos.y);
1103 				break;
1104 			case 2:
1105 				// top
1106 				roomDim = r.Size.x;
1107 				d = svec2i(0, 1);
1108 				doorStart = r.Pos;
1109 				break;
1110 			case 3:
1111 				// bottom:
1112 				roomDim = r.Size.x;
1113 				d = svec2i(0, 1);
1114 				doorStart = svec2i(r.Pos.x, r.Pos.y + r.Size.y - 1);
1115 				break;
1116 			default:
1117 				CASSERT(false, "unexpected side index");
1118 				return;
1119 		}
1120 		PlaceDoors(mb, doorSize, roomDim, doorStart, d, randomPos, tile);
1121 	}
1122 }
1123 static bool TryPlaceDoorTile(
1124 	MapBuilder *mb, const struct vec2i v, const struct vec2i d,
1125 	const TileClass *tile);
PlaceDoors(MapBuilder * mb,const int doorSize,const int roomDim,const struct vec2i doorStart,const struct vec2i d,const bool randomPos,const TileClass * tile)1126 static void PlaceDoors(MapBuilder *mb, const int doorSize, const int roomDim, const struct vec2i doorStart, const struct vec2i d, const bool randomPos, const TileClass *tile)
1127 {
1128 	const struct vec2i dAcross = svec2i_subtract(svec2i_one(), d);
1129 	const int size = MIN(doorSize, roomDim - 2);
1130 	struct vec2i start = doorStart;
1131 	if (randomPos)
1132 	{
1133 		start = svec2i_add(start, svec2i_scale(dAcross, (float)RAND_INT(1, roomDim - size - 1)));
1134 	}
1135 	else
1136 	{
1137 		start = svec2i_add(start, svec2i_scale(dAcross, (float)((roomDim - size) / 2)));
1138 	}
1139 	for (int i = 0; i < size; i++)
1140 	{
1141 		const struct vec2i v = svec2i_add(start, svec2i_scale(dAcross, (float)i));
1142 		if (!TryPlaceDoorTile(mb, v, d, tile))
1143 		{
1144 			break;
1145 		}
1146 	}
1147 }
TryPlaceDoorTile(MapBuilder * mb,const struct vec2i v,const struct vec2i d,const TileClass * tile)1148 static bool TryPlaceDoorTile(
1149 	MapBuilder *mb, const struct vec2i v, const struct vec2i d,
1150 	const TileClass *tile)
1151 {
1152 	if (MapBuilderGetTile(mb, v)->Type == TILE_CLASS_WALL &&
1153 		MapBuilderGetTile(mb, svec2i_add(v, d))->Type != TILE_CLASS_WALL &&
1154 		MapBuilderGetTile(mb, svec2i_subtract(v, d))->Type != TILE_CLASS_WALL)
1155 	{
1156 		MapBuilderSetTile(mb, v, tile);
1157 		return true;
1158 	}
1159 	return false;
1160 }
1161 
MapIsAreaInside(const Map * map,const struct vec2i pos,const struct vec2i size)1162 bool MapIsAreaInside(
1163 	const Map *map, const struct vec2i pos, const struct vec2i size)
1164 {
1165 	return pos.x >= 0 && pos.y >= 0 && pos.x + size.x < map->Size.x &&
1166 		   pos.y + size.y < map->Size.y;
1167 }
1168 
MapBuilderIsAreaFunc(const MapBuilder * mb,const struct vec2i pos,const struct vec2i size,bool (* func)(const MapBuilder *,const struct vec2i))1169 bool MapBuilderIsAreaFunc(
1170 	const MapBuilder *mb, const struct vec2i pos, const struct vec2i size,
1171 	bool (*func)(const MapBuilder *, const struct vec2i))
1172 {
1173 	if (!MapIsAreaInside(mb->Map, pos, size))
1174 	{
1175 		return false;
1176 	}
1177 	RECT_FOREACH(Rect2iNew(pos, size))
1178 	if (!func(mb, _v))
1179 		return false;
1180 	RECT_FOREACH_END()
1181 	return true;
1182 }
1183 
IsClear(const MapBuilder * mb,const struct vec2i pos)1184 static bool IsClear(const MapBuilder *mb, const struct vec2i pos)
1185 {
1186 	return MapBuilderGetTile(mb, pos)->Type == TILE_CLASS_FLOOR;
1187 }
MapIsAreaClear(const MapBuilder * mb,const struct vec2i pos,const struct vec2i size)1188 bool MapIsAreaClear(
1189 	const MapBuilder *mb, const struct vec2i pos, const struct vec2i size)
1190 {
1191 	return MapBuilderIsAreaFunc(mb, pos, size, IsClear);
1192 }
AreaHasRoomAndFloor(const MapBuilder * mb,const struct vec2i pos)1193 static bool AreaHasRoomAndFloor(const MapBuilder *mb, const struct vec2i pos)
1194 {
1195 	// Find whether a wall tile is part of a room perimeter
1196 	// The surrounding tiles must have normal floor and room tiles
1197 	// to be a perimeter
1198 	bool hasRoom = false;
1199 	bool hasFloor = false;
1200 	RECT_FOREACH(Rect2iNew(svec2i_subtract(pos, svec2i_one()), svec2i(3, 3)))
1201 	const TileClass *t = MapBuilderGetTile(mb, _v);
1202 	if (t == NULL || t->Type != TILE_CLASS_FLOOR)
1203 		continue;
1204 	if (t->IsRoom)
1205 		hasRoom = true;
1206 	else
1207 		hasFloor = true;
1208 	RECT_FOREACH_END()
1209 	return hasRoom && hasFloor;
1210 }
IsClearOrRoom(const MapBuilder * mb,const struct vec2i pos)1211 static bool IsClearOrRoom(const MapBuilder *mb, const struct vec2i pos)
1212 {
1213 	const TileClass *tile = MapBuilderGetTile(mb, pos);
1214 	if (tile->Type == TILE_CLASS_FLOOR)
1215 		return true;
1216 	// Check if this wall is part of a room
1217 	if (tile->Type != TILE_CLASS_WALL)
1218 		return false;
1219 	return AreaHasRoomAndFloor(mb, pos);
1220 }
MapIsAreaClearOrRoom(const MapBuilder * mb,const struct vec2i pos,const struct vec2i size)1221 bool MapIsAreaClearOrRoom(
1222 	const MapBuilder *mb, const struct vec2i pos, const struct vec2i size)
1223 {
1224 	return MapBuilderIsAreaFunc(mb, pos, size, IsClearOrRoom);
1225 }
IsClearOrWall(const MapBuilder * mb,const struct vec2i pos)1226 static bool IsClearOrWall(const MapBuilder *mb, const struct vec2i pos)
1227 {
1228 	const TileClass *tile = MapBuilderGetTile(mb, pos);
1229 	if (tile->Type == TILE_CLASS_FLOOR && !tile->IsRoom)
1230 		return true;
1231 	// Check if this wall is part of a room
1232 	if (tile->Type != TILE_CLASS_WALL)
1233 		return false;
1234 	return AreaHasRoomAndFloor(mb, pos);
1235 }
MapIsAreaClearOrWall(const MapBuilder * mb,struct vec2i pos,struct vec2i size)1236 bool MapIsAreaClearOrWall(
1237 	const MapBuilder *mb, struct vec2i pos, struct vec2i size)
1238 {
1239 	return MapBuilderIsAreaFunc(mb, pos, size, IsClearOrWall);
1240 }
1241 // Find the size of the passage created by the overlap of two rooms
1242 // To find whether an overlap is valid,
1243 // collect the perimeter walls that overlap
1244 // Two possible cases where there is a valid overlap:
1245 // - if there are exactly two overlapping perimeter walls
1246 //   i.e.
1247 //          X
1248 // room 2 XXXXXX
1249 //        X X     <-- all tiles between either belong to room 1 or room 2
1250 //     XXXXXX
1251 //        X    room 1
1252 //
1253 // - if the collection of overlapping tiles are contiguous
1254 //   i.e.
1255 //        X room 1 X
1256 //     XXXXXXXXXXXXXXX
1257 //     X     room 2  X
1258 //
1259 // In both cases, the overlap is valid if all tiles in between are room or
1260 // perimeter tiles. The size of the passage is given by the largest difference
1261 // in the x or y coordinates between the first and last intersection tiles,
1262 // minus 1
MapGetRoomOverlapSize(const MapBuilder * mb,const Rect2i r,uint16_t * overlapAccess)1263 bool MapGetRoomOverlapSize(
1264 	const MapBuilder *mb, const Rect2i r, uint16_t *overlapAccess)
1265 {
1266 	int numOverlaps = 0;
1267 	struct vec2i overlapMin = svec2i_zero();
1268 	struct vec2i overlapMax = svec2i_zero();
1269 
1270 	// Find perimeter tiles that overlap
1271 	RECT_FOREACH(r)
1272 	// only check perimeter
1273 	if (_v.x != r.Pos.x && _v.x != r.Pos.x + r.Size.x - 1 && _v.y != r.Pos.y &&
1274 		_v.y != r.Pos.y + r.Size.y - 1)
1275 	{
1276 		continue;
1277 	}
1278 	if (!MapIsTileIn(mb->Map, _v))
1279 	{
1280 		continue;
1281 	}
1282 	const TileClass *tile = MapBuilderGetTile(mb, _v);
1283 	// Check if this wall is part of a room
1284 	if (tile->Type != TILE_CLASS_WALL || !AreaHasRoomAndFloor(mb, _v))
1285 	{
1286 		continue;
1287 	}
1288 	// Get the access level of the room
1289 	struct vec2i v2;
1290 	for (v2.y = _v.y - 1; v2.y <= _v.y + 1; v2.y++)
1291 	{
1292 		for (v2.x = _v.x - 1; v2.x <= _v.x + 1; v2.x++)
1293 		{
1294 			const TileClass *t = MapBuilderGetTile(mb, v2);
1295 			if (t != NULL && t->IsRoom)
1296 			{
1297 				if (overlapAccess != NULL)
1298 				{
1299 					*overlapAccess |= MapBuildGetAccess(mb, v2);
1300 				}
1301 			}
1302 		}
1303 	}
1304 	if (numOverlaps == 0)
1305 	{
1306 		overlapMin = overlapMax = _v;
1307 	}
1308 	else
1309 	{
1310 		overlapMin = svec2i_min(overlapMin, _v);
1311 		overlapMax = svec2i_max(overlapMax, _v);
1312 	}
1313 	numOverlaps++;
1314 	RECT_FOREACH_END()
1315 	if (numOverlaps < 2)
1316 	{
1317 		return 0;
1318 	}
1319 
1320 	// Now check that all tiles between the first and last tiles are room or
1321 	// perimeter tiles
1322 	struct vec2i v;
1323 	for (v.y = overlapMin.y; v.y <= overlapMax.y; v.y++)
1324 	{
1325 		for (v.x = overlapMin.x; v.x <= overlapMax.x; v.x++)
1326 		{
1327 			const TileClass *tile = MapBuilderGetTile(mb, v);
1328 			if (tile->Type == TILE_CLASS_WALL)
1329 			{
1330 				// Check if this wall is part of a room
1331 				if (!AreaHasRoomAndFloor(mb, v))
1332 				{
1333 					return 0;
1334 				}
1335 			}
1336 			else if (!tile->IsRoom)
1337 			{
1338 				// invalid tile type
1339 				return 0;
1340 			}
1341 		}
1342 	}
1343 
1344 	return MAX(overlapMax.x - overlapMin.x, overlapMax.y - overlapMin.y) - 1;
1345 }
1346 // Check that this area does not overlap two or more "walls"
MapIsLessThanTwoWallOverlaps(const MapBuilder * mb,struct vec2i pos,struct vec2i size)1347 bool MapIsLessThanTwoWallOverlaps(
1348 	const MapBuilder *mb, struct vec2i pos, struct vec2i size)
1349 {
1350 	if (!MapIsTileIn(mb->Map, pos))
1351 	{
1352 		return false;
1353 	}
1354 
1355 	struct vec2i v;
1356 	int numOverlaps = 0;
1357 	struct vec2i overlapMin = svec2i_zero();
1358 	struct vec2i overlapMax = svec2i_zero();
1359 	for (v.y = pos.y; v.y < pos.y + size.y; v.y++)
1360 	{
1361 		for (v.x = pos.x; v.x < pos.x + size.x; v.x++)
1362 		{
1363 			// only check perimeter
1364 			if (v.x != pos.x && v.x != pos.x + size.x - 1 && v.y != pos.y &&
1365 				v.y != pos.y + size.y - 1)
1366 			{
1367 				continue;
1368 			}
1369 			if (MapBuilderGetTile(mb, v)->Type != TILE_CLASS_WALL)
1370 			{
1371 				continue;
1372 			}
1373 			// Check if this wall is part of a room
1374 			if (!AreaHasRoomAndFloor(mb, v))
1375 			{
1376 				if (numOverlaps == 0)
1377 				{
1378 					overlapMin = overlapMax = v;
1379 				}
1380 				else
1381 				{
1382 					overlapMin = svec2i_min(overlapMin, v);
1383 					overlapMax = svec2i_max(overlapMax, v);
1384 				}
1385 				numOverlaps++;
1386 			}
1387 		}
1388 	}
1389 	if (numOverlaps < 2)
1390 	{
1391 		return true;
1392 	}
1393 
1394 	// Now check that all tiles between the first and last tiles are
1395 	// pillar tiles
1396 	for (v.y = overlapMin.y; v.y <= overlapMax.y; v.y++)
1397 	{
1398 		for (v.x = overlapMin.x; v.x <= overlapMax.x; v.x++)
1399 		{
1400 			if (MapBuilderGetTile(mb, v)->Type != TILE_CLASS_WALL)
1401 			{
1402 				// invalid tile type
1403 				return false;
1404 			}
1405 			// Check if this wall is not part of a room
1406 			if (AreaHasRoomAndFloor(mb, v))
1407 			{
1408 				return false;
1409 			}
1410 		}
1411 	}
1412 
1413 	return true;
1414 }
1415 
MapFillRect(MapBuilder * mb,const Rect2i r,const TileClass * edge,const TileClass * fill)1416 void MapFillRect(MapBuilder *mb, const Rect2i r, const TileClass *edge, const TileClass *fill)
1417 {
1418 	RECT_FOREACH(r)
1419 	const TileClass *tc = Rect2iIsAtEdge(r, _v) ? edge : fill;
1420 	MapBuilderSetTile(mb, _v, tc);
1421 	RECT_FOREACH_END()
1422 }
1423 
GenerateAccessMask(int * accessLevel)1424 uint16_t GenerateAccessMask(int *accessLevel)
1425 {
1426 	uint16_t accessMask = 0;
1427 	switch (rand() % 20)
1428 	{
1429 	case 0:
1430 		if (*accessLevel >= 4)
1431 		{
1432 			accessMask = MAP_ACCESS_RED;
1433 			*accessLevel = 5;
1434 		}
1435 		break;
1436 	case 1:
1437 	case 2:
1438 		if (*accessLevel >= 3)
1439 		{
1440 			accessMask = MAP_ACCESS_BLUE;
1441 			if (*accessLevel < 4)
1442 			{
1443 				*accessLevel = 4;
1444 			}
1445 		}
1446 		break;
1447 	case 3:
1448 	case 4:
1449 	case 5:
1450 		if (*accessLevel >= 2)
1451 		{
1452 			accessMask = MAP_ACCESS_GREEN;
1453 			if (*accessLevel < 3)
1454 			{
1455 				*accessLevel = 3;
1456 			}
1457 		}
1458 		break;
1459 	case 6:
1460 	case 7:
1461 	case 8:
1462 	case 9:
1463 		if (*accessLevel >= 1)
1464 		{
1465 			accessMask = MAP_ACCESS_YELLOW;
1466 			if (*accessLevel < 2)
1467 			{
1468 				*accessLevel = 2;
1469 			}
1470 		}
1471 		break;
1472 	}
1473 	if (*accessLevel < 1)
1474 	{
1475 		*accessLevel = 1;
1476 	}
1477 	return accessMask;
1478 }
1479 
MapGenerateRandomExitArea(Map * map,const int mission)1480 static void MapGenerateRandomExitArea(Map *map, const int mission)
1481 {
1482 	const Tile *t = NULL;
1483 	Exit exit;
1484 	exit.Mission = mission + 1;
1485 	exit.Hidden = false;
1486 	for (int i = 0; i < 10000 && (t == NULL || !TileCanWalk(t)); i++)
1487 	{
1488 		exit.R.Pos.x = (rand() % (abs(map->Size.x) - EXIT_WIDTH - 1));
1489 		exit.R.Size.x = EXIT_WIDTH + 1;
1490 		exit.R.Pos.y = (rand() % (abs(map->Size.y) - EXIT_HEIGHT - 1));
1491 		exit.R.Size.y = EXIT_HEIGHT + 1;
1492 		// Check that the exit area is walkable
1493 		t = MapGetTile(map, Rect2iCenter(exit.R));
1494 	}
1495 	CArrayPushBack(&map->exits, &exit);
1496 }
1497 
MapAddDrains(MapBuilder * mb)1498 static void MapAddDrains(MapBuilder *mb)
1499 {
1500 	// Randomly add drainage tiles for classic map type;
1501 	// For other map types drains are regular map objects
1502 	const MapObject *drain = StrMapObject("drain0");
1503 	for (int i = 0; i < mb->Map->Size.x * mb->Map->Size.y / 45; i++)
1504 	{
1505 		// Make sure drain tiles aren't next to each other
1506 		struct vec2i v = MapGetRandomTile(mb->Map);
1507 		v.x &= 0xFFFFFE;
1508 		v.y &= 0xFFFFFE;
1509 		if (MapTileIsNormalFloor(mb, v))
1510 		{
1511 			MapTryPlaceOneObject(mb, v, drain, 0, false);
1512 		}
1513 	}
1514 }
1515