/* SPDX-License-Identifier: GPL-3.0-or-later */ /* Copyright (C) 2021 KikooDX */ #include "conf.h" #include "filepaths.h" #include "level.h" #include "tiles.h" #include #include #include #define assert(condition, error_msg) \ if (!(condition)) { \ fatal_error = -1; \ fatal_error_msg = error_msg; \ return; \ } /* globals are needed when using gint_switch() */ struct Level level; int level_id = 0; int fatal_error = 0; char *fatal_error_msg = "no error, happy kikoo :D"; int debug_value = 0; static int read_byte(int file); static Tile read_merge_bytes(int file, int size); static int autotile_value(int x, int y); /* Load level from storage memory. */ void level_load(void) { int file; int tile_size; int level_size; int file_size; int i; int x; int y; char byte; Tile tile; /* open file read only */ file = BFile_Open(filepaths[level_id], BFile_ReadOnly); assert(file >= 0, "can't open file"); /* assert KBLE format version */ byte = read_byte(file); assert(byte == KBLE_FORMAT_VERSION, "kble format version doesn't match"); /* get tile size (in bytes) */ tile_size = read_byte(file); assert(tile_size >= 0, "can't read tile size"); assert((unsigned int)tile_size <= sizeof(Tile), "tiles are too heavy"); /* assert than width and height are correct */ level.width = read_merge_bytes(file, 2); level.height = read_merge_bytes(file, 2); assert(level.width >= 0, "can't read level width"); assert(level.width == LEVEL_WIDTH, "invalid level width"); assert(level.height >= 0, "can't read level height"); assert(level.height == LEVEL_HEIGHT, "invalid level height"); level_size = level.width * level.height; /* using those informations, see if the file size make sense */ file_size = BFile_Size(file); assert(file_size >= 0, "can't read file size"); assert(file_size == KBLE_HEADER_LEN + level_size * tile_size, "file size doesn't make sense"); /* read file content */ level.gold = 0; level.exit_locked = 0; for (i = 0; i < level_size; i += 1) { tile = read_merge_bytes(file, tile_size); assert(tile != (Tile)-1, "can't read tile"); level.data[i] = tile; /* special tiles */ switch (tile) { case TILE_GOLD: level.gold += 1; break; case TILE_SWITCH: level.exit_locked = 1; break; default: break; } } /* set level id for display */ level.id = level_id; /* compute visuals */ y = level.height; i = 0; while (y-- > 0) { x = level.width; while (x-- > 0) { tile = level.data[x + y * level.width]; struct VisualTile visual_data; visual_data.x = x * TILE_WIDTH; visual_data.y = y * TILE_HEIGHT; visual_data.visible = tile != TILE_START; if (tile == TILE_SOLID) { const int autotile = autotile_value(x, y); visual_data.texture_x = autotile * TILE_WIDTH; visual_data.texture_y = TILE_HEIGHT; visual_data.visible = autotile != 15; } else if (tile != TILE_VOID) { visual_data.texture_x = (int)(tile % TILESET_WIDTH) * TILE_WIDTH; visual_data.texture_y = (int)(tile / TILESET_WIDTH) * TILE_HEIGHT; } else { visual_data.visible = 0; } level.visual_data[x + y * level.width] = visual_data; } } /* close file */ file = BFile_Close(file); assert(file >= 0, "file closure failed miserably"); } /* Read a single byte. Negative value is BFile error code. */ static int read_byte(int file) { char byte; const int error = BFile_Read(file, &byte, sizeof(char), -1); if (error < 0) return error; else return byte; } /* Read multiple bytes and merge them as one integer. * Return -1 on failure. */ static Tile read_merge_bytes(int file, int size) { Tile merged = 0; int byte = 0; char *byte_ptr = (char *)&merged; int i = 0; byte_ptr += sizeof(Tile) - size; for (i = 0; i < size; i += 1) { byte = read_byte(file); if (byte < 0) return -1; *byte_ptr = byte; byte_ptr += 1; } return merged; } static int autotile_value(int x, int y) { return ((level_get_tile(x - 1, y) == TILE_SOLID) | ((level_get_tile(x + 1, y) == TILE_SOLID) << 1) | ((level_get_tile(x, y - 1) == TILE_SOLID) << 2) | ((level_get_tile(x, y + 1) == TILE_SOLID) << 3)); }