/* $Id: map.c,v 1.26 2004/04/03 14:48:43 stpohle Exp $ */ /* map handling, like generate and load maps. */ #include "bomberclone.h" #include "player.h" _map map; // put the special items into the field void map_fillitems (int fieldtype, int num) { int nb_try = 100, d, x, y; /* this is the item factor we multiply it with this so we know how much items we want in the game */ float fkt = ((float) (map.size.x * map.size.y)) / (25.0 * 17.0); for (d = 0; d < num * fkt; d++) { x = y = 0; while (map.field[x][y].type != FT_stone && map.field[x][y].special != FT_nothing) { x = ((float) rand () / (float) RAND_MAX) * (map.size.x - 1); y = ((float) rand () / (float) RAND_MAX) * (map.size.y - 1); nb_try--; if (nb_try < 0) break; } if (map.field[x][y].type != FT_tunnel) map.field[x][y].special = fieldtype; } } // loads or generate an map void map_new (char *filename) { int x, y; FILE *fmap; signed char old_maptype = map.type; int pl_cnt, pl; /* initialize the start_point array in the _map struct */ map_init_start_points(); if (filename) { fmap = fopen (filename, "r"); /* if we can't open the given filename for any reason, reverting to default value else, load the file map */ if (fmap) map_load (fmap); } else fmap = NULL; // Clean and create the field // if (fmap == NULL) map_genrandom (); if (map.type == -1) map.type = s_random (MAPT_max); if (map.type == MAPT_tunnel) { /* insert tunnels */ for (x = 0; x < GAME_MAX_TUNNELS; x++) map.tunnel[x].x = map.tunnel[x].y = -1; map.field[3][3].type = FT_tunnel; map.field[3][3].special = 0; map.field[map.size.x - 4][map.size.y - 4].type = FT_tunnel; map.field[map.size.x - 4][map.size.y - 4].special = 1; if (map.size.y > 12) { map.field[map.size.x - 4][3].type = FT_tunnel; map.field[map.size.x - 4][3].special = 2; map.field[3][map.size.y - 4].type = FT_tunnel; map.field[3][map.size.y - 4].special = 3; map.tunnel[0].x = map.size.x - 4; map.tunnel[0].y = 3; map.tunnel[1].x = 3; map.tunnel[1].y = map.size.y - 4; map.tunnel[2].x = map.size.x - 4; map.tunnel[2].y = map.size.y - 4; map.tunnel[3].x = 3; map.tunnel[3].y = 3; } else { map.tunnel[0].x = map.size.x - 4; map.tunnel[0].y = map.size.y - 4; map.tunnel[1].x = 3; map.tunnel[1].y = 3; } } /* delete the bfield data */ for (x = 0; x < MAX_FIELDSIZE_X; x++) for (y = 0; y < MAX_FIELDSIZE_Y; y++) map.bfield[x][y] = 0; /* count the number of players on this map so we know how many starting points * to find */ pl_cnt = 0; for (pl = 0; pl < MAX_PLAYERS; pl++) { if (PS_IS_used (players[pl].state)) { pl_cnt++; } } /* identify possible starting positions for players and store them in the * start_point array in the _map struct. This will always succeed. If * it cannot find starting points within the tolerance, it first attempts * to create start points within the tolerence and otherwise lowers the * tolerence until it can satisfy the proper number of start points. * eventually the tolerence reaches 0, so it can, in the worst case, start * all players at the same start point. */ map_find_and_add_start_points(pl_cnt - map_num_defined_start_points(), MAP_POSITION_TOLERENCE); /* Set the Playerinformation */ map_set_playerposition (fmap != NULL); /* put the fire powerups in the field */ map_fillitems (FT_fire, map.fire); /* put the bomb powerups in the field */ map_fillitems (FT_bomb, map.bombs); /* put the shoe powerup in the field */ map_fillitems (FT_shoe, map.shoes); /* put the death ?powerups? in the field */ map_fillitems (FT_death, map.death); /* put the mixed powerrup in the field */ map_fillitems (FT_mixed, map.mixed); /* put the trigger special in the field */ map_fillitems (FT_sp_trigger, map.sp_trigger); /* put the row special in the field */ map_fillitems (FT_sp_row, map.sp_row); /* put the push special in the field */ map_fillitems (FT_sp_push, map.sp_push); map_fillitems (FT_sp_liquid, map.sp_push); map_fillitems (FT_sp_moved, map.sp_push); /* put the push special in the field */ map_fillitems(FT_sp_kick,map.sp_kick); map.type = old_maptype; } void map_genrandom () { int x, y, d; /* if we can't load the map check first the fieldsize settings */ if (map.size.x < MIN_FIELDSIZE_X) map.size.x = MIN_FIELDSIZE_X; if (map.size.x > MAX_FIELDSIZE_X) map.size.x = MAX_FIELDSIZE_X; for (x = 0; x < map.size.x; x++) for (y = 0; y < map.size.y; y++) { if ((y == 0) || (y == map.size.y - 1)) map.field[x][y].type = FT_block; else if ((x == 0) || (x == map.size.x - 1)) map.field[x][y].type = FT_block; else if (((x & 1) == 0) && ((y & 1) == 0)) map.field[x][y].type = FT_block; else { // create random field if ((s_random (256) & 3) == 0) map.field[x][y].type = FT_nothing; else map.field[x][y].type = FT_stone; } for (d = 0; d < 4; d++) map.field[x][y].ex[d].frame = map.field[x][y].ex[d].count = 0; map.field[x][y].ex_nr = -1; map.field[x][y].frame = 0.0f; map.field[x][y].special = FT_nothing; } /* set the corners of the map to be valid start points */ // map_ensure_corner_start_points(); } /* will set the playerposition but in a way that we won't start on a block */ /* i am just too lazy to write this all again and again */ void map_set_playerposition (int usermap) { int pl; d_printf ("map_set_playerposition\n"); /* This is the new code that will set every player in a starting point * It should never fail, but if it does, it will fall through to the old method */ for (pl = 0; pl < MAX_PLAYERS; pl++) { if (PS_IS_used(players[pl].state)) { map_place_player(pl); } } } /* load a random map */ void map_random () { _direntry *destart, *de, *desel; char path[LEN_PATHFILENAME]; int max, sel; sprintf (path, "%s/maps", bman.datapath); desel = destart = s_getdir (path); for (max = 0, de = destart; de != NULL; de = de->next) if ((de->flags & DF_file) == DF_file) max++; sel = s_random (max); for (max = 0, de = destart; max <= sel && de != NULL; de = de->next) if ((de->flags & DF_file) == DF_file) { desel = de; max++; } d_printf ("Random Map %s (%d on %d)\n", desel->name, sel, max); if (desel != NULL) sprintf (map.map, "%s/maps/%s", bman.datapath, desel->name); } // Init the game according to options void init_map_tileset () { if (GT_MP_PTPM || GT_SP) { switch (map.map_selection) { case (0): map_new (map.map); break; case (1): map_random (); map_new (map.map); break; case (2): map_new (NULL); break; } if (map.random_tileset) tileset_random (); } } /* read from an open file map, determine field.x and field.y and fill the field. (# correspond to a bloc and @ correspond to a stone, an espace is nothing ' ' % are commentary at the beginning of the map */ void map_load (FILE * fmap) { size_t length; char *currentline; char tmp[MAX_FIELDSIZE_X]; int sizex = 0; int sizey = 0; int i; int d; while ((currentline = fgets (tmp, MAX_FIELDSIZE_X, fmap))) { length = strlen (currentline); if (currentline[0] == '%') continue; /* now each line correspond to the field */ else if (strstr (currentline, "bombs") == currentline) // bombs map.bombs = atoi (strchr (currentline, '=') + 1); else if (strstr (currentline, "fire") == currentline) // fire map.fire = atoi (strchr (currentline, '=') + 1); else if (strstr (currentline, "shoes") == currentline) // shoes map.shoes = atoi (strchr (currentline, '=') + 1); else if (strstr (currentline, "mixed") == currentline) // mixed map.mixed = atoi (strchr (currentline, '=') + 1); else if (strstr (currentline, "death") == currentline) // death map.death = atoi (strchr (currentline, '=') + 1); else if (strstr (currentline, "sp_trigger") == currentline) // trigger special map.sp_trigger = atoi (strchr (currentline, '=') + 1); else if (strstr (currentline, "sp_push") == currentline) // push special map.sp_push = atoi (strchr (currentline, '=') + 1); else if (strstr (currentline, "sp_row") == currentline) // row special map.sp_row = atoi (strchr (currentline, '=') + 1); else if (currentline[0] == '#') { /* the map itself */ for (i = 0; i < length; i++) { switch (currentline[i]) { case '#': map.field[i][sizey].type = FT_block; break; case '@': map.field[i][sizey].type = FT_stone; break; case ' ': map.field[i][sizey].type = FT_nothing; default: break; } for (d = 0; d < 4; d++) map.field[i][sizey].ex[d].frame = map.field[i][sizey].ex[d].count = 0; map.field[i][sizey].ex_nr = -1; map.field[i][sizey].frame = 0.0f; map.field[i][sizey].special = FT_nothing; } sizey++; if (sizex < length) sizex = length; } } map.size.x = sizex - 1; map.size.y = sizey; /* darw the border so we know everything is right */ for (i = 0; i < map.size.x; i++) map.field[i][0].type = map.field[i][map.size.y - 1].type = FT_block; for (i = 0; i < map.size.y; i++) map.field[0][i].type = map.field[map.size.x - 1][i].type = FT_block; fclose (fmap); }; /* This is called for randomly generated maps. It clears out each corner of the map * to make sure that the 4 corners are legal start points. */ int map_ensure_corner_start_points () { /* make sure all the corners are empty as well as the 1 field in the Y direction * and one in the X direction */ /* top left corner is safe start point */ map.field[1][1].type = FT_nothing; map.field[1][2].type = FT_nothing; map.field[2][1].type = FT_nothing; map_add_start_point(1, 1); /* bottom left corner is safe start point */ map.field[1][map.size.y - 2].type = FT_nothing; map.field[1][map.size.y - 3].type = FT_nothing; map.field[2][map.size.y - 2].type = FT_nothing; map_add_start_point(1, map.size.y - 2); /* top right corner is safe start point */ map.field[map.size.x - 2][1].type = FT_nothing; map.field[map.size.x - 3][1].type = FT_nothing; map.field[map.size.x - 2][2].type = FT_nothing; map_add_start_point(map.size.x - 2, 1); /* bottom right corner is safe start point */ map.field[map.size.x - 2][map.size.y - 2].type = FT_nothing; map.field[map.size.x - 2][map.size.y - 3].type = FT_nothing; map.field[map.size.x - 3][map.size.y - 2].type = FT_nothing; map_add_start_point(map.size.x - 2, map.size.y - 2); return 1; } /* initializes all the start points for the map to (-1,-1) and their used flag to 0 */ int map_init_start_points() { int i; for (i = 0; i < MAX_PLAYERS; i++) { map.start_point[i].pos.x = -1; map.start_point[i].pos.y = -1; map.start_point[i].used = 0; } return 0; } /* blindly sets (x,y) as the idx'th start point in the map */ int map_set_start_point(int idx, int x, int y) { map.start_point[idx].pos.x = x; map.start_point[idx].pos.y = y; return idx; } /* checks to see if all start points have been set, if not, sets (x,y) as a possible start point * returns 0 on successful set, -1 on failure */ int map_add_start_point(int x, int y) { int i; /* find the first unset start point */ for (i = 0; i < MAX_PLAYERS; i++) { if ((map.start_point[i].pos.x == -1) && (map.start_point[i].pos.y == -1)) { map_set_start_point(i, x, y); return 0; } } /* if all start points are already set, do nothing and return -1 */ return -1; } /* returns 0 if (x,y) is not already set as a start point in the current map, nonzero otherwise */ int map_is_start_point(int x, int y) { int i; for (i = 0; i < MAX_PLAYERS; i++) { if ((map.start_point[i].pos.x == x) && (map.start_point[i].pos.y == y)) return 1; } return 0; } /* returns the number of start points set in the current map */ int map_num_defined_start_points() { int pts = 0; int i; for (i = 0; i < MAX_PLAYERS; i++) { if ((map.start_point[i].pos.x != -1) && (map.start_point[i].pos.y != -1)) pts++; } return pts; } /* checks if the start point (x, y) is far enough away from all the other start points * returns 1 if it is far enough, 0 otherwise */ int map_check_start_point(int x, int y, int tol) { int i; int dx, dy; float dist; for (i = 0; i < MAX_PLAYERS; i++) { if ((map.start_point[i].pos.x != -1) && (map.start_point[i].pos.y != -1)) { dx = map.start_point[i].pos.x - x; dy = map.start_point[i].pos.y - y; dist = sqrt(dx * dx + dy * dy); if (dist < tol) return 0; } } return 1; } /* checks to see if there is an available start point and if (x, y) is sufficiently far from all * defined start points and adds (x, y) as a start point if the conditions are met. * returns 1 on success, 0 on failure */ int map_check_and_add_start_point(int x, int y, int tol) { if ((map_num_defined_start_points() < MAX_PLAYERS) && (map_check_start_point(x, y, tol))) { map_add_start_point(x, y); return 1; } return 0; } /* locates and adds num start points to the current map. returns the number of start points added. */ int map_find_and_add_start_points(int num, int tol) { int x; int y; int i; int added = 0; while ((added < num) && (tol >= 0)) { for (x = 0; x < map.size.x; x++) { for (y = 0; y < map.size.y; y++) { if (map_is_possible_start_point(x, y)) { added += map_check_and_add_start_point(x, y, tol); } } } for (i = 0; i < num - added; i++) { added += map_create_and_add_start_point(tol); } tol--; } /* printf("Minimum Tolerance: %d\n", tol + 1); */ return added; } /* returns 1 if (x,y) is a feasible start point such that the player can safely lay a bomb and not die * otherwise returns 0. Note: this is a quick check only checking immediately adjacent fields. It will not * check secondary adjacencies, however, the idea is that when looking for possible start poitns, it will be run * on every FT_nothing field so it will catch the secondary adjacencies when they become primary adjacencies */ int map_is_possible_start_point(int x, int y) { int x_ok_pos; int x_ok_neg; int y_ok_pos; int y_ok_neg; int x_adj = 0; int y_adj = 0; int i; /* if (x, y) is not FT_nothing, this is not a valid start point */ if (map.field[x][y].type != FT_nothing) { return 0; } x_ok_pos = (x < map.size.x - 2) ? 1:0; x_ok_neg = (x > 1) ? 1:0; y_ok_pos = (y < map.size.y - 2) ? 1:0; y_ok_neg = (y > 1) ? 1:0; /* calculate the number of adjacent FT_nothing fields in the X and Y directions */ for (i = 1; i < bman.start_range + 2; i++) { if (x_ok_pos) { if (map.field[x+i][y].type == FT_nothing) { x_adj++; } else { x_ok_pos = 0; } } if (x_ok_neg) { if (map.field[x-i][y].type == FT_nothing) { x_adj++; } else { x_ok_neg = 0; } } if (y_ok_pos) { if (map.field[x][y+i].type == FT_nothing) { y_adj++; } else { y_ok_pos = 0; } } if (y_ok_neg) { if (map.field[x][y-i].type == FT_nothing) { y_adj++; } else { y_ok_neg = 0; } } } if ((x_adj >= bman.start_range + 1) || (y_adj >= bman.start_range + 1)) { return 1; } if ((x_adj >= 1) && (y_adj >= 1)) { return 1; } return 0; } /* alters the map to create another start point at least tol units from any other * start point. returns 1 on success, 0 on failure */ int map_create_and_add_start_point(int tol) { int x; int y; int dx; int dy; int init_x; int init_y; int end_x; int end_y; int step_x; int step_y; /* this changes how we traverse the map when looking for a place to put * a start point. this is so all the start points don't get stuck in one * part of the map if the map is large enough */ if (s_random(100) % 2) { init_x = 0; end_x = map.size.x; step_x = 1; } else { init_x = map.size.x - 1; end_x = -1; step_x = -1; } if (s_random(100) % 2) { init_y = 0; end_y = map.size.y; step_y = 1; } else { init_y = map.size.y - 1; end_y = -1; step_y = -1; } /* first try only FT_nothing fields as start points */ for (x = init_x; x != end_x; x += step_x) { for (y = init_y; y != end_y; y+= step_y) { if ((map.field[x][y].type == FT_nothing) && (map_check_start_point(x, y, tol))) { dx = (x >= map.size.x - 2) ? -1:1; dy = (y >= map.size.y - 2) ? -1:1; if ((map_is_removable_field(x+dx, y)) && (map_is_removable_field(x, y+dy))) { /* printf("Creating Start Point (%d, %d).\n", x, y); */ map.field[x][y].type = FT_nothing; map.field[x+dx][y].type = FT_nothing; map.field[x][y+dy].type = FT_nothing; map_add_start_point(x, y); return 1; } } } } /* if we get here we didn't find a useful FT_nothing field, so check the FT_stone * fields */ for (x = init_x; x != end_x; x += step_x) { for (y = init_y; y != end_y; y+= step_y) { if ((map.field[x][y].type == FT_stone) && (map_check_start_point(x, y, tol))) { dx = (x >= map.size.x - 2) ? -1:1; dy = (y >= map.size.y - 2) ? -1:1; if ((map_is_removable_field(x+dx, y)) && (map_is_removable_field(x, y+dy))) { /* printf("Creating Start Point (%d, %d).\n", x, y); */ map.field[x][y].type = FT_nothing; map.field[x+dx][y].type = FT_nothing; map.field[x][y+dy].type = FT_nothing; map_add_start_point(x, y); return 1; } } } } /* if we get to this point, we tried every field that we want to turn into a * start point, so we return 0 indicating failure */ return 0; } /* checks the type of the field at (x, y). if it is something we can remove without * drastically altering the map (that is to say, not a FT_tunnel or FT_block) returns * 1, returns 0 if this is not a field that we want to alter */ int map_is_removable_field(int x, int y) { if ((map.field[x][y].type == FT_nothing) || (map.field[x][y].type == FT_stone)) { return 1; } else { return 0; } } /* sets the players[pl] initial position to one of the start_points found in the map * randomly selects the start point so the same players don't always start near each * other */ int map_place_player(int pl) { int index; int start_points; int idx; int i; start_points = map_num_defined_start_points(); index = (s_random(MAX_PLAYERS) + 1) % start_points; for (i = 0; i < start_points; i++) { idx = (index + i) % start_points; if ((!map.start_point[idx].used) && (map.start_point[idx].pos.x != -1) && (map.start_point[idx].pos.y != -1)) { players[pl].pos.x = map.start_point[idx].pos.x; players[pl].pos.y = map.start_point[idx].pos.y; map.start_point[idx].used = 1; return 1; } } return 0; } /* this will randomly select a start point, check to see if it is a safe place to * respawn the player, and then set their position. It cannot fail unless there is * no start point possible on the map (which would prevent the game from ever starting) */ /* note: this is not used yet, but could be used to respawn players instead of the old * method */ int map_respawn_player(int pl) { int x; int y; do { x = s_random (map.size.x - 2) + 1; y = s_random (map.size.y - 2) + 1; } while (!map_is_possible_start_point(x, y)); players[pl].pos.x = x; players[pl].pos.y = y; return 1; }