425 lines
13 KiB
C
425 lines
13 KiB
C
#include <gint/display.h>
|
|
#include <gint/keyboard.h>
|
|
#include <gint/timer.h>
|
|
#include <gint/gint.h>
|
|
#include <gint/cpu.h>
|
|
#include <gint/dma.h>
|
|
#include <gint/drivers/r61524.h>
|
|
|
|
#include <fxlibc/printf.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "duet.h"
|
|
|
|
/* Game state */
|
|
typedef enum {
|
|
State_Playing,
|
|
State_Dead,
|
|
State_Rewind,
|
|
State_Transition,
|
|
State_EpisodeTransition,
|
|
State_Finale,
|
|
} state_t;
|
|
|
|
/* All levels, classified by episode */
|
|
episode_t episodes[] = {
|
|
{
|
|
.name = "ignorance",
|
|
.level_count = 5,
|
|
.levels = (level_t *[]){
|
|
&level1_1, &level1_2, &level1_3, &level1_4, &level1_5
|
|
},
|
|
},
|
|
{
|
|
.name = "denial",
|
|
.level_count = 6,
|
|
.levels = (level_t *[]){
|
|
&level2_1, &level2_2, &level2_3, &level2_4, &level2_5, &level2_6
|
|
},
|
|
},
|
|
{
|
|
.name = "anger",
|
|
.level_count = 5,
|
|
.levels = (level_t *[]){
|
|
&level3_1, &level3_2, &level3_3, &level3_4, &level3_5
|
|
},
|
|
},
|
|
{
|
|
.name = "bargaining",
|
|
.level_count = 6,
|
|
.levels = (level_t *[]){
|
|
&level4_1, &level4_2, &level4_3, &level4_4, &level4_5, &level4_6
|
|
},
|
|
},
|
|
{
|
|
.name = "guilt",
|
|
.level_count = 6,
|
|
.levels = (level_t *[]) {
|
|
&level5_1, &level5_2, &level5_3, &level5_4, &level5_5, &level5_6
|
|
},
|
|
},
|
|
{
|
|
.name = "depression",
|
|
.level_count = 6,
|
|
.levels = (level_t *[]){
|
|
&level6_1, &level6_2, &level6_3, &level6_4, &level6_5, &level6_6
|
|
},
|
|
},
|
|
{
|
|
.name = "hope",
|
|
.level_count = 6,
|
|
.levels = (level_t *[]){
|
|
&level7_1, &level7_2, &level7_3, &level7_4, &level7_5, &level7_6
|
|
},
|
|
},
|
|
{
|
|
.name = "acceptance",
|
|
.level_count = 9,
|
|
.levels = (level_t *[]){
|
|
&level8_1, &level8_2, &level8_3, &level8_4, &level8_5, &level8_6,
|
|
&level8_7, &level8_8, &level8_9
|
|
},
|
|
},
|
|
};
|
|
int episode_count = sizeof(episodes) / sizeof(episodes[0]);
|
|
|
|
void load_level(game_t *game, level_t const *lv)
|
|
{
|
|
game->level = lv;
|
|
game->time = -11.0;
|
|
|
|
game->rects = realloc(game->rects, lv->block_count * sizeof *game->rects);
|
|
if(!game->rects)
|
|
exit(1);
|
|
for(int i = 0; i < lv->block_count; i++) {
|
|
game->rects[i].meta = &lv->blocks[i];
|
|
rect_load(&game->rects[i], &lv->blocks[i]);
|
|
}
|
|
game->rect_count = lv->block_count;
|
|
}
|
|
|
|
int strcount(char const *str, int c)
|
|
{
|
|
int count = 0;
|
|
for(int i = 0; str[i]; i++)
|
|
count += (str[i] == c);
|
|
return count;
|
|
}
|
|
|
|
/* Round player rotation to closest multiple of M_PI */
|
|
float rpr(float rota)
|
|
{
|
|
if(fabs(rota) <= M_PI_2)
|
|
return 0;
|
|
return (rota < 0) ? -M_PI : M_PI;
|
|
}
|
|
|
|
/* Forced player rotation to go back to an even position */
|
|
float fpr(float rota, float duration)
|
|
{
|
|
return (rpr(rota) - rota) / duration;
|
|
}
|
|
|
|
void game_loop(int current_episode, int current_level)
|
|
{
|
|
game_t game;
|
|
memset(&game, 0x00, sizeof game);
|
|
|
|
load_level(&game, episodes[current_episode].levels[current_level]);
|
|
|
|
state_t state = State_Playing;
|
|
|
|
/* Direction of time (with a factor regulating game speed) */
|
|
float const time_direction_forward = 2.7;
|
|
float const time_direction_rewind = -25;
|
|
/* Level time to start (negative to leave time before blocks arrive) */
|
|
float const LEVEL_START = -14.0;
|
|
/* Level time until which message is shown */
|
|
float const MESSAGE_END = -5.0;
|
|
/* Duration of death pause (seconds) */
|
|
float const DEATH_PAUSE_DURATION = 0.7;
|
|
/* Level time to restarting playing at after rewind */
|
|
float const REWIND_END = -5.0;
|
|
/* Duration of level transitions (seconds) */
|
|
float const TRANSITION_DURATION = 1.0;
|
|
/* Duration of episode transitions (seconds)
|
|
Warning: overlaps with pre-level, will lock player if too long x_x */
|
|
float const EPISODE_TRANSITION_DURATION = 3.0;
|
|
|
|
volatile int need_frame = 1;
|
|
int timer = timer_configure(TIMER_ANY, 33000, GINT_CALL_SET(&need_frame));
|
|
if(timer >= 0) timer_start(timer);
|
|
/* Frame duration in real time */
|
|
float const FRAME_DURATION = 1.0 / 30;
|
|
|
|
if(current_level == 0) {
|
|
state = State_EpisodeTransition;
|
|
game.time_episode_transition = 0;
|
|
game.time = LEVEL_START;
|
|
}
|
|
|
|
while (1) {
|
|
level_t const *lv = game.level;
|
|
|
|
while (need_frame == 0) sleep();
|
|
need_frame = 0;
|
|
|
|
/* Time management */
|
|
|
|
float time_direction = time_direction_forward;
|
|
if(state == State_Rewind)
|
|
time_direction = time_direction_rewind;
|
|
if(state == State_Playing)
|
|
time_direction *= lv->speed_factor;
|
|
|
|
if(state == State_Dead) {
|
|
game.time_dead += FRAME_DURATION;
|
|
if(game.time_dead >= DEATH_PAUSE_DURATION) {
|
|
game.time_dead = 0;
|
|
game.forced_player_rota = fpr(game.player_rota,
|
|
REWIND_END - game.time);
|
|
state = State_Rewind;
|
|
}
|
|
continue; /* Pause effect */
|
|
}
|
|
if(state == State_Transition) {
|
|
time_direction = 1.0;
|
|
game.time_transition += FRAME_DURATION;
|
|
if(game.time_transition >= TRANSITION_DURATION) {
|
|
game.time_transition = 0;
|
|
game.forced_player_rota = 0;
|
|
game.player_rota = rpr(game.player_rota);
|
|
state = State_Playing;
|
|
}
|
|
}
|
|
if(state == State_EpisodeTransition) {
|
|
time_direction = 1.0;
|
|
game.time_episode_transition += FRAME_DURATION;
|
|
if(game.time_episode_transition >= TRANSITION_DURATION) {
|
|
game.forced_player_rota = 0;
|
|
game.player_rota = rpr(game.player_rota);
|
|
}
|
|
if(game.time_episode_transition >= EPISODE_TRANSITION_DURATION) {
|
|
game.time_episode_transition = 0;
|
|
state = State_Playing;
|
|
}
|
|
}
|
|
|
|
float dt = FRAME_DURATION * time_direction;
|
|
game.time += dt;
|
|
|
|
/* Finale */
|
|
if(state == State_Finale) {
|
|
game.time_finale += dt;
|
|
if(game.time_finale >= 0.5) {
|
|
float t = game.time_finale - 0.5;
|
|
game.player_rota -= 1.57 * t / 3 * dt;
|
|
}
|
|
}
|
|
|
|
/* Input analysis */
|
|
|
|
bool rotate_left=false, rotate_right=false;
|
|
|
|
key_event_t e;
|
|
bool stop_playing=false;
|
|
|
|
while ((e = pollevent()).type != KEYEV_NONE) {
|
|
if (e.type == KEYEV_DOWN && e.key == KEY_MENU)
|
|
gint_osmenu();
|
|
if (e.type == KEYEV_DOWN && e.key == KEY_EXIT)
|
|
stop_playing = true;
|
|
}
|
|
if (keydown(KEY_7)) {
|
|
rotate_left = true;
|
|
}
|
|
if (keydown(KEY_0)) {
|
|
rotate_right = true;
|
|
}
|
|
if ((keydown(KEY_0) || keydown(KEY_7)) && state == State_Finale &&
|
|
game.time_finale >= 16.0) {
|
|
stop_playing = true;
|
|
}
|
|
if (stop_playing)
|
|
break;
|
|
|
|
/* Level transitions */
|
|
|
|
// End of level
|
|
if(state == State_Playing &&
|
|
game.time >= lv->blocks[lv->block_count-1].time + 5) {
|
|
bool changed_episode = false;
|
|
|
|
current_level++;
|
|
if(current_level >= episodes[current_episode].level_count) {
|
|
current_episode++;
|
|
current_level = 0;
|
|
changed_episode = true;
|
|
}
|
|
if(current_episode >= episode_count) {
|
|
state = State_Finale;
|
|
continue;
|
|
}
|
|
|
|
load_level(&game, episodes[current_episode].levels[current_level]);
|
|
|
|
if(changed_episode) {
|
|
state = State_EpisodeTransition;
|
|
game.time_episode_transition = 0;
|
|
game.forced_player_rota = fpr(game.player_rota,
|
|
TRANSITION_DURATION);
|
|
game.time = LEVEL_START;
|
|
continue;
|
|
}
|
|
else {
|
|
state = State_Transition;
|
|
game.time_transition = 0;
|
|
game.forced_player_rota = fpr(game.player_rota,
|
|
TRANSITION_DURATION);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// End of rewind
|
|
if(state == State_Rewind && game.time <= REWIND_END) {
|
|
game.time = REWIND_END;
|
|
game.forced_player_rota = 0;
|
|
game.player_rota = rpr(game.player_rota);
|
|
state = State_Playing;
|
|
continue;
|
|
}
|
|
|
|
/* Physics */
|
|
|
|
if(state == State_Playing && player_collision(&game)) {
|
|
state = State_Dead;
|
|
game.time_dead = 0;
|
|
continue;
|
|
}
|
|
|
|
for(int i = 0; i < game.rect_count; i++) {
|
|
rect_physics(&game.rects[i], game.rects[i].meta, game.time);
|
|
}
|
|
|
|
if(game.forced_player_rota)
|
|
game.player_rota += game.forced_player_rota * dt;
|
|
else {
|
|
if(state == State_Playing && rotate_left)
|
|
game.player_rota += 1.57 * dt;
|
|
if(state == State_Playing && rotate_right)
|
|
game.player_rota -= 1.57 * dt;
|
|
}
|
|
if(game.player_rota > M_PI)
|
|
game.player_rota -= 2 * M_PI;
|
|
if(game.player_rota < -M_PI)
|
|
game.player_rota += 2 * M_PI;
|
|
|
|
/* Rendering */
|
|
|
|
dclear(C_BLACK);
|
|
|
|
// Cool animated background
|
|
int bg_color = 4;
|
|
if(state == State_Finale)
|
|
bg_color = (game.time_finale >= 2) ? 0 : 4 - 2 * game.time_finale;
|
|
|
|
for(int i = 0; i < 10; i++) {
|
|
int x=DWIDTH+2-40*i-20, y=20;
|
|
|
|
rect_t rect = { .x=x, .y=y, .w=20, .h=20, .r=1.0*game.time,
|
|
.opacity=256 };
|
|
drectoid(&rect, 0, C_RGB(bg_color, bg_color, bg_color));
|
|
}
|
|
for(int j = 1; j < 6; j++) {
|
|
int lines = (j == 5) ? 24 : 40;
|
|
dma_memcpy(gint_vram + 396*(40*j), gint_vram, 396*2*lines);
|
|
}
|
|
|
|
if(state == State_Playing && game.time < MESSAGE_END && lv->message) {
|
|
int x = DWIDTH/2 + 8 * (strcount(lv->message, '\n') + 1);
|
|
|
|
/* Split at newlines */
|
|
char const *str = lv->message;
|
|
while(*str) {
|
|
char const *end = strchrnul(str, '\n');
|
|
duet_text_opt(x, DHEIGHT/2, C_WHITE, C_NONE, DTEXT_CENTER,
|
|
DTEXT_MIDDLE, str, end - str);
|
|
x -= 16;
|
|
str = end + (*end != 0);
|
|
}
|
|
}
|
|
|
|
if(state == State_EpisodeTransition) {
|
|
duet_text_opt(DWIDTH/2, DHEIGHT/2, C_WHITE, C_NONE, DTEXT_CENTER,
|
|
DTEXT_MIDDLE, episodes[current_episode].name, -1);
|
|
}
|
|
|
|
if(state == State_Finale && game.time_finale >= 11.0) {
|
|
float t = game.time_finale - 11.0;
|
|
float rt = fminf(1.0, t / 4.0);
|
|
float opacity = fminf(1.0, t / 2.0);
|
|
render_glow(DWIDTH/2, DHEIGHT/2, 24*rt, 20*rt, C_RGB(15,12,13),
|
|
C_RGB(10,5,8), -t / 2.0, opacity);
|
|
}
|
|
|
|
float opacity = 1.0;
|
|
int x = PLAYER_X;
|
|
int r = PLAYER_R;
|
|
|
|
if(state == State_Finale) {
|
|
opacity = fmaxf(1 - game.time_finale / 2, 0);
|
|
/* Interpolate between x1 and x2 in 10.0 seconds along (1-x)² */
|
|
int x1 = PLAYER_X, x2 = DWIDTH / 2;
|
|
float t = fminf(game.time_finale / 10.0, 1.0);
|
|
t = 1 - (1-t)*(1-t);
|
|
x = (1 - t) * x1 + t * x2;
|
|
/* Interpolate radius between PLAYER_R and 0 in 10.0 seconds */
|
|
t = fmaxf(0.0, fminf(1.0, (game.time_finale - 2.0) / 10.0));
|
|
r = PLAYER_R * (1 - t);
|
|
}
|
|
render_player(x, DHEIGHT/2, game.player_rota, r, opacity);
|
|
|
|
/* Radial fade out */
|
|
if(state == State_Finale && game.time_finale >= 12.0) {
|
|
float t = fminf(1.0, (game.time_finale - 11.0) / 4.0);
|
|
int r1 = PLAYER_SIZE + (12 - PLAYER_SIZE) * t;
|
|
int r2 = PLAYER_SIZE + (24 - PLAYER_SIZE) * t;
|
|
radial_fadeout(DWIDTH/2, DHEIGHT/2, r1, r2, C_BLACK);
|
|
}
|
|
/* Final text */
|
|
if(state == State_Finale && game.time_finale >= 14.0) {
|
|
float t = fminf(1.0, (game.time_finale - 14.0) / 2.0);
|
|
int c = 31*t;
|
|
c = C_RGB(c, c, c);
|
|
duet_text_opt(DWIDTH/2 - 30, DHEIGHT/2, c, C_NONE, DTEXT_CENTER,
|
|
DTEXT_MIDDLE, "I am here", -1);
|
|
}
|
|
|
|
float extra_size = 1 + sinf(6 * game.time);
|
|
for(int i = 0; i < game.rect_count; i++)
|
|
drectoid(&game.rects[i], extra_size, C_WHITE);
|
|
dupdate();
|
|
}
|
|
|
|
timer_stop(timer);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
__printf_enable_fp();
|
|
|
|
/* Azur trickz for less tearing */
|
|
r61524_set(0x010, 0x0010);
|
|
|
|
int current_episode = 0;
|
|
int current_level = 0;
|
|
|
|
while(1) {
|
|
main_menu(¤t_episode, ¤t_level);
|
|
game_loop(current_episode, current_level);
|
|
}
|
|
|
|
return 1;
|
|
}
|