UFO: Alien Invasion
Doxygen documentation generating
sv_rma.cpp
Go to the documentation of this file.
1 
8 /*
9 All original material Copyright (C) 2002-2023 UFO: Alien Invasion.
10 
11 Original file from Quake 2 v3.21: quake2-2.31/server/sv_init.c
12 
13 Copyright (C) 1997-2001 Id Software, Inc.
14 
15 This program is free software; you can redistribute it and/or
16 modify it under the terms of the GNU General Public License
17 as published by the Free Software Foundation; either version 2
18 of the License, or (at your option) any later version.
19 
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23 
24 See the GNU General Public License for more details.
25 
26 You should have received a copy of the GNU General Public License
27 along with this program; if not, write to the Free Software
28 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 
30 */
31 
32 #include "server.h"
33 #include "sv_rma.h"
34 #include "../shared/parse.h"
35 #include "../shared/thread.h"
36 
37 #define ASSEMBLE_THREADS 2
38 
39 #define PRINT_RMA_PROGRESS 0
40 
41 #define SORT_BY_SIZE 1
42 
44 #define RMA2_MAX_REC 64
45 
46 /* How to calculate this value:
47  * Take he largest map of the project, eg. +farm
48  * farm large has a size of 12x12.
49  * If we had only 'fits everywhere'-tiles of size 1x1,
50  * we'd need 12x12=144 x tiletypes(25) = 3600 (in theory).
51  * Larger tiles eg. 2x2 would reduce the positions from 144 to 11x11=121
52  * Required tiles with exactly one instance ie. "1 1" reduce both the # of tiletypes
53  * and the # of positions by their size.
54  * The neighbouring requirements of required tiles will further reduce the value.
55  * => It's best to determine a working value empirically.
56  */
57 #define RMA2_MAX_TILEPOS 1700
58 
59 #define TCM 50
60 
62 
64 #define GAPS 25
65 #define MAX_RANDOM_MAP_WIDTH 32
66 #define MAX_RANDOM_MAP_HEIGHT 32
67 
69 
70 static int minMissingSolids;
71 static SDL_sem* mapSem;
72 static SDL_cond* mapCond;
73 static SDL_mutex* mapLock;
74 static Uint32 threadID;
75 
76 #define MAX_MAPASSEMBLIES 32
77 
79 #define MAX_TILETYPES 128
80 #define MAX_TILESETS 16
81 #define MAX_TILESETTILES 16
82 #define MAX_TILESIZE 16
83 #define MAX_FIXEDTILES 64
84 
86 class Tile {
87 public:
88  char id[MAX_VAR];
89  unsigned long spec[MAX_TILESIZE][MAX_TILESIZE];
90  int w, h;
91  int area;
92 };
93 
95 class TileSet {
96 public:
97  char id[MAX_VAR];
99  int numTiles;
100 };
101 
102 #define MAX_ASSEMBLY_SEEDS 32
103 
108 class Assembly
109 {
110 public:
111  char id[MAX_VAR];
112  char title[MAX_VAR];
118  int numFixed;
119  int width, height;
123  int size;
124  int dx, dy;
130  int numSeeds;
131 };
132 
137 typedef struct mToPlace_s {
139  int min, max;
140  int cnt;
141 } mToPlace_t;
142 
147 typedef struct mPlaced_s {
148  const Tile* tile;
149  int x, y;
150  int idx, pos;
151 } mPlaced_t;
152 
153 class MapInfo
154 {
155  char name[MAX_VAR];
156 public:
159 
167  mPlaced_t mPlaced[MAX_MAPTILES];
168  int numPlaced;
174  int numTiles;
175  unsigned long lineFlags;
180  int asmIdx;
182  int retryCnt;
184  inline const Assembly* getCurrentAssembly () const {
185  return &assemblies[asmIdx];
186  }
187  inline void setName (const char* mapTheme) {
188  Q_strncpyz(this->name, mapTheme, sizeof(this->name));
189  }
190  inline const char* getName () const {
191  return name;
192  }
193  inline const char* getCurrentAssemblyTitle () const {
194  return assemblies[asmIdx].title;
195  }
196 };
197 
203 static void RandomList (const int n, short* list)
204 {
205  short i;
206 
207  for (i = 0; i < n; i++)
208  list[i] = i;
209 
210  for (i = 0; i < n; i++) {
211  const short r = rand() % (i + (n - i));
212  const short t = list[r];
213  list[r] = list[i];
214  list[i] = t;
215  }
216 }
217 
218 #define ALL_TILES (0xfffffffeUL)
219 #define IS_SOLID(x) ((x)&1UL)
220 
245 static unsigned long tileMask (const char chr)
246 {
247  if (chr == '+')
248  return 1UL;
249  else if (chr == '0')
250  return ALL_TILES;
251  else if (chr >= '1' && chr <= '5')
252  return 1UL << (chr - '0');
253  else if (chr >= 'a' && chr <= 'z')
254  return 1UL << (chr - 'a' + 6);
255  else if (chr >= 'A' && chr <= 'Z')
256  return 1UL << (chr - 'A' + 6);
257 
258  Com_Error(ERR_DROP, "SV_ParseMapTile: Invalid tile char '%c'", chr);
259 }
260 
261 static void SV_TileMaskToString (unsigned long m, char* str)
262 {
263  int i;
264  int j = 0; /* writepos */
265 
266  if (m == ALL_TILES) {
267  str[0] = '0';
268  str[1] = 0;
269  return;
270  }
271  if (m & 1) {
272  str[0] = '+';
273  j = 1;
274  }
275  for (i = 1; i < 6; i++) {
276  if (m >> i & 1) {
277  str[j] = '0' + i;
278  j++;
279  }
280  }
281  for (i = 6; i < 32; i++) {
282  if (m >> i & 1) {
283  str[j] = 'a' - 6 + i;
284  j++;
285  }
286  }
287  str[j] = 0;
288 }
289 
290 #define ACW 6 /* ascii cell width */
291 #define ACH 3 /* ascii cell height */
292 #define MMW 13 /* map max width 13 means we support 12 */
293 #define MMH 13 /* map max height */
294 static void SV_RmaPrintMap (const MapInfo* map)
295 {
296  const Assembly* mAsm = map->getCurrentAssembly();
297  char screen[(MMH + 1) * ACH][(MMW + 1) * ACW];
298  int i, j;
299 
300  assert(mAsm->height < MMH);
301  assert(mAsm->width < MMW);
302 
303  /* initialize */
304  int rb = (1 + mAsm->width) * ACW; /* index of right border */
305  OBJSET(screen, ' ');
306  for (i = 0; i < MMH * ACH + 1; i++) {
307  screen[i][rb + 1] = '|';
308  screen[i][rb + 2] = 0;
309  }
310 
311  /* fill in the data */
312  for (i = 0; i < map->numPlaced; i++) {
313  const mPlaced_t* mp = &map->mPlaced[i];
314  const Tile* tile = mp->tile;
315  int tx, ty;
316  const char* tn = tile->id + 1;
317 
318  if (!strncmp(tn, "craft_", 6))
319  tn += 6;
320  for (ty = 0; ty < tile->h; ty++) {
321  for (tx = 0; tx < tile->w; tx++) {
322  if (IS_SOLID(tile->spec[ty][tx])) {
323  int cbX = ACW * (mp->x + tx);
324  int cbY = ACH * (mp->y + ty);
325  char flags[33] = {0,};
326 
327  /* write the tilename */
328  for (j = 0; j < ACW - 1; j++) {
329  if (tn[j])
330  screen[cbY + ACH - 1][cbX + 1 + j] = tn[j];
331  else
332  break;
333  }
334 
335  /* get the properties of that solid */
336  SV_TileMaskToString(tile->spec[ty][tx], flags);
337  /* write the flags */
338  for (j = 0; j < ACW - 1; j++) {
339  if (flags[j])
340  screen[cbY + ACH - 2][cbX + 1 + j] = flags[j];
341  else
342  break;
343  }
344 
345 
346  /* left border of tile */
347  if (tx > 0 && !IS_SOLID(tile->spec[ty][tx - 1])) {
348  for (j = 0; j < ACH; j++)
349  screen[cbY + j][cbX] = '!';
350  }
351  if (!IS_SOLID(tile->spec[ty][tx + 1])) {
352  for (j = 0; j < ACH; j++)
353  screen[cbY + j][cbX + ACW] = '!';
354  }
355  if (ty > 0 && !IS_SOLID(tile->spec[ty - 1][tx])) {
356  for (j = 1; j < ACW; j++)
357  screen[cbY][cbX + j] = '-';
358  }
359  if (!IS_SOLID(tile->spec[ty + 1][tx])) {
360  for (j = 1; j < ACW; j++)
361  screen[cbY + ACH][cbX + j] = '-';
362  }
363  }
364  }
365  }
366  }
367 
368  /* now add the specs of the gaps */
369  int cx, cy;
370  int height = mAsm->height;
371  int width = mAsm->width;
372  for (cy = 0; cy <= height; cy++) {
373  for (cx = 0; cx <= width; cx++) {
374  if (!IS_SOLID(map->curMap[cy][cx])) {
375  const int cbX = ACW * (cx);
376  const int cbY = ACH * (cy);
377  char flags2[33] = {0,};
378 
379  /* get the requirements of that gap */
380  SV_TileMaskToString(map->curMap[cy][cx], flags2);
381  /* write the flags */
382  for (j = 0; j < ACW - 1; j++) {
383  if (flags2[j])
384  screen[cbY + ACH - 2][cbX + 1 + j] = flags2[j];
385  else
386  break;
387  }
388  }
389  }
390  }
391 
392  /* print it */
393  const char* underscores = "_________________________________________________________________________\n";
394  Com_Printf("\nCurrent state of the map:\n");
395  int w = ACW * (MMW - 1 - mAsm->width);
396  Com_Printf("%s", underscores + w);
397  int h = ACH * (height + 1);
398  for (i = h; i >= ACH; i--)
399  Com_Printf("%s\n", screen[i] + ACW);
400  Com_Printf("%s", underscores + w);
401 }
402 
403 static const TileSet* SV_GetMapTileSet (const MapInfo* map, const char* tileSetName)
404 {
405  for (int i = 0; i < map->numTileSets; i++) {
406  const TileSet* tileSet = &map->tileSets[i];
407  if (Q_streq(tileSetName, tileSet->id))
408  return tileSet;
409  }
410 
411  return nullptr;
412 }
413 
414 static inline const Tile* SV_GetMapTile (const MapInfo* map, const char* tileName)
415 {
416  for (int i = 0; i < map->numTiles; i++) {
417  const Tile* tile = &map->mTile[i];
418  if (Q_streq(tileName, tile->id))
419  return tile;
420  }
421 
422  return nullptr;
423 }
424 
429 static bool SV_ParseMapTileSet (const char* filename, const char** text, MapInfo* map, bool inherit)
430 {
431  const char* errhead = "SV_ParseMapTileSet: Unexpected end of file (";
432  const char* token;
433  TileSet* target = &map->tileSets[map->numTileSets];
434 
435  OBJZERO(*target);
436 
437  /* get tileset name */
438  token = Com_EParse(text, errhead, filename);
439  if (!*text)
440  return false;
441 
442  Q_strncpyz(target->id, token, sizeof(target->id));
443 
444  /* start parsing the block */
445  token = Com_EParse(text, errhead, filename);
446  if (!*text)
447  return false;
448  if (*token != '{') {
449  Com_Printf("SV_ParseMapTileSet: Expected '{' for tileset '%s' (%s)\n", target->id, filename);
450  return false;
451  }
452 
453  do {
454  token = Com_EParse(text, errhead, filename);
455  if (!*text)
456  return false;
457  if (token[0] != '}') {
458  if (target->numTiles >= MAX_TILESETTILES)
459  Com_Error(ERR_DROP, "Max tileset limit reached for tileset '%s'", target->id);
460  else { /* just to get rid of the 'mixed decl and code' warning */
461  char* tileTarget = target->tiles[target->numTiles];
462  const size_t size = sizeof(target->tiles[target->numTiles]);
463  if (inherit) {
464  if (token[0] == '+')
465  token++;
466 
467  Com_sprintf(tileTarget, size, "%s%s", map->inheritBasePath, token);
468  } else {
469  Q_strncpyz(tileTarget, token, size);
470  }
471 
472  if (SV_GetMapTile(map, tileTarget) != nullptr)
473  target->numTiles++;
474  else
475  Com_Error(ERR_DROP, "Did not find tile '%s' from tileset '%s'", tileTarget, target->id);
476  }
477  }
478  } while (token[0] != '}');
479 
480  map->numTileSets++;
481  return false;
482 }
483 
488 static bool SV_ParseMapTile (const char* filename, const char** text, MapInfo* map, bool inherit)
489 {
490  const char* errhead = "SV_ParseMapTile: Unexpected end of file (";
491  Tile* target = &map->mTile[map->numTiles];
492  target->area = 0;
493 
494  /* get tile name */
495  const char* token = Com_EParse(text, errhead, filename);
496  if (!*text)
497  return false;
498 
499  OBJZERO(*target);
500 
501  if (inherit) {
502  if (token[0] == '+')
503  token++;
504  Com_sprintf(target->id, sizeof(target->id), "%s%s", map->inheritBasePath, token);
505  } else {
506  Q_strncpyz(target->id, token, sizeof(target->id));
507  }
508 
509  /* start parsing the block */
510  token = Com_EParse(text, errhead, filename);
511  if (!*text)
512  return false;
513  if (*token != '{') {
514  Com_Printf("SV_ParseMapTile: Expected '{' for tile '%s' (%s)\n", target->id, filename);
515  return false;
516  }
517 
518  /* get width and height */
519  token = Com_EParse(text, errhead, filename);
520  if (!*text)
521  return false;
522  target->w = atoi(token);
523 
524  token = Com_EParse(text, errhead, filename);
525  if (!*text)
526  return false;
527  target->h = atoi(token);
528 
529  if (target->w > MAX_TILESIZE || target->h > MAX_TILESIZE) {
530  Com_Printf("SV_ParseMapTile: Bad tile size [%i %i] (%s) (max. [%i %i])\n", target->w, target->h, filename, MAX_TILESIZE, MAX_TILESIZE);
531  *text = strchr(*text, '}');
532  return false;
533  }
534 
535  /* get tile specs */
536  for (int y = target->h - 1; y >= 0; y--)
537  for (int x = 0; x < target->w; x++) {
538  token = Com_EParse(text, errhead, filename);
539  if (!*text || *token == '}') {
540  Com_Printf("SV_ParseMapTile: Bad tile desc in '%s' - not enough entries for size\n", target->id);
541  *text = strchr(*text, '}') + 1;
542  return 0;
543  }
544  target->spec[y][x] = 0UL;
545  for (int i = 0; token[i]; i++) {
546  target->spec[y][x] |= tileMask(token[i]);
547  }
548  if (IS_SOLID(target->spec[y][x]))
549  target->area++; /* # of solid tiles */
550  }
551 
552  token = Com_EParse(text, errhead, filename);
553 
554  /* get connections */
555  if (*token != '}')
556  Com_Printf("SV_ParseMapTile: Bad tile desc in '%s' - too many entries for size\n", target->id);
557 
558  /* successfully parsed - this tile counts */
559  return true;
560 }
561 
572 static const char* SV_GetCvarToken (const MapInfo* map, const Assembly* a, const char* token, const char* filename, const char** text, const char* errhead)
573 {
574  const cvar_t* cvar;
575 
576  Com_DPrintf(DEBUG_SERVER, "SV_GetCvarToken: cvar replacement: %s\n", token);
577 
578  cvar = Cvar_FindVar(token);
579 
580  token = Com_EParse(text, errhead, filename);
581  if (!text || token[0] == '}')
582  return nullptr;
583 
584  if (cvar == nullptr)
585  return token;
586 
587  Com_DPrintf(DEBUG_SERVER, "SV_ParseAssembly: cvar replacement value: %s\n", cvar->string);
588  if (cvar->string[0] != '+') {
589  Com_Printf("SV_ParseAssembly: warning - cvar '%s' value doesn't seem to be a valid tile id '%s' - set to default '%s'\n",
590  cvar->name, cvar->string, token);
591  Cvar_Set(cvar->name, "%s", token);
592  if (token[0] != '+' && !strchr(token, '/'))
593  Com_Error(ERR_DROP, "SV_ParseAssembly: wrong tile id in assembly '%s'", a->id);
594 
595  return token;
596  }
597 
598  /*
599  * Allow cvar replacement to use inherited tiles - see FR #3446
600  * @todo a better way to do this?
601  */
602  const char* tokenTile = strrchr(token, '/');
603  if (tokenTile) {
604  const char* cvarTile = cvar->string + 1;
605  for (int i = 0; i < map->numTiles; i++) {
606  const char* tileId = map->mTile[i].id;
607  const char* tileName = strrchr(tileId, '/');
608  if (tileName && strstr(tileName, cvarTile) && !Q_strncasecmp(tileId, token, tokenTile - token))
609  return tileId;
610  }
611  }
612 
613  return cvar->string;
614 }
615 
616 static const char* SV_GetTileFromTileSet (const MapInfo* map, const char* filename, const char** text, const Assembly* a)
617 {
618  const char* errhead = "SV_GetTileFromTileSet: Unexpected end of file (";
619  const TileSet* tileSet;
620  int random;
621  const char* token;
622 
623  /* get tileset id */
624  token = Com_EParse(text, errhead, filename);
625  if (!text)
626  Com_Error(ERR_DROP, "SV_GetTileFromTileSet: illegal tileset syntax in assembly '%s' in %s", a->id, filename);
627 
628  tileSet = SV_GetMapTileSet(map, token);
629  if (tileSet == nullptr)
630  Com_Error(ERR_DROP, "SV_GetTileFromTileSet: Could not find tileset %s in %s (assembly %s)", token, filename, a->id);
631 
632  random = rand() % tileSet->numTiles;
633  return tileSet->tiles[random];
634 }
635 
644 static bool SV_ParseAssemblySeeds (MapInfo* map, const char* filename, const char** text, Assembly* a)
645 {
646  const char* errhead = "SV_ParseAssemblySeeds: Unexpected end of file (";
647  const char* token;
648 
649  /* start parsing the block */
650  token = Com_EParse(text, errhead, filename);
651  if (!*text)
652  return false;
653  if (*token != '{') {
654  Com_Printf("SV_ParseAssemblySeeds: Expected '{' for seed of assembly '%s' (%s)\n", a->id, filename);
655  return false;
656  }
657 
658  for (;;) {
659  token = Com_EParse(text, errhead, filename);
660  if (!*text || token[0] == '}')
661  break;
662 
663  if (a->numSeeds < lengthof(a->seeds)) {
664  a->seeds[a->numSeeds++] = atoi(token);
665  } else {
666  Com_Printf("too many seeds for %s (%s) - ignore seed %s\n", a->id, filename, token);
667  }
668  }
669  return true;
670 }
671 
672 static void SV_GetTilesFromTileSet (const MapInfo* map, const char* filename, const char** text, Assembly* a)
673 {
674  const char* errhead = "SV_GetTilesFromTileSet: Unexpected end of file (";
675  const TileSet* tileSet;
676  int min, max;
677  const char* token;
678 
679  /* get tileset id */
680  token = Com_EParse(text, errhead, filename);
681  if (!text)
682  Com_Error(ERR_DROP, "SV_GetTilesFromTilesSet: illegal tileset syntax in assembly '%s' in %s", a->id, filename);
683  tileSet = SV_GetMapTileSet(map, token);
684  if (tileSet == nullptr)
685  Com_Error(ERR_DROP, "SV_GetTilesFromTilesSet: Could not find tileset %s in %s (assembly %s)", token, filename, a->id);
686 
687  /* get min and max tileset number */
688  token = Com_EParse(text, errhead, filename);
689  if (!text || *token == '}')
690  Com_Error(ERR_DROP, "SV_GetTilesFromTilesSet: Error in assembly %s (invalid syntax for tileset %s)", filename, tileSet->id);
691  if (!strstr(token, " "))
692  Com_Error(ERR_DROP, "SV_GetTilesFromTilesSet: Error in assembly %s (min max value of tileset %s)", filename, tileSet->id);
693  sscanf(token, "%i %i", &min, &max);
694  if (min > max)
695  Com_Error(ERR_DROP, "SV_GetTilesFromTilesSet: Error in assembly %s (min is bigger than max for tileset %s)", filename, tileSet->id);
696  if (max <= 0)
697  Com_Error(ERR_DROP, "SV_GetTilesFromTilesSet: Error in assembly %s (max is <= 0 for tileset %s)", filename, tileSet->id);
698  /* set min and max tile numbers (increasing random tiles until the required number is reached) */
699  for (int i = max, j = min; i > 0; --i) {
700  const int random = rand() % tileSet->numTiles;
701  const Tile* tile = SV_GetMapTile(map, tileSet->tiles[random]);
702  if (tile != nullptr) {
703  const ptrdiff_t tileIdx = tile - map->mTile;
704  ++a->max[tileIdx];
705  if (j > 0) {
706  ++a->min[tileIdx];
707  --j;
708  }
709  } else {
710  Com_Error(ERR_DROP, "Could not find tile: '%s' in tileset '%s' (%s)", tileSet->tiles[random], tileSet->id, filename);
711  }
712  }
713 }
714 
727 static bool SV_ParseAssembly (MapInfo* map, const char* filename, const char** text, Assembly* a)
728 {
729  const char* errhead = "SV_ParseAssembly: Unexpected end of file (";
730  const char* token;
731  int x, y;
732  const Tile* tile;
733 
734  /* get assembly name */
735  token = Com_EParse(text, errhead, filename);
736  if (!*text)
737  return false;
738 
739  /* init */
740  OBJZERO(*a);
741  Q_strncpyz(a->id, token, sizeof(a->id));
742  a->width = 8;
743  a->height = 8;
744  a->dx = 1;
745  a->dy = 1;
746 
747  token = Com_EParse(text, errhead, filename);
748  if (!*text || *token != '{')
749  Com_Error(ERR_DROP, "Invalid assembly definition '%s' - invalid token '%s' (%s)", a->id, token, filename);
750 
751  do {
752  /* get tile name */
753  token = Com_EParse(text, errhead, filename);
754  if (!text || *token == '}')
755  break;
756 
757  if (Q_streq(token, "title")) {
758  /* get map title */
759  token = Com_EParse(text, errhead, filename);
760  if (!text)
761  break;
762 
763  Q_strncpyz(a->title, token, sizeof(a->title));
764  continue;
765  } else if (Q_streq(token, "multiplayer")) {
766  /* get map title */
767  token = Com_EParse(text, errhead, filename);
768  if (!text)
769  break;
770 
771  /* a multiplayer only tile - forced to be exactly once in the map when
772  * we are playing a multiplayer match */
773  if (sv_maxclients->integer > 1) {
774  const Tile* t = SV_GetMapTile(map, token);
775  if (t != nullptr) {
776  const ptrdiff_t i = t - map->mTile;
777  a->min[i] = 1;
778  a->max[i] = 1;
779  } else {
780  Com_Error(ERR_DROP, "Could not find multiplayer tile: '%s' in assembly '%s' (%s)", token, a->id, filename);
781  }
782  }
783  continue;
784  } else if (Q_streq(token, "size")) {
785  /* get map size */
786  token = Com_EParse(text, errhead, filename);
787  if (!text)
788  break;
789 
790  sscanf(token, "%i %i", &a->width, &a->height);
791  a->size = a->width * a->height;
792  continue;
793  } else if (Q_streq(token, "seeds")) {
794  if (!SV_ParseAssemblySeeds(map, filename, text, a))
795  return false;
796  continue;
797  } else if (Q_streq(token, "grid")) {
798  /* get map size */
799  token = Com_EParse(text, errhead, filename);
800  if (!text)
801  break;
802 
803  sscanf(token, "%i %i", &a->dx, &a->dy);
804  continue;
805  /* chose a tile from a tileset */
806  } else if (Q_streq(token, "tileset")) {
807  SV_GetTilesFromTileSet(map, filename, text, a);
808  continue;
809  /* fix tilename "x y" */
810  } else if (Q_streq(token, "fix")) {
811  const Tile* t;
812 
813  /* get tile */
814  token = Com_EParse(text, errhead, filename);
815  if (!text)
816  break;
817 
818  if (token[0] == '*') {
819  token = SV_GetCvarToken(map, a, token + 1, filename, text, errhead);
820  if (token == nullptr)
821  break;
822  } else if (Q_streq(token, "tileset")) {
823  token = SV_GetTileFromTileSet(map, filename, text, a);
824  }
825 
826  t = SV_GetMapTile(map, token);
827  if (t != nullptr) {
828  const ptrdiff_t i = t - map->mTile;
829  if (a->numFixed >= MAX_FIXEDTILES)
830  Com_Error(ERR_DROP, "SV_ParseAssembly: Too many fixed tiles in assembly '%s' (%s)", a->id, filename);
831 
832  /* get coordinates */
833  token = Com_EParse(text, errhead, filename);
834  if (!text)
835  Com_Error(ERR_DROP, "SV_ParseAssembly: Error in assembly %s - could not get coordinates for fixed tile", filename);
836 
837  sscanf(token, "%i %i", &x, &y);
838  if (x < 0 || x >= MAX_RANDOM_MAP_WIDTH) {
839  Com_Error(ERR_DROP, "SV_ParseAssembly: Error, invalid fixed coordinates given for x (%i) boundaries are: [0:%i] (%s).",
841  } else if (y < 0 || y >= MAX_RANDOM_MAP_HEIGHT) {
842  Com_Error(ERR_DROP, "SV_ParseAssembly: Error, invalid fixed coordinates given for y (%i) - boundaries are: [0:%i] (%s).",
844  }
845  a->fX[a->numFixed] = x;
846  a->fY[a->numFixed] = y;
847  a->fT[a->numFixed] = i;
848  a->numFixed++;
849  } else
850  Com_Error(ERR_DROP, "Could not find fixed tile: '%s' in assembly '%s' (%s)", token, a->id, filename);
851  continue;
852  /* <format>*cvarname <defaultvalue> "min max"</format> */
853  } else if (token[0] == '*') {
854  token = SV_GetCvarToken(map, a, token + 1, filename, text, errhead);
855  if (token == nullptr)
856  break;
857  }
858 
859  tile = SV_GetMapTile(map, token);
860  if (tile != nullptr) {
861  const ptrdiff_t i = tile - map->mTile;
862  /* get min and max tile number */
863  token = Com_EParse(text, errhead, filename);
864  if (!text || *token == '}')
865  Com_Error(ERR_DROP, "SV_ParseAssembly: Error in assembly %s (invalid syntax for tile %s)", filename, tile->id);
866 
867  if (!strstr(token, " "))
868  Com_Error(ERR_DROP, "SV_ParseAssembly: Error in assembly %s (min max value of tile %s)", filename, tile->id);
869 
870  sscanf(token, "%i %i", &x, &y);
871  a->min[i] = x;
872  a->max[i] = y;
873  if (a->min[i] > a->max[i])
874  Com_Error(ERR_DROP, "SV_ParseAssembly: Error in assembly %s (min is bigger than max for tile %s)", filename, tile->id);
875  if (a->max[i] <= 0)
876  Com_Error(ERR_DROP, "SV_ParseAssembly: Error in assembly %s (max is <= 0 for tile %s)", filename, tile->id);
877  } else {
878  Com_Error(ERR_DROP, "Could not find tile: '%s' in assembly '%s' (%s)", token, a->id, filename);
879  }
880  } while (text);
881 
882  return true;
883 }
884 
885 
892 static void SV_CombineAlternatives (unsigned long* mapAlts, const unsigned long tileAlts)
893 {
894  /* don't touch solid fields of the map, return if tile has no connection info */
895  if (IS_SOLID(*mapAlts) || (tileAlts == ALL_TILES))
896  return;
897 
898  /* copy if tile is solid */
899  if (IS_SOLID(tileAlts)) {
900  *mapAlts = tileAlts;
901  /* combine otherways */
902  } else {
903  *mapAlts &= tileAlts;
904  }
905 }
906 
910 static void SV_ClearMap (MapInfo* map)
911 {
912  unsigned long* mp = &map->curMap[0][0];
913  const unsigned long* end = &map->curMap[MAX_RANDOM_MAP_HEIGHT - 1][MAX_RANDOM_MAP_WIDTH - 1];
914 
915  while (mp <= end)
916  *(mp++) = ALL_TILES;
917 }
918 
926 static bool SV_FitTile (const MapInfo* map, const Tile* tile, const int x, const int y)
927 {
928  int tx, ty;
929  const unsigned long* spec = nullptr;
930  const unsigned long* m = nullptr;
931  const Assembly* mAsm = map->getCurrentAssembly();
932 
933  /* check for valid grid positions */
934  assert(x % mAsm->dx == 0);
935  assert(y % mAsm->dy == 0);
936  assert(tile);
937 
938  if (x < 0 || y < 0)
939  return false;
940 
941  /* check for map border */
942  if (x + tile->w > mAsm->width + 2 || y + tile->h > mAsm->height + 2)
943  return false;
944  unsigned long combined;
945 #if 0
946  /* shortcut: most tiles are solid at [1][1], so check this first */
947  spec = &tile->spec[1][1];
948  m = &map->curMap[y+1][x+1];
949  combined = (*m) & (*spec);
950  if (IS_SOLID(combined) || !combined)
951  return false;
952 #endif
953  /* test for fit */
954  spec = &tile->spec[0][0];
955  m = &map->curMap[y][x];
956  for (ty = 0; ty < tile->h; ty++) {
957  for (tx = 0; tx < tile->w; tx++, spec++, m++) {
958  combined = (*m) & (*spec);
959 
960  /* quit if both are solid or no equal connection is found */
961  if (IS_SOLID(combined) || !combined)
962  return false;
963  }
964  spec += (MAX_TILESIZE - tile->w);
965  m += (MAX_RANDOM_MAP_WIDTH - tile->w);
966  }
967 
968  return true;
969 }
970 
976 static bool SV_TestFilled (const MapInfo* map)
977 {
978  int x, y;
979  const Assembly* mAsm = map->getCurrentAssembly();
980 
981  for (y = 1; y < mAsm->height + 1; y++)
982  for (x = 1; x < mAsm->width + 1; x++)
983  if (!IS_SOLID(map->curMap[y][x]))
984  return false;
985 
986  return true;
987 }
988 
992 static void SV_DumpPlaced (const MapInfo* map, int pl)
993 {
994  int x, y;
995  const Assembly* mAsm = map->getCurrentAssembly();
996  const int h = mAsm->height;
997  const int w = mAsm->width;
998  const mPlaced_t* placed = &map->mPlaced[pl];
999 
1000  Com_Printf("Placed tile %s at %d %d\n", placed->tile->id, placed->x, placed->y);
1001 
1002  for (y = h; y >= 1; y--) {
1003  for (x = 1; x < w + 1; x++) {
1004  const int dx = x - placed->x;
1005  const int dy = y - placed->y;
1006 
1007  if (dx >= 0 && dx < placed->tile->w && dy >= 0 && dy < placed->tile->h &&
1008  IS_SOLID(placed->tile->spec[dy][dx]))
1009  Com_Printf(" X");
1010  else
1011  Com_Printf(" .");
1012  }
1013  Com_Printf("\n");
1014  }
1015  Com_Printf("\n");
1016 }
1017 
1028 static void SV_AddTile (MapInfo* map, const Tile* tile, int x, int y, int idx, int pos)
1029 {
1030  int tx, ty;
1031 #ifdef DEBUG
1032  const Assembly* mAsm = map->getCurrentAssembly();
1033 
1034  /* check vor valid grid positions */
1035  assert(x % mAsm->dx == 0);
1036  assert(y % mAsm->dy == 0);
1037 #endif
1038 
1039  /* add the new tile */
1040  for (ty = 0; ty < tile->h; ty++)
1041  for (tx = 0; tx < tile->w; tx++) {
1042  assert(y + ty < MAX_RANDOM_MAP_HEIGHT);
1043  assert(x + tx < MAX_RANDOM_MAP_WIDTH);
1044 
1045  SV_CombineAlternatives(&map->curMap[y + ty][x + tx], tile->spec[ty][tx]);
1046  }
1047 
1048  /* add the tile to the array of placed tiles*/
1049  if (map->numPlaced >= MAX_MAPTILES)
1050  Com_Error(ERR_DROP, "SV_AddTile: Too many map tiles");
1051 
1052  map->mPlaced[map->numPlaced].tile = tile;
1053  map->mPlaced[map->numPlaced].x = x;
1054  map->mPlaced[map->numPlaced].y = y;
1055  map->mPlaced[map->numPlaced].idx = idx;
1056  map->mPlaced[map->numPlaced].pos = pos;
1057 
1058  map->numPlaced++;
1059 
1060  if (idx >= 0) {
1061  map->mToPlace[idx].cnt++;
1062  }
1063 }
1064 
1073 static void SV_RemoveTile (MapInfo* map, int* idx, int* pos)
1074 {
1075  int tx, ty;
1076  int i, index;
1077 
1078  SV_ClearMap(map);
1079 
1080  if (map->numPlaced == 0)
1081  return;
1082 
1083  map->numPlaced--;
1084  index = map->mPlaced[map->numPlaced].idx;
1085 
1086  if (index >= 0) {
1087  map->mToPlace[index].cnt--;
1088  }
1089 
1090  for (i = map->numPlaced; i--;) {
1091  const Tile* tile = map->mPlaced[i].tile;
1092  const int x = map->mPlaced[i].x;
1093  const int y = map->mPlaced[i].y;
1094  assert(i >= 0);
1095  assert(tile);
1096 
1097  /* add the tile again*/
1098  for (ty = 0; ty < tile->h; ty++) {
1099  for (tx = 0; tx < tile->w; tx++) {
1100  assert(y + ty < MAX_RANDOM_MAP_HEIGHT);
1101  assert(x + tx < MAX_RANDOM_MAP_WIDTH);
1102 
1103  SV_CombineAlternatives(&map->curMap[y + ty][x + tx], tile->spec[ty][tx]);
1104  }
1105  }
1106  }
1107 
1108  if (idx)
1109  *idx = index;
1110 
1111  if (pos)
1112  *pos = map->mPlaced[map->numPlaced].pos;
1113 }
1114 
1126 static void SV_BuildMapStrings (const MapInfo* map, char* asmTiles, char* asmPos, bool print)
1127 {
1128  const Assembly* mAsm = map->getCurrentAssembly();
1129 
1130  for (int i = 0; i < map->numPlaced; i++) {
1131  const mPlaced_t* pl = &map->mPlaced[i];
1132 
1134  SV_DumpPlaced(map, i);
1135 
1136  if (asmTiles[0])
1137  Q_strcat(asmTiles, MAX_TOKEN_CHARS * MAX_TILESTRINGS, " ");
1138  if (asmPos[0])
1139  Q_strcat(asmPos, MAX_TOKEN_CHARS * MAX_TILESTRINGS, " ");
1140 
1141  Q_strcat(asmTiles, MAX_TOKEN_CHARS * MAX_TILESTRINGS, "%s", pl->tile->id);
1142  Q_strcat(asmPos, MAX_TOKEN_CHARS * MAX_TILESTRINGS, "%i %i %i", (pl->x - mAsm->width / 2) * 8, (pl->y - mAsm->height / 2) * 8, 0);
1143  }
1144 
1145  if (print) {
1146  Com_Printf("Map info - tiles used: %s\n", asmTiles);
1147  Com_Printf("Map info - tiles pos: %s\n", asmPos);
1148  Com_Printf("Map info - tiles count: %i\n", map->numPlaced);
1149  }
1150 }
1151 
1161 static unsigned long SV_GapGetFlagsAtAbsPos (MapInfo* map, int tileCode, int mapW, int mapX, int mapY)
1162 {
1163  const int pos = tileCode / TCM;
1164  const int ti = tileCode % TCM;
1165  const int posX = pos % mapW;
1166  const int posY = pos / mapW;
1167  const mToPlace_t* mToPlace = map->mToPlace;
1168  const Tile* tile = mToPlace[ti].tile;
1169 
1170  return tile->spec[mapY - posY][mapX - posX];
1171 }
1172 
1184 static int availableTiles[MAX_TILETYPES][2]; /* the 2nd dimension is index and count */
1185 
1186 static bool SV_AddMissingTiles_r (MapInfo* map, int rec, int posListCnt, short myPosList[], const Tile* prevTile, int prevX, int prevY)
1187 {
1188  static int callCnt = 0;
1189  const Assembly* mAsm = map->getCurrentAssembly();
1190  const int mapW = mAsm->width;
1191  const mToPlace_t* mToPlace = map->mToPlace;
1192  int i, j = 0;
1193  int solids = 0; /* the # of places that the remaining tiles can theoretically cover */
1194  int availableTilesCnt = 0; /* the # of different tiles remaining in posTileList */
1195 
1196  assert(rec < RMA2_MAX_REC);
1197  callCnt++;
1198 
1200  int prevMaxX = 0, prevMaxY = 0;
1201  if (rec > 0) {
1202  prevMaxX = prevX + prevTile->w - 1;
1203  prevMaxY = prevY + prevTile->h - 1;
1204  }
1205 
1207  for (i = 0; i < posListCnt; i++) {
1208  const int pos = myPosList[i] / TCM;
1209  const int ti = myPosList[i] % TCM;
1210  const int x = pos % mapW;
1211  const int y = pos / mapW;
1212 
1213  if (mToPlace[ti].cnt >= mToPlace[ti].max)
1214  continue;
1215 
1216  const Tile* cTile = mToPlace[ti].tile;
1217  bool ok = false;
1219  if (rec > 0) { /* the first recursion doesn't have a previous tile */
1220  if (x > prevMaxX || y > prevMaxY || prevX > x + cTile->w - 1 || prevY > y + cTile->h - 1)
1221  ok = true; /* tiles do not overlap, so tile will still fit */
1222  }
1223 
1224  if (!ok) {
1225  ok = SV_FitTile(map, mToPlace[ti].tile, x, y);
1226  }
1227  if (ok) {
1228  /* store the posTile in our new list */
1229  assert(j < RMA2_MAX_TILEPOS);
1230  posTileList[rec][j] = myPosList[i];
1231  j++;
1232  /* store the tile index in the list of remaining tile types */
1233  int k;
1234  for (k = 0; k < availableTilesCnt; k++) {
1235  if (availableTiles[k][0] == ti) {
1236  availableTiles[k][1]++; /* inc count */
1237  break;
1238  }
1239  }
1240  if (k >= availableTilesCnt) { /* didn't find it in the list */
1241  availableTiles[availableTilesCnt][0] = ti; /* store the tile index */
1242  availableTiles[availableTilesCnt][1] = 1; /* init counter of places */
1243  availableTilesCnt++;
1244  }
1245  }
1246  }
1247 
1249  int x, y;
1250  int gapCount = 0;
1251  for (y = 1; y < mAsm->height + 1; y++) {
1252  for (x = 1; x < mAsm->width + 1; x++)
1253  if (IS_SOLID(map->curMap[y][x]))
1254  gapList[x][y][0] = -1;
1255  else {
1256  gapCount++;
1257  gapList[x][y][0] = 0;
1258  }
1259  }
1260 
1262  for (i = 0; i < availableTilesCnt; i++) {
1263  const int ti = availableTiles[i][0];
1264  const int allowed = mToPlace[ti].max - mToPlace[ti].cnt;
1265  const int possible = availableTiles[i][1];
1266  const int remaining = std::min(allowed, possible);
1267  solids += remaining * mToPlace[ti].tile->area;
1268  }
1269  if (solids < gapCount) {
1271  const int missing = gapCount - solids;
1272  SV_RmaPrintMap(map);
1273  if (minMissingSolids > missing)
1274  minMissingSolids = missing;
1275  Com_Printf("out of solids (missing: %i min: %i)\n", missing, minMissingSolids);
1276  }
1277  return false;
1278  }
1279 
1281  for (i = 0; i < j; i++) {
1282  const int pos = posTileList[rec][i] / TCM;
1283  const int ti = posTileList[rec][i] % TCM;
1284  const int x = pos % mapW;
1285  const int y = pos / mapW;
1286  const Tile* tile = mToPlace[ti].tile;
1287  int tx, ty;
1288  for (ty = 0; ty < tile->h; ty++) {
1289  for (tx = 0; tx < tile->w; tx++) {
1290  if (IS_SOLID(tile->spec[ty][tx])) {
1291  gapList[x + tx][y + ty][0] += 1;
1292  int cnt = gapList[x + tx][y + ty][0]; /* get the counter */
1293  if (cnt < GAPS + 1)
1294  gapList[x + tx][y + ty][cnt] = posTileList[rec][i]; /* remember the tilecode */
1295  }
1296  }
1297  }
1298  }
1299 
1301  for (y = 1; y < mAsm->height + 1; y++) {
1302  for (x = 1; x < mAsm->width + 1; x++) {
1303  if (gapList[x][y][0] == 0) {
1305  SV_RmaPrintMap(map);
1306  Com_Printf("uncovered gap: %i/%i\n", x, y);
1307  }
1308  return false;
1309  }
1310  }
1311  }
1312 
1313 #if 0
1314 
1315  for (y = 1; y < mAsm->height + 1; y++) {
1316  for (x = 1; x < mAsm->width + 1; x++) {
1317  Com_Printf("%2.i ", gapList[x][y][0]);
1318  }
1319  Com_Printf("\n");
1320  }
1321  Com_Printf("\n");
1322 #endif
1323 
1326  int g, h, line = 0;
1327  unsigned lineFlags = map->lineFlags; /* lineforming tiles, eg. water tiles in forest_large */
1328  unsigned nonLineFlags = (~lineFlags) ^ 1L;
1329  if (lineFlags)
1330  line = 1;
1331  for (; line >= 0; line--) { /* if there are lineforming tiles, process them first */
1332  for (g = 1; g <= GAPS; g++) { /* process the gaps with the least alternatives first */
1333  for (y = 1; y < mAsm->height + 1; y++) {
1334  for (x = 1; x < mAsm->width + 1; x++) {
1335  if (gapList[x][y][0] == g) { /* if this gap has the right amount of covering tiles */
1336  /* if we are looking for lines but the gap doesn't require a line-tile, skip */
1337  if (line && (map->curMap[y][x] & nonLineFlags))
1338  continue;
1339  for (h = 1; h <= g; h++) { /* circle through the alternatives stored for this gap */
1340  const int tc = gapList[x][y][h];
1341  const int pos = tc / TCM;
1342  const int ti = tc % TCM;
1343  const int px = pos % mapW;
1344  const int py = pos / mapW;
1345 
1346  SV_AddTile(map, mToPlace[ti].tile, px, py, ti, pos);
1347 #if PRINT_RMA_PROGRESS
1348  if (rec < 10)
1349  Com_Printf("GAPS: %i rec: %i chances: %i calls: %i\n", GAPS, rec, j, callCnt);
1350 #endif
1351  if (SV_TestFilled(map))
1352  return true; /* this was the last tile we needed */
1353  if (SV_AddMissingTiles_r(map, rec + 1, j, posTileList[rec], mToPlace[ti].tile, px, py))
1354  return true; /* recursive placement succeeded */
1355 
1356  /* tile was a dead end, remove it */
1357  SV_RemoveTile(map, nullptr, nullptr);
1358 
1359  if (h >= g) {
1360 #if 0
1362  SV_RmaPrintMap(map);
1363  Com_Printf("no tile works for gap\n");
1364  }
1365 #endif
1366  return false;
1367  }
1368  }
1369  }
1370  }
1371  }
1372  }
1373  }
1374 
1376  for (i = 0; i < j; i++) {
1377  const int pos = posTileList[rec][i] / TCM;
1378  const int ti = posTileList[rec][i] % TCM;
1379  const int x = pos % mapW;
1380  const int y = pos / mapW;
1381 
1382  SV_AddTile(map, mToPlace[ti].tile, x, y, ti, pos);
1383  if (SV_TestFilled(map))
1384  return true;
1385  if (SV_AddMissingTiles_r(map, rec + 1, j, posTileList[rec], mToPlace[ti].tile, x, y))
1386  return true;
1387  else
1388  SV_RemoveTile(map, nullptr, nullptr);
1389  }
1390  return false;
1391 }
1392 
1400 static bool SV_GapListBuild (MapInfo* map, int tilePosListCnt)
1401 {
1402  const Assembly* mAsm = map->getCurrentAssembly();
1403  const int mapW = mAsm->width;
1404  const mToPlace_t* mToPlace = map->mToPlace;
1405 
1407  int x, y;
1408  for (y = 1; y < mAsm->height + 1; y++) {
1409  for (x = 1; x < mAsm->width + 1; x++)
1410  if (IS_SOLID(map->curMap[y][x]))
1411  gapList[x][y][0] = -1; /* the gap is solid already, so we don't need a counter. But we might need the info. */
1412  else
1413  gapList[x][y][0] = 0; /* the counter for this pos */
1414  }
1415 
1416  /* check how well the tiles can cover the gaps */
1417  for (int i = 0; i < tilePosListCnt; i++) {
1418  const int pos = posTileList[0][i] / TCM;
1419  const int ti = posTileList[0][i] % TCM;
1420  x = pos % mapW;
1421  y = pos / mapW;
1422  Tile* tile = mToPlace[ti].tile;
1423  for (int ty = 0; ty < tile->h; ty++) {
1424  for (int tx = 0; tx < tile->w; tx++) {
1425  if (IS_SOLID(tile->spec[ty][tx])) {
1426  gapList[x + tx][y + ty][0] += 1;
1427  const int cnt = gapList[x + tx][y + ty][0]; /* get the counter */
1428  if (cnt < GAPS + 1)
1429  gapList[x + tx][y + ty][cnt] = posTileList[0][i]; /* remember the tilecode */
1430  }
1431  }
1432  }
1433  }
1434 
1436  for (y = 1; y < mAsm->height + 1; y++) {
1437  for (x = 1; x < mAsm->width + 1; x++) {
1438  if (gapList[x][y][0] == 0)
1439  return false;
1440  }
1441  }
1442  return true;
1443 }
1444 
1455 static bool SV_GapCheckNeighbour (MapInfo* map, int tc1, int mapW, int mapH, int nx, int ny)
1456 {
1457  if (nx < 1)
1458  /* map border */
1459  return false;
1460  if (ny < 1)
1461  return false;
1462  if (nx > mapW)
1463  return false;
1464  if (ny > mapH)
1465  return false;
1466 
1467  if (gapList[nx][ny][0] < 1)
1468  /* no tiles cover this gap, probably map border */
1469  return false;
1470  if (gapList[nx][ny][0] >= GAPS)
1471  /* if there are more tiles than we stored the tc's of,
1472  * we can not evaluate this neighbour. */
1473  return false;
1474 
1475  bool flags1 = SV_GapGetFlagsAtAbsPos(map, tc1, mapW, nx, ny);
1476  if (IS_SOLID(flags1))
1477  /* nx/ny is part of tc1 itself */
1478  return false;
1479 
1481  int h;
1482  for (h = 1; h <= gapList[nx][ny][0]; h++) {
1483  const int tc2 = gapList[nx][ny][h];
1484  const unsigned long flags2 = SV_GapGetFlagsAtAbsPos(map, tc2, mapW, nx, ny);
1485 
1486  if (flags1 & flags2) {
1487  /* found at least one tile that would work */
1488  return false;
1489  }
1490  }
1491  return true;
1492 }
1493 
1500 static int SV_GapListReduce (MapInfo* map)
1501 {
1502  const Assembly* mAsm = map->getCurrentAssembly();
1503  const int mapW = mAsm->width;
1504  const int mapH = mAsm->height;
1505  int x, y;
1506  int n = 0;
1509  for (y = 1; y < mapH + 1; y++) {
1510  for (x = 1; x < mapW + 1; x++) {
1511  if (gapList[x][y][0] < 1) /* solid ? */
1512  continue;
1513 
1514  int g;
1515  for (g = 1; g <= gapList[x][y][0]; g++) {
1516  const int tc1 = gapList[x][y][g];
1517  if (g >= GAPS)
1518  break; /* there are more tiles than we stored the tc's of. */
1519 
1520  /* check the neighbour to the right */
1521  if (SV_GapCheckNeighbour(map, tc1, mapW, mapH, x+1, y)) {
1522  posTileList[1][n] = tc1;
1523  n++;
1524  continue;
1525  }
1526  /* check the neighbour to the left */
1527  if (SV_GapCheckNeighbour(map, tc1, mapW, mapH, x-1, y)) {
1528  posTileList[1][n] = tc1;
1529  n++;
1530  continue;
1531  }
1532  /* check the upper neighbour */
1533  if (SV_GapCheckNeighbour(map, tc1, mapW, mapH, x, y+1)) {
1534  posTileList[1][n] = tc1;
1535  n++;
1536  continue;
1537  }
1538  /* check the neighbour below */
1539  if (SV_GapCheckNeighbour(map, tc1, mapW, mapH, x, y-1)) {
1540  posTileList[1][n] = tc1;
1541  n++;
1542  continue;
1543  }
1544  }
1545  }
1546  }
1547 
1548  return n;
1549 }
1550 
1551 #if PRINT_RMA_PROGRESS
1552  char mapStr[10000] = {0};
1553  char posStr[10000] = {0};
1554 #endif
1555 
1572 static bool SV_AddMissingTiles (MapInfo* map)
1573 {
1574  static int attempts = 0; /* how often this function is called in the RMA process */
1575  const Assembly* mAsm = map->getCurrentAssembly();
1576  const int mapSize = mAsm->size; /* the # of grid squares in the assembly. A grid suare is usually 8x8 cells. */
1577  const int mapW = mAsm->width;
1578  const mToPlace_t* mToPlace = map->mToPlace;
1579  short posList[MAX_RANDOM_MAP_HEIGHT * MAX_RANDOM_MAP_WIDTH]; /* array of random positions */
1580  short tilenumList[MAX_TILETYPES]; /* array of tiles */
1581 
1582  /* shuffle only once, the map will be build with that seed */
1583  RandomList(mapSize, posList);
1584  RandomList(map->numToPlace, tilenumList);
1585  attempts++;
1586 
1587  /* check if the map is already filled */
1588  if (SV_TestFilled(map))
1589  return true;
1590 
1592  int i, j, k, offs, num, n = 0;
1593  for (i = 0; i < mapSize; i++) {
1594  const int x = posList[i] % mapW;
1595  const int y = posList[i] / mapW;
1596 
1597  /* only use positions that are on the grid */
1598  if (x % mAsm->dx != 0 || y % mAsm->dy != 0) {
1599  continue;
1600  }
1601  /* if we simply test the tiles in the same sequence for each pos, we get the 'boring maps' problem */
1602  /* So let's check them from a different starting point in the list each time */
1603  /* Example: if we have say 20 tiles, test eg. 13-20 first, then 1-12 */
1604  num = map->numToPlace;
1605  offs = rand() % num;
1606  for (k = offs; k < num + offs; k++) {
1607  const int ti = tilenumList[k % num];
1608 
1609  if (mToPlace[ti].cnt >= mToPlace[ti].max)
1610  continue;
1611  if (SV_FitTile(map, mToPlace[ti].tile, x, y)) {
1612  posTileList[0][n] = posList[i] * TCM + ti;
1613  assert(n < RMA2_MAX_TILEPOS);
1614  n++;
1615  }
1616  }
1617  }
1618 #if PRINT_RMA_PROGRESS
1619  Com_Printf("\nMapsize: %i tiles: %i chances: %i\n", mapSize, map->numToPlace, n);
1620  mapStr[0] = 0;
1621  posStr[0] = 0;
1622  if (map->numPlaced < 8)
1623  SV_BuildMapStrings(map, mapStr, posStr, true);
1624 #endif
1625 
1626  bool eliminated = true;
1627  while (eliminated) { /* if we could eliminate one or more tiles, try again */
1628  eliminated = false;
1629 #if 0
1630  /* print the posTileList */
1631  for (i = 0; i < n; i++) {
1632  Com_Printf("%2.i/%2.i ", posTileList[0][i] / TCM, posTileList[0][i] % TCM);
1633  if (!(i % 10))
1634  Com_Printf("\n");
1635  }
1636  Com_Printf("\n");
1637 #endif
1638  bool covered = SV_GapListBuild(map, n);
1639 
1640 #if 0
1641  /* print the gapList */
1642  Com_Printf("\n");
1643  for (int x = 0; x < mAsm->width + 1; x++){
1644  for (int y = 0; y < mAsm->height + 1; y++){
1645  int cnt = gapList[x][y][0];
1646  Com_Printf("x:%i y:%i cnt:%i ", x, y, cnt);
1647  for (j = 0; j <= cnt + 3; j++) {
1648  Com_Printf("%i ", gapList[x][y][j]);
1649  }
1650  Com_Printf("\n");
1651  }
1652  }
1653 #endif
1654 
1655  if (!covered)
1656  return false; /* creating the gapList left one gap uncovered */
1657 
1658  int m = SV_GapListReduce(map);
1659  if (m) { /* the number of tilepositions to discard */
1660  eliminated = true;
1661  for (j = 0; j < m; j++) {
1662  const int tc = posTileList[1][j]; /* SV_GapListReduce abuses the space for rec=1 to return it's result */
1663  int offset = 0;
1664  for (i = 0; i < n; i++) {
1665  if (posTileList[0][i] == tc) {
1666  offset = 1;
1667  continue;
1668  }
1669  posTileList[0][i - offset] = posTileList[0][i];
1670  }
1671  if (offset) /* only if we actually removed the tile */
1672  n--; /* reduce the counter of posTileList */
1673  }
1674  }
1675  }
1676 
1677  minMissingSolids = 999;
1678  return SV_AddMissingTiles_r(map, 0, n, posTileList[0], nullptr, 0, 0);
1679 }
1680 
1698 static bool SV_AddMapTiles (MapInfo* map)
1699 {
1700  int idx; /* index in the array of available tiles */
1701  int pos; /* index in the array of random positions */
1702  const Assembly* mAsm = map->getCurrentAssembly();
1703  const int mapW = mAsm->width; /* width in x-direction */
1704  const int mapSize = mAsm->size; /* the # of grid squares in the assembly. A grid suare is usually 8x8 cells. */
1705  const int numToPlace = map->numToPlace;
1706  const mToPlace_t* mToPlace = map->mToPlace; /* pointer to a tile descriptor */
1707  short prList[MAX_RANDOM_MAP_HEIGHT * MAX_RANDOM_MAP_WIDTH]; /* array of random positions */
1708  const int start = map->numPlaced;
1709 #ifdef DEBUG
1710  const mPlaced_t* mPlaced = map->mPlaced;
1711 #endif
1712 
1713 #if PRINT_RMA_PROGRESS
1714  char mapStr[10000] = {0};
1715  char posStr[10000] = {0};
1716 #endif
1717 
1718  /* shuffle only once, the map will be build with that seed */
1719  RandomList(mapSize, prList);
1720 
1721  pos = 0;
1722  idx = 0;
1723  while (idx < numToPlace) { /* for all tile-descriptors */
1724  while (mToPlace[idx].cnt < mToPlace[idx].min) {
1725  for (; pos < mapSize; pos++) {
1726  const int x = prList[pos] % mapW;
1727  const int y = prList[pos] / mapW;
1728  if (sv_threads->integer && sv_rma->integer == 1) {
1729  if (SDL_SemValue(mapSem) != 1) {
1730  /* someone else beat me to it */
1731  return true;
1732  }
1733  }
1734 
1735  if ((x % mAsm->dx != 0) || (y % mAsm->dy != 0))
1736  continue;
1737 
1738  if (SV_FitTile(map, mToPlace[idx].tile, x, y)) {
1739  /* add tile */
1740  SV_AddTile(map, mToPlace[idx].tile, x, y, idx, pos);
1741 #if PRINT_RMA_PROGRESS
1742  mapStr[0] = 0;
1743  posStr[0] = 0;
1744  if (map->numPlaced < 6)
1745  SV_BuildMapStrings(map, mapStr, posStr, true);
1746 #endif
1747  break;
1748  }
1749  }
1750  /* tile fits, try another tile of the same type */
1751  if (pos < mapSize)
1752  continue;
1753 
1754  /* tile doesn't fit and no try left with this tile */
1755  if (!mToPlace[idx].cnt)
1756  break;
1757 
1758  /* tile does not fit, restore last status - replace the last tile */
1759  assert(map->numPlaced > 0);
1760 #ifdef DEBUG
1761  assert(idx == mPlaced[map->numPlaced - 1].idx);
1762 #endif
1764  SV_RmaPrintMap(map);
1765  Com_Printf("required tile doesn't fit\n");
1766  }
1767  SV_RemoveTile(map, &idx, &pos);
1768  pos++;
1769  }
1770 
1771  /* tile fits, try next tile */
1772  if (pos < mapSize) {
1773  pos = 0; /* start at the beginning of the random positions array */
1774  idx++;
1775  } else {
1776  /* no more retries */
1777  if (start == map->numPlaced) {
1778  if (mAsm->numSeeds == 0 || map->retryCnt > 2) {
1779  Com_Error(ERR_DROP, "SV_AddMapTiles: Impossible to assemble map '%s' with assembly '%s'\n",
1780  map->getName(), mAsm->id ? mAsm->id : "");
1781  } else {
1782  Com_Printf("SV_AddMapTiles: Impossible to assemble map '%s' with assembly '%s' - retry with another seed\n",
1783  map->getName(), mAsm->id ? mAsm->id : "");
1784  return false;
1785  }
1786  }
1787  SV_RemoveTile(map, &idx, &pos);
1788  pos++;
1789  }
1790 
1791  if (idx == numToPlace && !SV_AddMissingTiles(map)) {
1793  SV_RmaPrintMap(map);
1794  Com_Printf("couldn't pad\n");
1795  }
1796  SV_RemoveTile(map, &idx, &pos);
1797  pos++;
1798  }
1799  }
1800 
1801  return true;
1802 }
1803 
1809 {
1810  const Assembly* mAsm = map->getCurrentAssembly();
1811 
1812  map->numToPlace = 0;
1813  OBJZERO(map->mToPlace);
1814 
1815  for (int i = 0; i < map->numTiles; i++) {
1816  if (mAsm->max[i]) {
1817  map->mToPlace[map->numToPlace].tile = &map->mTile[i];
1818  map->mToPlace[map->numToPlace].min = mAsm->min[i];
1819  map->mToPlace[map->numToPlace].max = mAsm->max[i];
1820  map->numToPlace++;
1821  }
1822  }
1823 }
1824 
1835 static int SV_AssemblyThread (void* data)
1836 {
1837  MapInfo* map = static_cast<MapInfo*>(data);
1838 
1839  Com_SetRandomSeed(time(nullptr));
1840 
1841  if (!SV_AddMapTiles(map)) {
1842  map->retryCnt++;
1843  }
1844 
1845  /* the first thread to reach this point, gets the semaphore */
1846  if (SDL_SemTryWait(mapSem) != 0)
1847  return -1;
1848  SDL_LockMutex(mapLock);
1849 
1850  assert(threadID == 0);
1851  threadID = SDL_ThreadID();
1852 
1853  /* tell main we're done */
1854  SDL_CondSignal(mapCond);
1855  SDL_UnlockMutex(mapLock);
1856 
1857  return 0;
1858 }
1859 
1884 static int SV_ParallelSearch (MapInfo* map)
1885 {
1886  SDL_Thread* threads[ASSEMBLE_THREADS];
1887  MapInfo* maps[ASSEMBLE_THREADS];
1888  int i;
1889  static int timeout = 5000; /* wait for 5 sec initially, double it every time it times out */
1890  const int threadno = std::min(sv_threads->integer, ASSEMBLE_THREADS);
1891 
1892  assert(mapLock == nullptr);
1893  mapLock = SDL_CreateMutex();
1894 
1895  assert(mapCond == nullptr);
1896  mapCond = SDL_CreateCond();
1897 
1898  threadID = 0;
1899  assert(mapSem == nullptr);
1900  mapSem = SDL_CreateSemaphore(1);
1901 
1902  SDL_LockMutex(mapLock);
1903  for (i = 0; i < threadno; i++) {
1904  maps[i] = Mem_AllocType(MapInfo);
1905  memcpy(maps[i], map, sizeof(*map));
1906  threads[i] = Com_CreateThread(SV_AssemblyThread, "AssemblyThread", (void*) maps[i]);
1907  }
1908  while (threadID == 0) {
1909  /* if nobody is done after 5 sec, restart, double the timeout. */
1910  if (SDL_CondWaitTimeout(mapCond, mapLock, timeout) != 0) {
1911  Com_Printf("SV_ParallelSearch: timeout at %i ms, restarting\n", timeout);
1912  timeout += timeout;
1913  /* tell them all to die */
1914  if (SDL_SemTryWait(mapSem) != 0) {
1915  /* couldn't tell everyone to die, someone must have finished since the last line... */
1916  continue;
1917  }
1918  /* collect the dead */
1919  for (i = 0; i < threadno; i++) {
1920  SDL_WaitThread(threads[i], nullptr);
1921  }
1922  /* reset semaphore */
1923  SDL_SemPost(mapSem);
1924  /* start'em again */
1925  for (i = 0; i < threadno; i++) {
1926  memcpy(maps[i], map, sizeof(*map));
1927  threads[i] = Com_CreateThread(SV_AssemblyThread, "AssemblyThread", (void*) maps[i]);
1928  }
1929  } else {
1930  /* someone finished */
1931  assert(threadID != 0);
1932  }
1933  }
1934  SDL_UnlockMutex(mapLock);
1935  for (i = 0; i < threadno; i++) {
1936  if (SDL_GetThreadID(threads[i]) == threadID) {
1937  memcpy(map, maps[i], sizeof(*map));
1938  }
1939 
1940  SDL_WaitThread(threads[i], nullptr);
1941  Mem_Free(maps[i]);
1942  }
1943 
1944  /* cleanup, for possible next time */
1945  SDL_DestroySemaphore(mapSem);
1946  SDL_DestroyCond(mapCond);
1947  SDL_DestroyMutex(mapLock);
1948  mapLock = nullptr;
1949  mapSem = nullptr;
1950  mapCond = nullptr;
1951  threadID = 0;
1952  timeout = 5000;
1953 
1954  return 0;
1955 }
1956 
1965 static void SV_ParseUMP (const char* name, char* entityString, MapInfo* map, bool inherit)
1966 {
1967  char filename[MAX_QPATH];
1968  byte* buf;
1969  const char* text, *token;
1970 
1971  /* load the map info */
1972  Com_sprintf(filename, sizeof(filename), "maps/%s.ump", name);
1974  if (!buf)
1975  Com_Error(ERR_DROP, "SV_ParseUMP: Map assembly info '%s' not found", filename);
1976 
1977  /* parse it */
1978  text = (const char*)buf;
1979  do {
1980  token = Com_Parse(&text);
1981  if (!text)
1982  break;
1983 
1984  if (Q_streq(token, "extends")) {
1985  token = Com_Parse(&text);
1986  if (inherit)
1987  Com_Printf("SV_ParseUMP: Nested extends in %s 'extends %s' ignored\n", filename, token);
1988  else
1989  SV_ParseUMP(token, entityString, map, true);
1990  } else if (Q_streq(token, "base")) {
1991  token = Com_Parse(&text);
1992  if (inherit)
1993  Q_strncpyz(map->inheritBasePath, token, sizeof(map->inheritBasePath));
1994  else
1995  Q_strncpyz(map->basePath, token, sizeof(map->basePath));
1996  } else if (Q_streq(token, "line")) {
1997  token = Com_Parse(&text);
1998  const char* p = token;
1999  map->lineFlags = 0;
2000  while (*p) {
2001  map->lineFlags |= tileMask(*p);
2002  p++;
2003  }
2004  } else if (Q_streq(token, "tileset")) {
2005  if (map->numTileSets >= MAX_TILESETS)
2006  Com_Printf("SV_ParseUMP: Too many map tileset found in (%s)\n", filename);
2007  else if (SV_ParseMapTileSet(filename, &text, map, inherit))
2008  map->numTileSets++;
2009  } else if (Q_streq(token, "worldspawn")) {
2010  const char* start = nullptr;
2011  const int length = Com_GetBlock(&text, &start);
2012  if (length == -1) {
2013  Com_Printf("SV_ParseUMP: Not a valid worldspawn block in '%s'\n", filename);
2014  } else {
2015  const int worldSpawnLength = SV_GetConfigStringLength(CS_ENTITYSTRING);
2016  if (length >= worldSpawnLength)
2017  Com_Printf("SV_ParseUMP: worldspawn is too big - only %i characters are allowed", worldSpawnLength);
2018  else
2019  Q_strncpyz(entityString, start, length);
2020  }
2021  } else if (Q_streq(token, "tile")) {
2022  if (map->numTiles >= MAX_TILETYPES)
2023  Com_Printf("SV_ParseUMP: Too many map tile types (%s)\n", filename);
2024  else if (SV_ParseMapTile(filename, &text, map, inherit))
2025  map->numTiles++;
2026  } else if (Q_streq(token, "assembly")) {
2027  if (inherit) {
2028  Com_SkipBlock(&text);
2029  } else {
2030  if (map->numAssemblies >= MAX_MAPASSEMBLIES)
2031  Com_Printf("SV_ParseUMP: Too many map assemblies (%s)\n", filename);
2032  else if (SV_ParseAssembly(map, filename, &text, &map->assemblies[map->numAssemblies]))
2033  map->numAssemblies++;
2034  }
2035  } else if (token[0] == '{') {
2036  Com_Printf("SV_ParseUMP: Skipping unknown block\n");
2037  /* ignore unknown block */
2038  text = strchr(text, '}') + 1;
2039  if (!text)
2040  break;
2041  } else
2042  Com_Printf("SV_ParseUMP: Unknown token '%s' (%s)\n", token, filename);
2043  } while (text);
2044 
2045  /* free the file */
2046  FS_FreeFile(buf);
2047 }
2048 
2049 #if SORT_BY_SIZE
2050 static int cmpTileAreaSize (const void* a, const void* b)
2051 {
2052  if (((const mToPlace_t*) a)->tile->area > ((const mToPlace_t*) b)->tile->area)
2053  return -1;
2054  else if (((const mToPlace_t*) a)->tile->area == ((const mToPlace_t*) b)->tile->area)
2055  return 0;
2056  return 1;
2057 }
2058 #endif
2059 
2060 static MapInfo* SV_DoMapAssemble (MapInfo* map, const char* assembly, char* asmTiles, char* asmPos, const unsigned int seed, bool print)
2061 {
2062  const Assembly* mAsm = map->getCurrentAssembly();
2063 
2064  Com_DPrintf(DEBUG_SERVER, "Use assembly: '%s'\n", mAsm->id);
2065 
2066  /* check size */
2067  assert(mAsm->width <= MAX_RANDOM_MAP_WIDTH);
2068  assert(mAsm->height <= MAX_RANDOM_MAP_HEIGHT);
2069 
2071 
2072 #if SORT_BY_SIZE
2073  /* This is the perfect time to sort them by size, which helps RMA a lot.
2074  * This eliminates the need for a seedlist in oriental large completely.
2075  * Unfortunately, it doesn't do that for the others. Instead, it can slow down some maps quite a bit. */
2076  qsort(map->mToPlace, map->numToPlace, sizeof(mToPlace_t), cmpTileAreaSize);
2077 #endif
2078 
2079  /* assemble the map */
2080  map->numPlaced = 0;
2081  SV_ClearMap(map);
2082 
2083  /* place fixed parts - defined in ump via fix parameter */
2084  for (int i = 0; i < mAsm->numFixed; i++)
2085  SV_AddTile(map, &map->mTile[mAsm->fT[i]], mAsm->fX[i], mAsm->fY[i], -1, -1);
2086 
2087  if (sv_threads->integer && sv_rma->integer == 1) {
2088  const int oldCount = map->retryCnt;
2089  if (SV_ParallelSearch(map) < 0) {
2090  if (oldCount < map->retryCnt && mAsm->numSeeds > 0) {
2091  /* if we are allowed to restart the search with a fixed seed
2092  * from the assembly definition, do so */
2093  Com_SetRandomSeed(mAsm->seeds[rand() % mAsm->numSeeds]);
2094  return SV_DoMapAssemble(map, assembly, asmTiles, asmPos, seed, print);
2095  }
2096  Mem_Free(map);
2097  return nullptr;
2098  }
2099  } else {
2100  unsigned int seedUsed;
2101 
2102  if (mAsm->numSeeds > 0) {
2103  /* if the map has a seedlist defined, use that */
2104  seedUsed = mAsm->seeds[rand() % mAsm->numSeeds];
2105  if (seed) {
2106  /* if a seed was passed, we are in cunit test mode */
2107  if (seed >= mAsm->numSeeds)
2108  /* if the given seed is outside the seedlist, assume that we already tested it and pretend that it's ok */
2109  return map;
2110  /* use the passed seed as an index into the seedlist */
2111  seedUsed = mAsm->seeds[seed % mAsm->numSeeds];
2112  }
2113  Com_Printf("Picked seed: %i for <%s>\n", seedUsed, assembly);
2114  } else {
2115  /* no seedlist */
2116  if (seed)
2117  seedUsed = seed;
2118  else
2119  seedUsed = rand() % 50; /* limit the possible seeds to (testable) values between 0 and 49 */
2120  }
2121  Com_SetRandomSeed(seedUsed);
2122 
2123  if (!SV_AddMapTiles(map)) {
2124  map->retryCnt++;
2125  if (mAsm->numSeeds > 0) {
2126  /* if we are allowed to restart the search with a fixed seed
2127  * from the assembly definition, do so */
2128  Com_SetRandomSeed(mAsm->seeds[seed % mAsm->numSeeds]);
2129  return SV_DoMapAssemble(map, assembly, asmTiles, asmPos, seed, print);
2130  }
2131  return nullptr;
2132  }
2133  }
2134 
2135  /* prepare map and pos strings */
2136  if (map->basePath[0])
2137  Com_sprintf(asmTiles, sizeof(map->basePath) + 1, "-%s", map->basePath);
2138 
2139  asmPos[0] = 0;
2140 
2141  /* generate the strings */
2142  SV_BuildMapStrings(map, asmTiles, asmPos, print);
2143 
2144  return map;
2145 }
2146 
2167 static MapInfo* SV_AssembleMap_ (const char* mapTheme, const char* assembly, char* asmTiles, char* asmPos, char* entityString, const unsigned int seed, bool print)
2168 {
2169  MapInfo* map;
2170 
2171  map = Mem_AllocType(MapInfo);
2172  map->setName(mapTheme);
2173 
2174  SV_ParseUMP(mapTheme, entityString, map, false);
2175 
2176  /* check for parsed tiles and assemblies */
2177  if (!map->numTiles)
2178  Com_Error(ERR_DROP, "No map tiles defined (%s)!", map->getName());
2179 #ifdef DEBUG
2180  else
2181  Com_DPrintf(DEBUG_SERVER, "numTiles: %i\n", map->numTiles);
2182 #endif
2183 
2184  if (!map->numAssemblies)
2185  Com_Error(ERR_DROP, "No map assemblies defined (%s)!", map->getName());
2186 #ifdef DEBUG
2187  else
2188  Com_DPrintf(DEBUG_SERVER, "numAssemblies: %i\n", map->numAssemblies);
2189 #endif
2190 
2191  /* use random assembly, if no valid one has been specified */
2192  map->asmIdx = rand() % map->numAssemblies;
2193 
2194  /* overwrite with specified, if any */
2195  if (Q_strvalid(assembly)) {
2196  int i;
2197  for (i = 0; i < map->numAssemblies; i++)
2198  if (Q_streq(assembly, map->assemblies[i].id)) {
2199  map->asmIdx = i;
2200  break;
2201  }
2202  if (i == map->numAssemblies) {
2203  Com_Printf("SV_AssembleMap: Map assembly '%s' not found\n", assembly);
2204  }
2205  }
2206 
2207  SV_DoMapAssemble(map, assembly, asmTiles, asmPos, seed, print);
2208 
2209  return map;
2210 }
2211 
2212 int SV_AssembleMap (const char* mapTheme, const char* assembly, char* asmTiles, char* asmPos, char* entityString, const unsigned int seed, bool print)
2213 {
2214  MapInfo* map = SV_AssembleMap_ (mapTheme, assembly, asmTiles, asmPos, entityString, seed, print);
2215  int num = map->numPlaced;
2216  Mem_Free(map);
2217  return num;
2218 }
2219 
2220 int SV_AssembleMapAndTitle (const char* mapTheme, const char* assembly, char* asmTiles, char* asmPos, char* entityString, const unsigned int seed, bool print, char* asmTitle)
2221 {
2222  MapInfo* map = SV_AssembleMap_ (mapTheme, assembly, asmTiles, asmPos, entityString, seed, print);
2223  int num = map->numPlaced;
2224  strcpy(asmTitle, map->getCurrentAssemblyTitle());
2225  Mem_Free(map);
2226  return num;
2227 }
2228 
2229 void SV_PrintAssemblyStats (const char* mapTheme, const char* asmName)
2230 {
2231  MapInfo* theMap = Mem_AllocType(MapInfo);
2232  char mapAsmName[80];
2233  const char* p = mapTheme;
2234 
2235  if (*p == '+')
2236  p++;
2237  else
2238  return;
2239 
2240  SV_ParseUMP(p, nullptr, theMap, false);
2241  theMap->asmIdx = 0;
2242  /* overwrite with specified, if any */
2243  if (asmName && asmName[0]) {
2244  int j;
2245  for (j = 0; j < theMap->numAssemblies; j++)
2246  if (Q_streq(asmName, theMap->assemblies[j].id)) {
2247  theMap->asmIdx = j;
2248  break;
2249  }
2250  if (j == theMap->numAssemblies) {
2251  Com_Printf("testMapDefStatistic: Map assembly '%s' not found\n", asmName);
2252  }
2253  }
2254 
2255  SV_PrepareTilesToPlace(theMap);
2256  const Assembly* assembly = theMap->getCurrentAssembly();
2257 
2258  int required = 0;
2259  int solids = 0;
2260  for (int k = 0; k < theMap->numToPlace; k++) {
2261  required += theMap->mToPlace[k].min;
2262  solids += theMap->mToPlace[k].max * theMap->mToPlace[k].tile->area;
2263  }
2264 
2265  Com_sprintf(mapAsmName, sizeof(mapAsmName), "%s %s", p, asmName);
2266  Com_Printf("%22.22s %2.i %2.i %2.i %2.i %3.i %3.i \n", mapAsmName, theMap->numTiles, theMap->numToPlace, required, assembly->numSeeds, assembly->size, solids);
2267 }
static unsigned long SV_GapGetFlagsAtAbsPos(MapInfo *map, int tileCode, int mapW, int mapX, int mapY)
get the specs of a tile at map-x/y if it was placed where tileCode indicates
Definition: sv_rma.cpp:1161
static bool SV_AddMissingTiles_r(MapInfo *map, int rec, int posListCnt, short myPosList[], const Tile *prevTile, int prevX, int prevY)
Definition: sv_rma.cpp:1186
byte fX[MAX_FIXEDTILES]
Definition: sv_rma.cpp:116
static int availableTiles[MAX_TILETYPES][2]
Select the next tile to place and place it (recursively)
Definition: sv_rma.cpp:1184
#define GAPS
the # of different tiles we can store for a gap
Definition: sv_rma.cpp:64
char * name
Definition: cvar.h:72
int area
Definition: sv_rma.cpp:91
#define MMW
Definition: sv_rma.cpp:292
static unsigned long tileMask(const char chr)
Convert to tile spec - normalize the characters.
Definition: sv_rma.cpp:245
static bool SV_ParseAssembly(MapInfo *map, const char *filename, const char **text, Assembly *a)
Parses an assembly block.
Definition: sv_rma.cpp:727
static void SV_GetTilesFromTileSet(const MapInfo *map, const char *filename, const char **text, Assembly *a)
Definition: sv_rma.cpp:672
char id[MAX_VAR]
Definition: sv_rma.cpp:88
static void SV_CombineAlternatives(unsigned long *mapAlts, const unsigned long tileAlts)
Combines the alternatives/connection info of a map with a tile and sets the rating.
Definition: sv_rma.cpp:892
mPlaced_t mPlaced[MAX_MAPTILES]
Definition: sv_rma.cpp:167
#define ALL_TILES
Definition: sv_rma.cpp:218
static void SV_AddTile(MapInfo *map, const Tile *tile, int x, int y, int idx, int pos)
Adds a new map-tile to an assembled map. Also adds the tile to the placed-tiles list.
Definition: sv_rma.cpp:1028
static SDL_mutex * mapLock
Definition: sv_rma.cpp:73
Stores the parsed data of an assembly definition. See *.ump files.
Definition: sv_rma.cpp:108
#define DEBUG_SERVER
Definition: defines.h:60
int asmIdx
Definition: sv_rma.cpp:180
int h
Definition: sv_rma.cpp:90
cvar_t * sv_maxclients
Definition: g_main.cpp:43
char id[MAX_VAR]
Definition: sv_rma.cpp:111
void Com_SetRandomSeed(unsigned int seed)
Definition: common.cpp:999
static bool SV_TestFilled(const MapInfo *map)
Checks if the map is completely filled.
Definition: sv_rma.cpp:976
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition: shared.cpp:494
static SDL_cond * mapCond
Definition: sv_rma.cpp:72
int numSeeds
Definition: sv_rma.cpp:130
#define MAX_TILESTRINGS
Definition: q_shared.h:298
int w
Definition: sv_rma.cpp:90
const char * filename
Definition: ioapi.h:41
static bool SV_ParseMapTile(const char *filename, const char **text, MapInfo *map, bool inherit)
Parsed a tile definition out of the ump-files.
Definition: sv_rma.cpp:488
static int cmpTileAreaSize(const void *a, const void *b)
Definition: sv_rma.cpp:2050
#define OBJSET(obj, val)
Definition: shared.h:177
cvar_t * sv_rma
Definition: sv_main.cpp:49
int integer
Definition: cvar.h:81
voidpf void uLong size
Definition: ioapi.h:42
static MapInfo * SV_DoMapAssemble(MapInfo *map, const char *assembly, char *asmTiles, char *asmPos, const unsigned int seed, bool print)
Definition: sv_rma.cpp:2060
static const char * SV_GetCvarToken(const MapInfo *map, const Assembly *a, const char *token, const char *filename, const char **text, const char *errhead)
Tries to extract a tile name from a cvar - the cvar value must start with a &#39;+&#39;.
Definition: sv_rma.cpp:572
QGL_EXTERN GLsizei const GLvoid * data
Definition: r_gl.h:89
char basePath[MAX_QPATH]
Definition: sv_rma.cpp:177
int FS_LoadFile(const char *path, byte **buffer)
Filenames are relative to the quake search path.
Definition: files.cpp:384
static short gapList[MAX_RANDOM_MAP_HEIGHT][MAX_RANDOM_MAP_HEIGHT][GAPS+1]
for every x/y we can store the tiles that can cover that place here
Definition: sv_rma.cpp:68
void Com_Printf(const char *const fmt,...)
Definition: common.cpp:386
SDL_Thread * Com_CreateThread(int(*fn)(void *), const char *name, void *data=nullptr)
Definition: thread.h:5
const char * getName() const
Definition: sv_rma.cpp:190
#define TCM
tile code multiplier. For the various debug printfs we want a number that we can easily divide throug...
Definition: sv_rma.cpp:59
static int SV_ParallelSearch(MapInfo *map)
Spawn ASSEMBLE_THREADS threads to try and assemble a map. The first map complete gets returned...
Definition: sv_rma.cpp:1884
#define MAX_FIXEDTILES
Definition: sv_rma.cpp:83
int numTiles
Definition: sv_rma.cpp:99
#define ACH
Definition: sv_rma.cpp:291
voidpf void * buf
Definition: ioapi.h:42
static void SV_PrepareTilesToPlace(MapInfo *map)
Prepare the list of tiles to place.
Definition: sv_rma.cpp:1808
static short posTileList[RMA2_MAX_REC][RMA2_MAX_TILEPOS]
array of working random tile positions, 50 recursions
Definition: sv_rma.cpp:61
int numAssemblies
Definition: sv_rma.cpp:165
#define Q_strvalid(string)
Definition: shared.h:141
void Com_Error(int code, const char *fmt,...)
Definition: common.cpp:417
static MapInfo * SV_AssembleMap_(const char *mapTheme, const char *assembly, char *asmTiles, char *asmPos, char *entityString, const unsigned int seed, bool print)
Assembles a "random" map and parses the *.ump files for assembling the "random" maps and places the &#39;...
Definition: sv_rma.cpp:2167
static bool SV_AddMapTiles(MapInfo *map)
Tries to build the map There are 3 categories of tiles:
Definition: sv_rma.cpp:1698
byte fY[MAX_FIXEDTILES]
Definition: sv_rma.cpp:117
static const Tile * SV_GetMapTile(const MapInfo *map, const char *tileName)
Definition: sv_rma.cpp:414
unsigned long spec[MAX_TILESIZE][MAX_TILESIZE]
Definition: sv_rma.cpp:89
Defines a tile to place.
Definition: sv_rma.cpp:137
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition: shared.cpp:457
#define IS_SOLID(x)
Definition: sv_rma.cpp:219
static void SV_RemoveTile(MapInfo *map, int *idx, int *pos)
Rebuilds a assembled map up to the previous tile.
Definition: sv_rma.cpp:1073
static void RandomList(const int n, short *list)
Fills a list with random values between 0 and n.
Definition: sv_rma.cpp:203
#define ERR_DROP
Definition: common.h:211
cvar_t * sv_threads
Definition: sv_main.cpp:48
#define MAX_RANDOM_MAP_HEIGHT
Definition: sv_rma.cpp:66
static void SV_RmaPrintMap(const MapInfo *map)
Definition: sv_rma.cpp:294
#define OBJZERO(obj)
Definition: shared.h:178
int retryCnt
Definition: sv_rma.cpp:182
#define MAX_VAR
Definition: shared.h:36
const char * getCurrentAssemblyTitle() const
Definition: sv_rma.cpp:193
QGL_EXTERN GLuint GLsizei GLsizei * length
Definition: r_gl.h:110
#define Q_strncasecmp(s1, s2, n)
Definition: shared.h:132
int width
Definition: sv_rma.cpp:119
Defines a placed tile.
Definition: sv_rma.cpp:147
static const TileSet * SV_GetMapTileSet(const MapInfo *map, const char *tileSetName)
Definition: sv_rma.cpp:403
#define RMA2_MAX_TILEPOS
max # of valid tile/position combinations
Definition: sv_rma.cpp:57
static bool SV_ParseAssemblySeeds(MapInfo *map, const char *filename, const char **text, Assembly *a)
Parses a list of working seeds to assemble this rma assembly.
Definition: sv_rma.cpp:644
Main server include file.
#define MAX_TILESIZE
Definition: sv_rma.cpp:82
TileSet tileSets[MAX_TILESETS]
Definition: sv_rma.cpp:170
int seeds[MAX_ASSEMBLY_SEEDS]
Definition: sv_rma.cpp:128
static int SV_GapListReduce(MapInfo *map)
Tries to find tiles that exclude all of their neighbours This is called only once, before recursion starts. So we can safely (ab)use the posTileList space for recursion 1.
Definition: sv_rma.cpp:1500
static void SV_ParseUMP(const char *name, char *entityString, MapInfo *map, bool inherit)
Parses an ump file that contains the random map definition.
Definition: sv_rma.cpp:1965
const char * Com_EParse(const char **text, const char *errhead, const char *errinfo, char *target, size_t size)
Parsing function that prints an error message when there is no text in the buffer.
Definition: scripts.cpp:277
char id[MAX_VAR]
Definition: sv_rma.cpp:97
byte min[MAX_TILETYPES]
Definition: sv_rma.cpp:113
#define ASSEMBLE_THREADS
Definition: sv_rma.cpp:37
byte max[MAX_TILETYPES]
Definition: sv_rma.cpp:114
#define MAX_TOKEN_CHARS
Definition: defines.h:372
static void SV_TileMaskToString(unsigned long m, char *str)
Definition: sv_rma.cpp:261
char title[MAX_VAR]
Definition: sv_rma.cpp:112
void Com_DPrintf(int level, const char *fmt,...)
A Com_Printf that only shows up if the "developer" cvar is set.
Definition: common.cpp:398
static Uint32 threadID
Definition: sv_rma.cpp:74
QGL_EXTERN GLuint index
Definition: r_gl.h:110
int Com_GetBlock(const char **text, const char **start)
Get the start and end point of a block in the given text.
Definition: parse.cpp:272
int numTileSets
Definition: sv_rma.cpp:171
unsigned long lineFlags
Definition: sv_rma.cpp:175
static SDL_sem * mapSem
Definition: sv_rma.cpp:71
Stores the parsed data for a map tile. (See *.ump files)
Definition: sv_rma.cpp:86
#define MAX_TILESETTILES
Definition: sv_rma.cpp:81
static bool SV_ParseMapTileSet(const char *filename, const char **text, MapInfo *map, bool inherit)
Parsed a tileset definition out of the ump-files.
Definition: sv_rma.cpp:429
static int minMissingSolids
Definition: sv_rma.cpp:70
int numTiles
Definition: sv_rma.cpp:174
int numFixed
Definition: sv_rma.cpp:118
Tile * tile
Definition: sv_rma.cpp:138
static void SV_BuildMapStrings(const MapInfo *map, char *asmTiles, char *asmPos, bool print)
Creates the mapstrings as known from the ufoconsole.log and optionally prints them. This can also be used to dump the progress of the RMA process.
Definition: sv_rma.cpp:1126
const Tile * tile
Definition: sv_rma.cpp:148
Tile mTile[MAX_TILETYPES]
Definition: sv_rma.cpp:173
int numPlaced
Definition: sv_rma.cpp:168
int dy
Definition: sv_rma.cpp:124
const char * Com_Parse(const char *data_p[], char *target, size_t size, bool replaceWhitespaces)
Parse a token out of a string.
Definition: parse.cpp:107
static int SV_AssemblyThread(void *data)
The main function for the threads that try to create random map assemblies in parallel.
Definition: sv_rma.cpp:1835
char inheritBasePath[MAX_QPATH]
Definition: sv_rma.cpp:178
int SV_AssembleMapAndTitle(const char *mapTheme, const char *assembly, char *asmTiles, char *asmPos, char *entityString, const unsigned int seed, bool print, char *asmTitle)
Definition: sv_rma.cpp:2220
#define MAX_QPATH
Definition: filesys.h:40
char name[MAX_VAR]
Definition: sv_rma.cpp:155
QGL_EXTERN GLint i
Definition: r_gl.h:113
int dx
Definition: sv_rma.cpp:124
const Assembly * getCurrentAssembly() const
Definition: sv_rma.cpp:184
#define MAX_TILESETS
Definition: sv_rma.cpp:80
int SV_AssembleMap(const char *mapTheme, const char *assembly, char *asmTiles, char *asmPos, char *entityString, const unsigned int seed, bool print)
Definition: sv_rma.cpp:2212
cvar_t * sv_rmadisplaythemap
display a character graphic of the tiles placed when RMA2 reaches a dead end.
Definition: sv_main.cpp:50
char mapStr[MAX_TOKEN_CHARS *MAX_TILESTRINGS]
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition: r_gl.h:110
int pos
Definition: sv_rma.cpp:150
#define MAX_TILETYPES
Definition: sv_rma.cpp:79
#define Mem_Free(ptr)
Definition: mem.h:35
void Q_strcat(char *dest, size_t destsize, const char *format,...)
Safely (without overflowing the destination buffer) concatenates two strings.
Definition: shared.cpp:475
mToPlace_t mToPlace[MAX_TILETYPES]
Stores the Tiles to Place in the map.
Definition: sv_rma.cpp:161
static void SV_DumpPlaced(const MapInfo *map, int pl)
Debug function to dump the map location of a placed tile.
Definition: sv_rma.cpp:992
void setName(const char *mapTheme)
Definition: sv_rma.cpp:187
#define MAX_RANDOM_MAP_WIDTH
Definition: sv_rma.cpp:65
int idx
Definition: sv_rma.cpp:150
static bool SV_FitTile(const MapInfo *map, const Tile *tile, const int x, const int y)
Checks if a given map-tile fits into the empty space (in a given location) of a map.
Definition: sv_rma.cpp:926
#define Mem_AllocType(type)
Definition: mem.h:39
#define lengthof(x)
Definition: shared.h:105
unsigned long curMap[MAX_RANDOM_MAP_HEIGHT][MAX_RANDOM_MAP_WIDTH]
Stores the alternatives information for the assembled map.
Definition: sv_rma.cpp:158
cvar_t * Cvar_Set(const char *varName, const char *value,...)
Sets a cvar value.
Definition: cvar.cpp:615
#define ACW
Definition: sv_rma.cpp:290
int size
Definition: sv_rma.cpp:123
#define Q_streq(a, b)
Definition: shared.h:136
#define MMH
Definition: sv_rma.cpp:293
voidpf uLong offset
Definition: ioapi.h:45
char tiles[MAX_TILESETTILES][MAX_VAR]
Definition: sv_rma.cpp:98
#define MAX_ASSEMBLY_SEEDS
Definition: sv_rma.cpp:102
byte fT[MAX_FIXEDTILES]
Definition: sv_rma.cpp:115
int height
Definition: sv_rma.cpp:119
#define RMA2_MAX_REC
max # of recursions
Definition: sv_rma.cpp:44
#define CS_ENTITYSTRING
Definition: q_shared.h:324
uint8_t byte
Definition: ufotypes.h:34
A list of tiles with the same size and neighbouring requirements to randomly pick from...
Definition: sv_rma.cpp:95
int SV_GetConfigStringLength(int index)
Definition: sv_main.cpp:85
#define MAX_MAPASSEMBLIES
Definition: sv_rma.cpp:76
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
Definition: cvar.h:71
cvar_t * Cvar_FindVar(const char *varName)
Searches for a cvar given by parameter.
Definition: cvar.cpp:106
cvar_t * sv_dumpmapassembly
Definition: sv_main.cpp:47
Assembly assemblies[MAX_MAPASSEMBLIES]
Definition: sv_rma.cpp:164
static struct mdfour * m
Definition: md4.cpp:35
static void SV_ClearMap(MapInfo *map)
Reset the map to empty state.
Definition: sv_rma.cpp:910
void SV_PrintAssemblyStats(const char *mapTheme, const char *asmName)
Definition: sv_rma.cpp:2229
static bool SV_GapListBuild(MapInfo *map, int tilePosListCnt)
Builds a list of map positions (gaps) and the tiles that can cover them.
Definition: sv_rma.cpp:1400
static bool SV_AddMissingTiles(MapInfo *map)
Tries to fill the missing tiles of the current map. While the 2010 algo used a &#39;by chance&#39;-algo...
Definition: sv_rma.cpp:1572
static bool SV_GapCheckNeighbour(MapInfo *map, int tc1, int mapW, int mapH, int nx, int ny)
Find a tile that meets the requirements of tc1 at a given pos.
Definition: sv_rma.cpp:1455
static const char * SV_GetTileFromTileSet(const MapInfo *map, const char *filename, const char **text, const Assembly *a)
Definition: sv_rma.cpp:616
int numToPlace
Definition: sv_rma.cpp:162
void Com_SkipBlock(const char **text)
Skips a block of {} in our script files.
Definition: parse.cpp:253
void FS_FreeFile(void *buffer)
Definition: files.cpp:411
char posStr[MAX_TOKEN_CHARS *MAX_TILESTRINGS]
char * string
Definition: cvar.h:73