314 lines
6.8 KiB
C
314 lines
6.8 KiB
C
#include "main.h"
|
|
#include <gint/display.h>
|
|
#include <gint/keyboard.h>
|
|
#define NENEMIES 64
|
|
|
|
/* State definition, as used throughout the main game.
|
|
*
|
|
* For each enemy, the data is:
|
|
* - alive: if the enemy is alive or dead.
|
|
*
|
|
* - nstepf: number of frames for one step, defines the speed of the
|
|
* enemy.
|
|
*
|
|
* - x, y: calculated only for display and collision.
|
|
* - step, substep: where the AI is at. `step` is the index starting at zero
|
|
* of the path where is at, `substep` is the substep from 0 to 7, looping
|
|
* when 8 (augmenting step, except when at the castle).
|
|
* - nfwait: number of frames to be waited.
|
|
* - pdr: the enemy that spawned previously or is the next further on the
|
|
* path. Simple chain list that could evolve later if some enemies are
|
|
* slowed down or have various speeds, allowing some enemies to overtake
|
|
* others. */
|
|
|
|
struct enemy {
|
|
int alive;
|
|
|
|
int nstepf;
|
|
|
|
int x, y;
|
|
int step, substep;
|
|
int nfwait;
|
|
struct enemy **ndrp; /* pointer on the pointer to this enemy structure */
|
|
struct enemy *pdr; /* previous enemy to be drawn */
|
|
};
|
|
|
|
struct state {
|
|
/* Map information:
|
|
*
|
|
* - w, h: dimensions
|
|
* - sp_x, sp_y: enemy spawn coordinates
|
|
* - nep: number of enemy path */
|
|
|
|
int w, h;
|
|
int sp_x, sp_y;
|
|
int nep; /* number of enemies path. */
|
|
uint8_t *grid;
|
|
uint8_t *ep;
|
|
|
|
/* AI information:
|
|
*
|
|
* - tbsp: time between enemy spawns.
|
|
* - tnsp: time to next spawn (as a number of ticks).
|
|
* - nae: number of currently alive enemies.
|
|
* - first_enemy: pointer to the last enemy to have spawned,
|
|
* to draw first */
|
|
|
|
int tbsp, tnsp;
|
|
int nae;
|
|
struct enemy *first_enemy;
|
|
|
|
/* Camera, cursor and display information:
|
|
*
|
|
* - cam_x, cam_y: camera coordinates, always displays 16x8 tiles
|
|
* - cur_x, cur_y: map coordinate for the cursor.
|
|
* - drf: draw frame. */
|
|
|
|
int cam_x, cam_y;
|
|
int cur_x, cur_y;
|
|
int drf; /* draw frame, varies from 0 to 255 and looping */
|
|
|
|
/* Raw data */
|
|
|
|
struct enemy enemies[NENEMIES];
|
|
};
|
|
|
|
static struct state state;
|
|
|
|
/* init(raw): initialize the state with the given raw map data. */
|
|
|
|
static void init(uint8_t *raw)
|
|
{
|
|
int i;
|
|
|
|
/* Initialize map information */
|
|
|
|
state.w = raw[0];
|
|
state.h = raw[1];
|
|
state.sp_x = raw[4];
|
|
state.sp_y = raw[5];
|
|
state.nep = raw[6];
|
|
state.grid = &raw[(raw[16] << 8) | raw[17]];
|
|
state.ep = &raw[(raw[18] << 8) | raw[19]];
|
|
|
|
/* Initialize AI information */
|
|
|
|
state.tbsp = 50;
|
|
state.tnsp = state.tbsp;
|
|
state.nae = 0;
|
|
state.first_enemy = NULL;
|
|
|
|
for (i = 0; i < NENEMIES; i++)
|
|
state.enemies[i].alive = 0;
|
|
|
|
/* Initialize display information */
|
|
|
|
state.cam_x = raw[2];
|
|
state.cam_y = raw[3];
|
|
state.cur_x = 1;
|
|
state.cur_y = 1;
|
|
state.drf = 0;
|
|
}
|
|
|
|
/* tick(): tick the game engine. */
|
|
|
|
static void tick()
|
|
{
|
|
struct enemy *ep;
|
|
int i;
|
|
|
|
for (ep = state.first_enemy; ep; ep = ep->pdr) {
|
|
if (!--ep->nfwait) {
|
|
ep->nfwait = ep->nstepf;
|
|
|
|
switch (state.ep[ep->step]) {
|
|
case 0:
|
|
ep->y--;
|
|
break;
|
|
case 1:
|
|
ep->x++;
|
|
break;
|
|
case 2:
|
|
ep->y++;
|
|
break;
|
|
case 3:
|
|
ep->x--;
|
|
break;
|
|
}
|
|
|
|
ep->substep++;
|
|
if (ep->substep == 8) {
|
|
ep->step++;
|
|
ep->substep = 0;
|
|
|
|
if (ep->step == state.nep) {
|
|
ep->alive = 0;
|
|
if (ep->ndrp)
|
|
(*ep->ndrp) = ep->pdr;
|
|
|
|
state.nae--;
|
|
|
|
/* TODO: remove one life to the castle */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Spawn enemies if required */
|
|
|
|
if (!--state.tnsp) {
|
|
state.tnsp = state.tbsp;
|
|
|
|
if (state.nae < NENEMIES) {
|
|
for (i = 0, ep = &state.enemies[0]; i < NENEMIES; i++, ep++);
|
|
ep->pdr = state.first_enemy;
|
|
ep->pdr->ndrp = &ep->pdr;
|
|
state.first_enemy = ep;
|
|
ep->ndrp = &state.first_enemy;
|
|
|
|
ep->alive = 1;
|
|
ep->x = state.sp_x << 3;
|
|
ep->y = state.sp_y << 3;
|
|
ep->step = 0;
|
|
ep->substep = 0;
|
|
|
|
/* TODO: use a given type of enemy? */
|
|
|
|
ep->nstepf = 10;
|
|
ep->nfwait = ep->nstepf;
|
|
|
|
state.nae++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* tile_cam(x, y): return the type of the tile at coordinates (x, y) relative
|
|
* to the current camera position.
|
|
*
|
|
* If out of bounds, a basic placement tile (id. 0) will be returned */
|
|
|
|
static inline int tile_cam(int x, int y)
|
|
{
|
|
x += state.cam_x;
|
|
y += state.cam_y;
|
|
|
|
if (x < 0 || x >= state.w || y < 0 || y >= state.h)
|
|
return 0;
|
|
return state.grid[y * state.w + x];
|
|
}
|
|
|
|
/* draw(): basically the drawing function for everything. */
|
|
|
|
static void draw()
|
|
{
|
|
int x, y;
|
|
|
|
dclear(C_WHITE);
|
|
|
|
/* draw the grid */
|
|
|
|
for (y = 0; y < 8; y++) {
|
|
for (x = 0; x < 16; x++) {
|
|
int tile = tile_cam(x, y);
|
|
int ox = x * 8, oy = y * 8;
|
|
|
|
if (tile == 1 || tile == 2) {
|
|
/* above, below, left, right */
|
|
if (tile_cam(x, y - 1) == 0)
|
|
dline(ox, oy, ox + 7, oy, C_BLACK);
|
|
if (tile_cam(x, y + 1) == 0)
|
|
dline(ox, oy + 7, ox + 7, oy + 7, C_BLACK);
|
|
if (tile_cam(x - 1, y) == 0)
|
|
dline(ox, oy, ox, oy + 7, C_BLACK);
|
|
if (tile_cam(x + 1, y) == 0)
|
|
dline(ox + 7, oy, ox + 7, oy + 7, C_BLACK);
|
|
|
|
/* top left, top right, bottom left, bottom right */
|
|
if (tile_cam(x - 1, y - 1) == 0)
|
|
dpixel(ox, oy, C_BLACK);
|
|
if (tile_cam(x + 1, y - 1) == 0)
|
|
dpixel(ox + 7, oy, C_BLACK);
|
|
if (tile_cam(x - 1, y + 1) == 0)
|
|
dpixel(ox, oy + 7, C_BLACK);
|
|
if (tile_cam(x + 1, y + 1) == 0)
|
|
dpixel(ox + 7, oy + 7, C_BLACK);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw the enemies. */
|
|
|
|
{
|
|
struct enemy *ep;
|
|
extern bopti_image_t img_enemy;
|
|
|
|
for (ep = state.first_enemy; ep; ep = ep->pdr)
|
|
dimage(ep->x, ep->y, &img_enemy);
|
|
}
|
|
|
|
/* Draw the cursor */
|
|
|
|
{
|
|
int d = state.drf & 128 ? 2 : 0;
|
|
int ox = state.cur_x - state.cam_x + 1;
|
|
int oy = state.cur_y - state.cam_y + 1;
|
|
|
|
dline(ox + d, oy, ox + d + 1, oy, C_BLACK);
|
|
dline(ox + d + 4, oy, ox + d + 5, oy, C_BLACK);
|
|
dline(ox, oy + d, ox, oy + d + 1, C_BLACK);
|
|
dline(ox, oy + d + 4, ox, oy + d + 5, C_BLACK);
|
|
|
|
dline(ox + d, oy + 7, ox + d + 1, oy + 7, C_BLACK);
|
|
dline(ox + d + 4, oy + 7, ox + d + 5, oy + 7, C_BLACK);
|
|
dline(ox + 7, oy + d, ox + 7, oy + d + 1, C_BLACK);
|
|
dline(ox + 7, oy + d + 4, ox + 7, oy + d + 5, C_BLACK);
|
|
}
|
|
|
|
/* okay we can update now */
|
|
|
|
dprint(16, 96, C_BLACK, "state.tnsp = 0x%08X", state.tnsp);
|
|
dupdate();
|
|
}
|
|
|
|
/* Main game function. */
|
|
|
|
menu_t *game(menu_t *last_menu)
|
|
{
|
|
int timeout = 1 /* a non-zero value just in case */;
|
|
extern uint8_t map_basic[];
|
|
|
|
(void)last_menu;
|
|
|
|
init(map_basic);
|
|
while (1) {
|
|
int should_draw = 0;
|
|
key_event_t event = getkey_opt(GETKEY_MOD_SHIFT | GETKEY_BACKLIGHT,
|
|
&timeout);
|
|
|
|
/* TODO: find a way to creatively deduce the time it takes to
|
|
* go from here to the `if (should_draw)`, in order to keep
|
|
* an as-constant-as-possible draw time */
|
|
|
|
switch (event.type) {
|
|
case KEYEV_NONE:
|
|
/* timeout event */
|
|
should_draw = 1;
|
|
break;
|
|
case KEYEV_DOWN:
|
|
if (event.key == KEY_EXE)
|
|
return no_menu;
|
|
}
|
|
|
|
if (timeout < 0 || should_draw) {
|
|
timeout = 65;
|
|
state.drf = (state.drf + 1) & 255;
|
|
|
|
/* Here, we should draw. */
|
|
tick(); /* TODO: perhaps a better placement somewhere */
|
|
draw();
|
|
}
|
|
}
|
|
|
|
return no_menu;
|
|
}
|