317 lines
7.1 KiB
C
317 lines
7.1 KiB
C
#include <gint/display.h>
|
|
#include <gint/keyboard.h>
|
|
#include <gint/std/string.h>
|
|
#include "engine.h"
|
|
|
|
#define CELL_X(x) (-2 + 10 * (x))
|
|
#define CELL_Y(y) (-3 + 10 * (y))
|
|
|
|
//---
|
|
// Utilities
|
|
//---
|
|
|
|
|
|
/* Determine the cycle pattern for a certain door */
|
|
static char const *map_door_cycle(struct map const *map, int x, int y)
|
|
{
|
|
int door = map->tiles[y * map->w + x];
|
|
int cycle_id = -1;
|
|
|
|
if(door >= TILE_VDOOR && door < TILE_VDOOR + 8)
|
|
cycle_id = door - TILE_VDOOR;
|
|
else if(door >= TILE_HDOOR && door < TILE_HDOOR + 8)
|
|
cycle_id = door - TILE_HDOOR + 8;
|
|
else
|
|
return NULL;
|
|
|
|
return map->door_cycle + map->door_cycle_index[cycle_id];
|
|
}
|
|
|
|
/* Check whether a cell of the map is walkable */
|
|
static bool map_walkable(struct game const *game, int x, int y)
|
|
{
|
|
/* Refuse to walk on walls */
|
|
int tile = game->map->tiles[y * game->map->w + x];
|
|
if(tile == TILE_WALL) return false;
|
|
|
|
/* Refuse to walk on closed doors */
|
|
for(int t = 0; t < game->dynamic_tile_count; t++)
|
|
{
|
|
struct dynamic_tile const *dt = &game->dynamic_tiles[t];
|
|
if(dt->x != x || dt->y != y) continue;
|
|
|
|
char const *cycle = map_door_cycle(game->map, x, y);
|
|
if(!dt->state2 && cycle[dt->state] == '#')
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//---
|
|
// Map loading
|
|
//---
|
|
|
|
void engine_load(struct game *game, struct map const *map)
|
|
{
|
|
game->map = map;
|
|
game->time = 0;
|
|
|
|
for(int p = 0; p < PLAYER_COUNT + 1; p++)
|
|
game->players[p] = NULL;
|
|
|
|
memset(&game->dynamic_tiles, 0,
|
|
MAP_DYNAMIC_TILES * sizeof(struct dynamic_tile));
|
|
|
|
/* Create dynamic tiles for suitable tiles in the map */
|
|
int t = 0;
|
|
|
|
for(int y = 0; y < map->h && t < MAP_DYNAMIC_TILES; y++)
|
|
for(int x = 0; x < map->w && t < MAP_DYNAMIC_TILES; x++)
|
|
{
|
|
int tile = map->tiles[y*map->w + x];
|
|
struct dynamic_tile *dt = &game->dynamic_tiles[t];
|
|
|
|
dt->y = y;
|
|
dt->x = x;
|
|
dt->state = 0;
|
|
|
|
int is_dynamic = 1;
|
|
|
|
if(tile == TILE_START)
|
|
{
|
|
dt->idle = !anim_tile_start(&dt->anim, 1);
|
|
}
|
|
else if(tile == TILE_END)
|
|
{
|
|
dt->idle = !anim_tile_end(&dt->anim, 1);
|
|
}
|
|
else if(tile >= TILE_KEY && tile < TILE_KEY + 8)
|
|
{
|
|
/* TODO: Initial keys' dynamic tile information */
|
|
}
|
|
else if((tile >= TILE_VDOOR && tile < TILE_VDOOR + 8) ||
|
|
(tile >= TILE_HDOOR && tile < TILE_HDOOR + 8))
|
|
{
|
|
dt->anim.dir = (tile < TILE_HDOOR) ? DIR_UP : DIR_LEFT;
|
|
char const *cycle = map_door_cycle(map, x, y);
|
|
|
|
if(cycle[dt->state] == '#')
|
|
dt->idle = !anim_door_closed(&dt->anim, 1);
|
|
else
|
|
dt->idle = !anim_door_open(&dt->anim, 1);
|
|
|
|
}
|
|
else is_dynamic = 0;
|
|
|
|
t += is_dynamic;
|
|
}
|
|
|
|
game->dynamic_tile_count = t;
|
|
}
|
|
|
|
void engine_spawn(struct game *game)
|
|
{
|
|
struct map const *map = game->map;
|
|
int p = 0;
|
|
|
|
for(int y = 0; y < map->h && game->players[p]; y++)
|
|
for(int x = 0; x < map->w && game->players[p]; x++)
|
|
{
|
|
/* Try to spawn a player at (x,y) */
|
|
if(map->tiles[y*map->w + x] == TILE_START)
|
|
{
|
|
struct player *player = game->players[p];
|
|
|
|
player->y = y;
|
|
player->x = x;
|
|
player->dir = DIR_DOWN;
|
|
player->anim.dir = DIR_DOWN;
|
|
player->idle = !anim_player_idle(&player->anim, 1);
|
|
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool engine_all_players_idle(struct game *game)
|
|
{
|
|
for(int p = 0; game->players[p]; p++)
|
|
{
|
|
if(!game->players[p]->idle) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool engine_all_dynamic_tiles_idle(struct game *game)
|
|
{
|
|
for(int t = 0; t < game->dynamic_tile_count; t++)
|
|
{
|
|
if(!game->dynamic_tiles[t].idle) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool engine_wins(struct game *game, struct player *player)
|
|
{
|
|
/* First check that the player is participating in this game */
|
|
bool found = false;
|
|
for(int p = 0; game->players[p] && !found; p++)
|
|
{
|
|
if(game->players[p] == player) found = true;
|
|
}
|
|
if(!found) return false;
|
|
|
|
int tile = game->map->tiles[player->y * game->map->w + player->x];
|
|
return (tile == TILE_END);
|
|
}
|
|
|
|
//---
|
|
// Animations
|
|
//---
|
|
|
|
void engine_tick(struct game *game, int delta_time)
|
|
{
|
|
game->time += delta_time;
|
|
|
|
/* Update the animations for every player */
|
|
for(int p = 0; game->players[p]; p++)
|
|
{
|
|
struct player *player = game->players[p];
|
|
|
|
player->anim.duration -= delta_time;
|
|
if(player->anim.duration > 0) continue;
|
|
|
|
/* Call the animation function to generate the next frame */
|
|
player->idle = !player->anim.function(&player->anim, 0);
|
|
}
|
|
|
|
/* Update animations for dynamic tiles */
|
|
for(int t = 0; t < game->dynamic_tile_count; t++)
|
|
{
|
|
struct dynamic_tile *dt = &game->dynamic_tiles[t];
|
|
|
|
dt->anim.duration -= delta_time;
|
|
if(dt->anim.duration > 0) continue;
|
|
|
|
dt->idle = !dt->anim.function(&dt->anim, 0);
|
|
}
|
|
}
|
|
|
|
//---
|
|
// Rendering
|
|
//---
|
|
|
|
static void engine_draw_player(struct player const *player)
|
|
{
|
|
dframe(CELL_X(player->x) - 1 + player->anim.dx,
|
|
CELL_Y(player->y) - 5 + player->anim.dy,
|
|
player->anim.img);
|
|
}
|
|
|
|
static void engine_draw_dynamic_tile(struct dynamic_tile const *dt)
|
|
{
|
|
/* The -10 is hardcoded to suit the format of the tilesheet */
|
|
dframe(CELL_X(dt->x) + dt->anim.dx,
|
|
CELL_Y(dt->y) + dt->anim.dy - 10,
|
|
dt->anim.img);
|
|
}
|
|
|
|
void engine_draw(struct game const *game)
|
|
{
|
|
dclear(C_WHITE);
|
|
dimage(0, 0, game->map->img);
|
|
|
|
for(int t = 0; t < game->dynamic_tile_count; t++)
|
|
{
|
|
engine_draw_dynamic_tile(&game->dynamic_tiles[t]);
|
|
}
|
|
|
|
for(int p = 0; game->players[p]; p++)
|
|
{
|
|
engine_draw_player(game->players[p]);
|
|
}
|
|
}
|
|
|
|
//---
|
|
// Physics
|
|
//---
|
|
|
|
int engine_move(struct game *game, struct player *player, int dir)
|
|
{
|
|
int dx = (dir == DIR_RIGHT) - (dir == DIR_LEFT);
|
|
int dy = (dir == DIR_DOWN) - (dir == DIR_UP);
|
|
int olddir = player->dir;
|
|
|
|
/* Don't move players that are already moving */
|
|
if(!player->idle) return 0;
|
|
|
|
/* Update the direction */
|
|
player->dir = dir;
|
|
player->anim.dir = dir;
|
|
|
|
/* Only move the player if the destination is walkable */
|
|
if(!map_walkable(game, player->x + dx, player->y + dy))
|
|
{
|
|
/* If not, set the new idle animation */
|
|
if(dir != olddir)
|
|
{
|
|
player->idle = !anim_player_idle(&player->anim,1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
player->x += dx;
|
|
player->y += dy;
|
|
|
|
/* Set the walking animation */
|
|
player->idle = !anim_player_walking(&player->anim, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void engine_finish_turn(struct game *game)
|
|
{
|
|
/* TODO: Have players pick up keys and open corresponding doors */
|
|
|
|
/* Update door cycles */
|
|
for(int t = 0; t < game->dynamic_tile_count; t++)
|
|
{
|
|
struct dynamic_tile *dt = &game->dynamic_tiles[t];
|
|
char const *cycle = map_door_cycle(game->map, dt->x, dt->y);
|
|
if(!cycle) continue;
|
|
|
|
/* Determine current open/closed status */
|
|
int current_status = dt->state2 ? '.' : cycle[dt->state];
|
|
|
|
/* Move to next cycle state */
|
|
dt->state++;
|
|
if(cycle[dt->state] == ' ' || cycle[dt->state] == 0)
|
|
dt->state = 0;
|
|
dt->state2 = 0;
|
|
|
|
/* Determine next open/closed status (this might be different
|
|
from the cycle state if a player standing in the door
|
|
prevents if from closing) */
|
|
int new_status = cycle[dt->state];
|
|
|
|
for(int p = 0; game->players[p]; p++)
|
|
{
|
|
struct player *player = game->players[p];
|
|
if(player->x == dt->x && player->y == dt->y)
|
|
{
|
|
new_status = '.';
|
|
dt->state2 = 1;
|
|
}
|
|
}
|
|
|
|
if(new_status == current_status) continue;
|
|
|
|
/* Set animations when changing status */
|
|
if(new_status == '#')
|
|
dt->idle = !anim_door_closing(&dt->anim, 1);
|
|
else if(new_status == '.')
|
|
dt->idle = !anim_door_opening(&dt->anim, 1);
|
|
}
|
|
}
|