406 lines
12 KiB
C++
406 lines
12 KiB
C++
#include "level.h"
|
|
#include "generator.h"
|
|
#include "render.h"
|
|
#include "game.h"
|
|
#include "util.h"
|
|
#include "log.h"
|
|
#include "menu.h"
|
|
|
|
#include <gint/display.h>
|
|
#include <gint/keyboard.h>
|
|
#include <gint/timer.h>
|
|
#include <gint/clock.h>
|
|
#include <gint/cpu.h>
|
|
#include <azur/gint/render.h>
|
|
#include <gint/drivers/r61524.h>
|
|
#include <gint/drivers/keydev.h>
|
|
#include <fxlibc/printf.h>
|
|
#include <gint/gint.h>
|
|
|
|
#ifdef LOG_USB_ENABLE
|
|
|
|
#include <gint/usb.h>
|
|
#include <gint/usb-ff-bulk.h>
|
|
|
|
/* Screenshot next frame */
|
|
bool volatile screenshot = false;
|
|
/* Send next frame as video to fxlink */
|
|
bool volatile videocapture = false;
|
|
|
|
static bool async_event_filter(key_event_t ev)
|
|
{
|
|
if(ev.key == KEY_7) {
|
|
if(ev.type == KEYEV_DOWN)
|
|
screenshot = true;
|
|
return false;
|
|
}
|
|
if(ev.key == KEY_8) {
|
|
if(ev.type == KEYEV_DOWN)
|
|
videocapture = !videocapture;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void hook_prefrag(int id, void *fragment, int size)
|
|
{
|
|
if(!screenshot && !videocapture)
|
|
return;
|
|
|
|
/* Screenshot takes precedence */
|
|
char const *type = screenshot ? "image" : "video";
|
|
|
|
int pipe = usb_ff_bulk_output();
|
|
|
|
if(id == 0) {
|
|
usb_fxlink_header_t h;
|
|
usb_fxlink_image_t sh;
|
|
int size = azrp_width * azrp_height * 2;
|
|
|
|
usb_fxlink_fill_header(&h, "fxlink", type, size + sizeof sh);
|
|
sh.width = htole32(azrp_width);
|
|
sh.height = htole32(azrp_height);
|
|
sh.pixel_format = htole32(USB_FXLINK_IMAGE_RGB565);
|
|
|
|
usb_write_sync(pipe, &h, sizeof h, false);
|
|
usb_write_sync(pipe, &sh, sizeof sh, false);
|
|
}
|
|
|
|
usb_write_sync(pipe, fragment, size, false);
|
|
|
|
if(id == azrp_frag_count - 1) {
|
|
usb_commit_sync(pipe);
|
|
screenshot = false;
|
|
}
|
|
}
|
|
|
|
static void hook_dupdate(void)
|
|
{
|
|
if(screenshot) {
|
|
usb_fxlink_screenshot(true);
|
|
screenshot = false;
|
|
}
|
|
else if(videocapture) {
|
|
usb_fxlink_videocapture(false);
|
|
}
|
|
}
|
|
|
|
#endif /* LOG_USB_ENABLE */
|
|
|
|
int play_level(int level_id)
|
|
{
|
|
struct game game;
|
|
struct camera *camera = &game.camera;
|
|
struct player *player = &game.player;
|
|
|
|
game.level = level_create(level_id);
|
|
game.t = 0.0;
|
|
game.sections_passed = 0;
|
|
game.debug.footer = false;
|
|
game.debug.invincible = false;
|
|
|
|
camera->set_fov(120.0);
|
|
camera->screen_size = vec2(DWIDTH, DHEIGHT);
|
|
|
|
player->z = 0.0;
|
|
player->vz = 3.0;
|
|
player->platform = 0;
|
|
player->stance = player::Falling;
|
|
player->jump_dir = 0;
|
|
player->jump_t = 0.0;
|
|
player->height = 1.0;
|
|
player->vheight = 0.0;
|
|
player->frame = 0.0;
|
|
player->energy_percent = 0.0;
|
|
|
|
/* Input buffering */
|
|
struct { int left, right, up, shift; } keybuffer = {};
|
|
|
|
/* FPS regulation setup */
|
|
int volatile need_frame = 1;
|
|
int last_frame_us = 0;
|
|
int timer = timer_configure(TIMER_ANY, 33000, GINT_CALL_SET(&need_frame));
|
|
timer_start(timer);
|
|
|
|
camera_track(camera, player);
|
|
log_printf("cylinder quasiradius: %s\n",str(space_cylinder_quasiradius()));
|
|
log_printf("player: at %s\n", str(player->pos()));
|
|
log_printf("camera: at %s, screen distance %s, platform %d, "
|
|
"angle vector %s\n",
|
|
str(camera->pos),
|
|
str(camera->screen_distance()),
|
|
camera->platform,
|
|
str(camera->angle_vector));
|
|
|
|
image_t *effect_bpp = NULL;
|
|
|
|
bool game_run = true;
|
|
while(game_run) {
|
|
while(!need_frame) sleep();
|
|
need_frame = 0;
|
|
|
|
//======= Initial data updates =======//
|
|
|
|
num dt = 1.0 / 30;
|
|
game.t += dt;
|
|
prof_t perf_frame = prof_make();
|
|
prof_enter_norec(perf_frame);
|
|
game.perf.effect_bpp = prof_make();
|
|
|
|
level_update(&game.level, player->z);
|
|
camera_track(camera, player);
|
|
|
|
struct platform *standing_on = game_platform_under_player(&game,
|
|
&game.player);
|
|
|
|
//======= Rendering =======//
|
|
|
|
azrp_perf_clear();
|
|
azrp_clear(game.level.bgcolor);
|
|
|
|
prof_t perf_comp = prof_make();
|
|
prof_enter_norec(perf_comp);
|
|
|
|
for(auto it = game.level.platform_buffer.rbegin();
|
|
it != game.level.platform_buffer.rend();
|
|
++it) {
|
|
int color = render_platform_color(&*it, &game.camera, game.t);
|
|
struct prect p = space_platform_position(&*it);
|
|
|
|
/* Near plane clipping */
|
|
num near = camera->pos.z + camera->near_plane();
|
|
if(p.fl.z <= near) continue;
|
|
if(p.nl.z <= near) p.nl.z = near;
|
|
if(p.nr.z <= near) p.nr.z = near;
|
|
camera_project_prect(&game.camera, &p);
|
|
|
|
render_triangle(&p.nl, &p.fr, &p.fl, color);
|
|
render_triangle(&p.fr, &p.nl, &p.nr, color);
|
|
}
|
|
|
|
/* Render player */
|
|
vec3 player_dot = camera_project_point(&game.camera, player->pos());
|
|
struct anim *player_anim;
|
|
int player_frame;
|
|
player->get_anim(&player_anim, &player_frame);
|
|
render_anim_frame((int)player_dot.x, (int)player_dot.y,
|
|
player_anim, player_frame);
|
|
|
|
if(standing_on && standing_on->type == PLATFORM_BLUE) {
|
|
prof_enter(game.perf.effect_bpp);
|
|
render_effect_bpp(&effect_bpp, game.t);
|
|
azrp_image((int)player_dot.x - effect_bpp->width / 2,
|
|
(int)player_dot.y - effect_bpp->height + 2,
|
|
effect_bpp);
|
|
prof_leave(game.perf.effect_bpp);
|
|
}
|
|
|
|
/* Render energy score */
|
|
char energy_str[32];
|
|
sprintf(energy_str, "%.2f%%", (float)player->energy_percent);
|
|
azrp_rect(DWIDTH-100, 4, 96, 20, AZRP_RECT_DARKEN);
|
|
render_energy_str(DWIDTH-8, 8, DTEXT_RIGHT, energy_str);
|
|
|
|
prof_leave_norec(perf_comp);
|
|
azrp_update();
|
|
|
|
num floor = 0;
|
|
|
|
if(game.debug.footer) {
|
|
drect(0, DHEIGHT-20, DWIDTH-1, DHEIGHT-1, C_WHITE);
|
|
dprint(4, 209, C_BLACK, "render:%4d+%4dus comp:%4dus FPS:%02d",
|
|
prof_time(azrp_perf_render) - prof_time(azrp_perf_r61524),
|
|
prof_time(azrp_perf_r61524),
|
|
prof_time(perf_comp),
|
|
last_frame_us ? 1000000 / last_frame_us : 0);
|
|
r61524_display(gint_vram, DHEIGHT-20, 20, R61524_DMA_WAIT);
|
|
}
|
|
|
|
//======= Input =======//
|
|
|
|
key_event_t ev;
|
|
|
|
while((ev = pollevent()).type != KEYEV_NONE) {
|
|
if(ev.type == KEYEV_UP || ev.type == KEYEV_HOLD)
|
|
continue;
|
|
int key = ev.key;
|
|
|
|
if(key == KEY_EXIT || key == KEY_MENU)
|
|
game_run = false;
|
|
if(key == KEY_OPTN)
|
|
game.debug.footer = !game.debug.footer;
|
|
#if 0
|
|
if(key == KEY_VARS)
|
|
game.debug.invincible = !game.debug.invincible;
|
|
#endif
|
|
|
|
if(key == KEY_LEFT)
|
|
keybuffer.left = 8;
|
|
if(key == KEY_RIGHT)
|
|
keybuffer.right = 8;
|
|
if(key == KEY_UP)
|
|
keybuffer.up = 8;
|
|
if(key == KEY_SHIFT)
|
|
keybuffer.shift = 8;
|
|
}
|
|
|
|
if(!game_run) break;
|
|
|
|
int key = keybuffer.left ? KEY_LEFT :
|
|
keybuffer.right ? KEY_RIGHT :
|
|
keybuffer.up ? KEY_UP :
|
|
keybuffer.shift ? KEY_SHIFT : 0;
|
|
|
|
if(key && !player->airborne()) {
|
|
player->stance = player::Jumping;
|
|
player->jump_dir = (key == KEY_RIGHT) - (key == KEY_LEFT);
|
|
player->jump_t = 0.0;
|
|
player->jump_key = key;
|
|
player->vheight = JUMP_THRUST;
|
|
player->frame = 0.0;
|
|
|
|
keybuffer.left = 0;
|
|
keybuffer.right = 0;
|
|
keybuffer.up = 0;
|
|
keybuffer.shift = 0;
|
|
}
|
|
|
|
if(keybuffer.left > 0)
|
|
keybuffer.left--;
|
|
if(keybuffer.right > 0)
|
|
keybuffer.right--;
|
|
if(keybuffer.up > 0)
|
|
keybuffer.up--;
|
|
if(keybuffer.shift > 0)
|
|
keybuffer.shift--;
|
|
|
|
//======= Simulation =======//
|
|
|
|
num vz = player->vz * (num(1) + player->energy_percent / 200);
|
|
player->z += vz * dt;
|
|
player->height += player->vheight * dt;
|
|
|
|
/* Apply gravity */
|
|
num gravity = 0.0;
|
|
if(player->stance == player::Jumping) {
|
|
gravity = HOVERING_GRAVITY;
|
|
}
|
|
else if(player->stance == player::Falling) {
|
|
gravity = FALLING_GRAVITY;
|
|
}
|
|
player->vheight += gravity * dt;
|
|
floor = game_height_at(&game, player->z, player->platform);
|
|
|
|
if(player->airborne()) {
|
|
player->jump_t += dt;
|
|
|
|
/* Update the current platform number after rotation */
|
|
if(player->jump_t > TIME_ROTATION && player->jump_dir) {
|
|
int p = player->platform + player->jump_dir;
|
|
p = (p + PLATFORM_COUNT) % PLATFORM_COUNT;
|
|
player->platform = p;
|
|
player->jump_dir = 0;
|
|
}
|
|
|
|
/* Switch to falling if the jump key is released */
|
|
if(player->stance == player::Jumping
|
|
&& player->jump_t > TIME_JUMP_THRUST_MIN
|
|
&& !keydown(player->jump_key)) {
|
|
player->stance = player::Falling;
|
|
player->vheight = -1;
|
|
}
|
|
|
|
if((player->height >= floor - STEP_HEIGHT
|
|
&& player->height <= floor)
|
|
|| (game.debug.invincible && player->height < 0)) {
|
|
player->stance = player::Running;
|
|
player->jump_dir = 0;
|
|
player->jump_t = 0.0;
|
|
player->height = floor;
|
|
player->vheight = 0;
|
|
player->jump_key = 0;
|
|
player->frame = 0.0;
|
|
|
|
if(game.debug.invincible && player->height < 0)
|
|
player->height = 0;
|
|
}
|
|
}
|
|
else if(floor < player->height) {
|
|
player->stance = player::Falling;
|
|
player->vheight = 0.0;
|
|
}
|
|
|
|
/* Death condition #1/2: inside a platform (hit it in front) */
|
|
bool death_1 = (player->height >= floor - PLAYER_HEIGHT &&
|
|
player->height < floor - STEP_HEIGHT);
|
|
/* Death condition #2/2: below the kill plane */
|
|
bool death_2 = (player->height < LEVEL_RADIUS - KILL_PLANE_RADIUS);
|
|
|
|
if(!game.debug.invincible && (death_1 || death_2))
|
|
break;
|
|
|
|
if(standing_on && standing_on->type == PLATFORM_BLUE) {
|
|
player->energy_percent += dt * vz * 5.5;
|
|
}
|
|
if(standing_on && standing_on->type == PLATFORM_RED) {
|
|
standing_on->falling = true;
|
|
}
|
|
|
|
for(auto &p: game.level.platform_buffer) {
|
|
if(p.type == PLATFORM_RED && p.falling) {
|
|
p.height -= PLATFORM_FALL_SPEED * dt;
|
|
if(standing_on == &p)
|
|
player->height = p.height;
|
|
}
|
|
}
|
|
|
|
game_advance(&game);
|
|
prof_leave_norec(perf_frame);
|
|
last_frame_us = prof_time(perf_frame);
|
|
}
|
|
|
|
if(effect_bpp)
|
|
image_free(effect_bpp);
|
|
|
|
timer_stop(timer);
|
|
return 0;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int level;
|
|
|
|
__printf_enable_fp();
|
|
prof_init();
|
|
render_init();
|
|
space_init();
|
|
|
|
extern font_t font_boson;
|
|
dfont(&font_boson);
|
|
|
|
azrp_config_scale(1);
|
|
|
|
#if LOG_USB_ENABLE
|
|
dclear(C_WHITE);
|
|
dtext_opt(DWIDTH/2, DHEIGHT/2, C_BLACK, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
|
|
"Waiting for USB log...", -1);
|
|
dupdate();
|
|
log_init(true);
|
|
|
|
azrp_hook_set_prefrag(hook_prefrag);
|
|
dupdate_set_hook(GINT_CALL(hook_dupdate));
|
|
keydev_set_async_filter(keydev_std(), async_event_filter);
|
|
#endif
|
|
|
|
while (1) {
|
|
if (menu_title() != 0) {
|
|
gint_osmenu();
|
|
continue;
|
|
}
|
|
while (menu_select_level(&level) >= 0) {
|
|
play_level(level + 1);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|