275 lines
7.6 KiB
C++
275 lines
7.6 KiB
C++
/* Functions for game-specific mechanics. */
|
|
#include "chaos-drop.h"
|
|
#include <azur/azur.h>
|
|
|
|
#ifdef AZUR_TOOLKIT_GINT
|
|
extern struct level level_1_1, level_1_2, level_1_3;
|
|
extern struct level level_2_1, level_2_2, level_2_3;
|
|
extern struct level level_3_1, level_3_2, level_3_3;
|
|
|
|
static struct level const *ALL_LEVELS[] = {
|
|
&level_1_1, &level_1_2, &level_1_3,
|
|
&level_2_1, &level_2_2, &level_2_3,
|
|
&level_3_1, &level_3_2, &level_3_3,
|
|
};
|
|
#else
|
|
/* TODO: PC/SDL build: Embed levels as in the add-in */
|
|
static struct level const lv_test = {
|
|
.name = "<Test>",
|
|
.finish = num(1024),
|
|
.mirror_count = 1,
|
|
.mirrors = (struct element_mirror[]){
|
|
{ .begin = 256, .end = 768 },
|
|
},
|
|
.plane_count = 2,
|
|
.planes = (struct element_plane[]){
|
|
{ .y = 384, .shape = 0b01010'10101'01010'10101'01010,
|
|
.type = ELEMENT_PLANE_DAMAGE, .data = 0 },
|
|
{ .y = 384+64, .shape = 0b11111'10001'10001'10001'11111,
|
|
.type = ELEMENT_PLANE_DAMAGE, .data = 0 },
|
|
},
|
|
.text_count = 1,
|
|
.texts = (struct element_text[]){
|
|
{ .begin = 32, .end = 256,
|
|
.str = "Red stuff bad!\nDon't touch the red stuff!", },
|
|
},
|
|
};
|
|
static struct level const *ALL_LEVELS[] = { &lv_test };
|
|
#endif
|
|
|
|
void camera_set_fov(struct camera *camera, float fov_degrees)
|
|
{
|
|
camera->fov = num(fov_degrees);
|
|
float fov_radians = fov_degrees * 3.14159 / 180;
|
|
/* Use FOV as the horizontal viewing angle */
|
|
float sd = (VWIDTH * HALF_SCREEN_HEIGHT / VHEIGHT) / tanf(fov_radians / 2);
|
|
/* The screen is at such a distance that 16 units is half the height. We
|
|
don't care where it is placed as we'll always send rays from the camera
|
|
itself. This is to ensure good ranges for fixed point values */
|
|
camera->screen_distance = num(sd);
|
|
}
|
|
|
|
bool camera_rolling(struct camera const *camera)
|
|
{
|
|
float target_roll = camera->roll_quadrant * M_PI / 2;
|
|
float distance = fabsf(target_roll - camera->roll);
|
|
|
|
/* Wrap-around case */
|
|
if(camera->roll > 6.0 && camera->roll_quadrant == 0)
|
|
distance = fabsf(target_roll - (camera->roll - 2*M_PI));
|
|
|
|
return distance > 0.3;
|
|
}
|
|
|
|
void camera_roll(struct camera *camera, float snap_factor)
|
|
{
|
|
float target_roll = camera->roll_quadrant * M_PI / 2;
|
|
float distance_nowrap = target_roll - camera->roll;
|
|
float distance_wrap_up = (target_roll + 2*M_PI) - camera->roll;
|
|
float distance_wrap_down = (target_roll - 2*M_PI) - camera->roll;
|
|
|
|
float mv;
|
|
|
|
/* Snap with closest direction */
|
|
if(fabsf(distance_nowrap) < fabsf(distance_wrap_up)) {
|
|
if(fabsf(distance_nowrap) < fabsf(distance_wrap_down))
|
|
mv = distance_nowrap * snap_factor;
|
|
else
|
|
mv = distance_wrap_down * snap_factor;
|
|
}
|
|
else {
|
|
if(fabsf(distance_wrap_down) < fabsf(distance_wrap_down))
|
|
mv = distance_wrap_down * snap_factor;
|
|
else
|
|
mv = distance_wrap_up * snap_factor;
|
|
}
|
|
|
|
camera->roll += mv;
|
|
if(camera->roll < 0)
|
|
camera->roll += 2 * M_PI;
|
|
else if(camera->roll > 2 * M_PI)
|
|
camera->roll -= 2 * M_PI;
|
|
|
|
/* Also rotate the player along with it */
|
|
float cos_mv, sin_mv;
|
|
sincosf(-mv, &sin_mv, &cos_mv);
|
|
num c = cos_mv, s = sin_mv;
|
|
num nx = camera->pos.x * c + camera->pos.z * s;
|
|
num nz = -camera->pos.x * s + camera->pos.z * c;
|
|
camera->pos.x = nx;
|
|
camera->pos.z = nz;
|
|
}
|
|
|
|
mat3 camera_camera2world_matrix(struct camera const *camera)
|
|
{
|
|
num cos_r = camera->cos_r;
|
|
num sin_r = camera->sin_r;
|
|
num cos_p = camera->cos_p;
|
|
num sin_p = camera->sin_p;
|
|
num cos_y = camera->cos_y;
|
|
num sin_y = camera->sin_y;
|
|
|
|
mat3 m_anti_roll = {
|
|
cos_r, 0, -sin_r,
|
|
0, 1, 0,
|
|
+sin_r, 0, cos_r,
|
|
};
|
|
mat3 m_anti_pitch = {
|
|
1, 0, 0,
|
|
0, cos_p, -sin_p,
|
|
0, +sin_p, cos_p,
|
|
};
|
|
mat3 m_anti_yaw = {
|
|
cos_y, -sin_y, 0,
|
|
+sin_y, cos_y, 0,
|
|
0, 0, 1,
|
|
};
|
|
return m_anti_yaw * (m_anti_pitch * m_anti_roll);
|
|
}
|
|
|
|
void camera_update_angles(struct camera *camera)
|
|
{
|
|
float c, s;
|
|
|
|
sincosf(camera->roll, &s, &c);
|
|
camera->sin_r = s;
|
|
camera->cos_r = c;
|
|
|
|
sincosf(camera->pitch, &s, &c);
|
|
camera->sin_p = s;
|
|
camera->cos_p = c;
|
|
|
|
sincosf(camera->yaw, &s, &c);
|
|
camera->sin_y = s;
|
|
camera->cos_y = c;
|
|
|
|
mat3 c2w = camera_camera2world_matrix(camera);
|
|
camera->forward = c2w * vec3(0, 1, 0);
|
|
camera->right = c2w * vec3(1, 0, 0);
|
|
camera->up = c2w * vec3(0, 0, 1);
|
|
}
|
|
|
|
int level_count(void)
|
|
{
|
|
return sizeof ALL_LEVELS / sizeof ALL_LEVELS[0];
|
|
}
|
|
|
|
const struct level *level_get(int id)
|
|
{
|
|
return (id >= 0 && id < level_count()) ? ALL_LEVELS[id] : NULL;
|
|
}
|
|
|
|
bool level_plane_collides(num16 x, num16 z, uint32_t shape)
|
|
{
|
|
int cx = ((x + num16(WORLD_SIZE)) * num16(5.0 / 2 / WORLD_SIZE)).ifloor();
|
|
int cz = ((z + num16(WORLD_SIZE)) * num16(5.0 / 2 / WORLD_SIZE)).ifloor();
|
|
|
|
return (unsigned)cx < 5 && (unsigned)cz < 5
|
|
&& ((shape >> (5*cz + 4-cx)) & 1);
|
|
}
|
|
|
|
bool game_in_menu(struct game const *g)
|
|
{
|
|
return !g->level;
|
|
}
|
|
|
|
static void game_reset_level_data(struct game *g)
|
|
{
|
|
g->level = NULL;
|
|
g->level_id = -1;
|
|
g->mirror_index = 0;
|
|
g->plane_index = 0;
|
|
g->text_index = 0;
|
|
g->vfx_index = 0;
|
|
g->mirror = NULL;
|
|
g->plane = NULL;
|
|
g->text = NULL;
|
|
g->vfx = NULL;
|
|
g->depth = 0;
|
|
g->plane_collision[0] = num(0x7ff);
|
|
g->plane_collision[1] = num(0x7ff);
|
|
}
|
|
|
|
void game_load_menu(struct game *g)
|
|
{
|
|
game_reset_level_data(g);
|
|
g->level = NULL;
|
|
g->level_id = -1;
|
|
}
|
|
|
|
void game_load_level(struct game *g, int index, int blank_frames)
|
|
{
|
|
struct level const *level = level_get(index);
|
|
if(!level)
|
|
return;
|
|
|
|
game_reset_level_data(g);
|
|
g->level = level;
|
|
g->level_id = index;
|
|
g->blank_frames = blank_frames;
|
|
}
|
|
|
|
void game_restart_level(struct game *g, int blank_frames)
|
|
{
|
|
if(!game_in_menu(g))
|
|
game_load_level(g, g->level_id, blank_frames);
|
|
}
|
|
|
|
void game_prev_level_or_menu(struct game *g)
|
|
{
|
|
if(g->level_id > 0)
|
|
game_load_level(g, g->level_id - 1, 0);
|
|
else
|
|
game_load_menu(g);
|
|
}
|
|
|
|
void game_next_level_or_menu(struct game *g)
|
|
{
|
|
if(g->level_id >= 0 && g->level_id + 1 < level_count())
|
|
game_load_level(g, g->level_id + 1, 0);
|
|
else
|
|
game_load_menu(g);
|
|
}
|
|
|
|
const struct element_plane *game_plane_collision(struct game const *g)
|
|
{
|
|
return g->plane && g->depth > g->plane->y - num(4) ? g->plane : NULL;
|
|
}
|
|
|
|
void game_advance_in_level(struct game *g)
|
|
{
|
|
struct level const *level = g->level;
|
|
|
|
/* Remove old elements */
|
|
|
|
if(g->mirror && g->depth > g->mirror->end)
|
|
g->mirror = NULL;
|
|
if(game_plane_collision(g))
|
|
g->plane = NULL;
|
|
if(g->text && g->depth > g->text->end)
|
|
g->text = NULL;
|
|
if(g->vfx && g->depth > g->vfx->end)
|
|
g->vfx = NULL;
|
|
|
|
/* Load incoming elements */
|
|
|
|
if(!g->mirror && g->mirror_index < level->mirror_count
|
|
&& level->mirrors[g->mirror_index].begin < g->depth + num(64)) {
|
|
g->mirror = &level->mirrors[g->mirror_index++];
|
|
}
|
|
if(!g->plane && g->plane_index < level->plane_count
|
|
&& level->planes[g->plane_index].y < g->depth + num(64)) {
|
|
g->plane = &level->planes[g->plane_index++];
|
|
}
|
|
// FIXME: Should've loaded just in time, not 64 in advance...
|
|
// (but now all levels are calibrated for that)
|
|
if(!g->text && g->text_index < level->text_count
|
|
&& level->texts[g->text_index].begin < g->depth + num(64)) {
|
|
g->text = &level->texts[g->text_index++];
|
|
}
|
|
if(!g->vfx && g->vfx_index < level->vfx_count
|
|
&& level->vfxs[g->vfx_index].begin < g->depth) {
|
|
g->vfx = &level->vfxs[g->vfx_index++];
|
|
}
|
|
}
|