momento/src/level/load.c

169 lines
4.3 KiB
C

/* 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 <gint/bfile.h>
#include <gint/std/stdlib.h>
#include <stdint.h>
#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));
}