momento/src/level/load.c

216 lines
5.2 KiB
C

/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright (C) 2021 KikooDX */
#include "conf.h"
#include "level.h"
#include "pack_count.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 uint16_t *filepath = u"none";
static void level_load_post(void);
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_bfile(void)
{
int file;
int tile_size;
int level_size;
int file_size;
int i;
uint8_t byte;
Tile tile;
/* open file read only */
file = BFile_Open(filepath, 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 */
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;
}
level_load_post();
/* close file */
file = BFile_Close(file);
assert(file >= 0, "file closure failed miserably");
}
/* Load level bundled in game binary. */
void
level_load_binary(void)
{
extern uint8_t *level_binaries[];
uint8_t *level_binary;
int i;
assert(level_id < LEVEL_COUNT, "level doesn't exist");
level_binary = level_binaries[level_id];
i = LEVEL_SIZE;
while (i-- > 0)
level.data[i] = level_binary[KBLE_HEADER_LEN + i];
level.width = LEVEL_WIDTH;
level.height = LEVEL_HEIGHT;
level_load_post();
}
/* Shared post processing between BFile and binary level load. */
static void
level_load_post(void)
{
Tile tile;
int x;
int y;
level.gold = 0;
level.exit_locked = 0;
/* set level id for display */
level.id = level_id;
/* disable water by default */
level.water_level = -1;
level.water_timer = 0;
y = level.height;
while (y-- > 0) {
x = level.width;
while (x-- > 0) {
int visible = 1;
tile = level.data[x + y * level.width];
/* special tiles */
switch (tile) {
case TILE_START:
visible = 0;
break;
case TILE_GOLD:
level.gold += 1;
break;
case TILE_SWITCH:
level.exit_locked = 1;
break;
case TILE_WATER:
level.water_level = y;
visible = 0;
break;
default:
break;
}
/* compute visuals */
struct VisualTile visual_data;
visual_data.x = x * TILE_WIDTH;
visual_data.y = y * TILE_HEIGHT;
visual_data.visible = visible;
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;
}
}
}
/* Read a single byte. Negative value is BFile error code. */
static int
read_byte(int file)
{
uint8_t byte;
const int error = BFile_Read(file, &byte, sizeof(uint8_t), -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;
uint8_t *byte_ptr = (uint8_t *)&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));
}