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