mystnb/src/engine.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);
}
}