You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bomberclone/src/map.c

850 lines
23 KiB

/* $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;
}