Whitespace: change line endings to LF everywhere

This commit is contained in:
Dominic Szablewski 2023-09-04 17:34:29 +02:00
parent a829166082
commit 0594285013
26 changed files with 6344 additions and 6344 deletions

View file

@ -1,174 +1,174 @@
#include "../mem.h"
#include "../utils.h"
#include "../types.h"
#include "../render.h"
#include "../system.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "droid.h"
#include "camera.h"
void camera_init(camera_t *camera, section_t *section) {
camera->section = section;
for (int i = 0; i < 10; i++) {
camera->section = camera->section->next;
}
camera->position = camera->section->center;
camera->velocity = vec3(0, 0, 0);
camera->angle = vec3(0, 0, 0);
camera->angular_velocity = vec3(0, 0, 0);
camera->has_initial_section = false;
}
vec3_t camera_forward(camera_t *camera) {
float sx = sin(camera->angle.x);
float cx = cos(camera->angle.x);
float sy = sin(camera->angle.y);
float cy = cos(camera->angle.y);
return vec3(-(sy * cx), -sx, (cy * cx));
}
void camera_update(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->last_position = camera->position;
(camera->update_func)(camera, ship, droid);
camera->real_velocity = vec3_mulf(vec3_sub(camera->position, camera->last_position), 1.0/system_tick());
}
void camera_update_race_external(camera_t *camera, ship_t *ship, droid_t *droid) {
vec3_t pos = vec3_sub(ship->position, vec3_mulf(ship->dir_forward, 1024));
pos.y -= 200;
camera->section = track_nearest_section(pos, camera->section, NULL);
section_t *next = camera->section->next;
vec3_t target = vec3_project_to_ray(pos, next->center, camera->section->center);
vec3_t diff_from_center = vec3_sub(pos, target);
vec3_t acc = diff_from_center;
acc.y += vec3_len(diff_from_center) * 0.5;
camera->velocity = vec3_sub(camera->velocity, vec3_mulf(acc, 0.015625 * 30 * system_tick()));
camera->velocity = vec3_sub(camera->velocity, vec3_mulf(camera->velocity, 0.125 * 30 * system_tick()));
pos = vec3_add(pos, camera->velocity);
camera->position = pos;
camera->angle = vec3(ship->angle.x, ship->angle.y, 0);
}
void camera_update_race_internal(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->section = ship->section;
camera->position = ship_cockpit(ship);
camera->angle = vec3(ship->angle.x, ship->angle.y, ship->angle.z);
}
void camera_update_race_intro(camera_t *camera, ship_t *ship, droid_t *droid) {
// Set to final position
vec3_t pos = vec3_sub(ship->position, vec3_mulf(ship->dir_forward, 0.25 * 4096));
pos.x += sin(( (ship->update_timer - UPDATE_TIME_RACE_VIEW) * 30 * 3.0 * M_PI * 2) / 4096.0) * 4096;
pos.y -= (2 * (ship->update_timer - UPDATE_TIME_RACE_VIEW) * 30) + 200;
pos.z += sin(( (ship->update_timer - UPDATE_TIME_RACE_VIEW) * 30 * 3.0 * M_PI * 2) / 4096.0) * 4096;
if (!camera->has_initial_section) {
camera->section = ship->section;
camera->has_initial_section = true;
}
else {
camera->section = track_nearest_section(pos, camera->section, NULL);
}
camera->position = pos;
camera->angle.z = 0;
camera->angle.x = ship->angle.x * 0.5;
vec3_t target = vec3_sub(ship->position, pos);
camera->angle.y = -atan2(target.x, target.z);
if (ship->update_timer <= UPDATE_TIME_RACE_VIEW) {
flags_add(ship->flags, SHIP_VIEW_INTERNAL);
camera->update_func = camera_update_race_internal;
}
}
void camera_update_attract_circle(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->update_timer -= system_tick();
if (camera->update_timer <= 0) {
camera->update_func = camera_update_attract_random;
}
// FIXME: not exactly sure what I'm doing here. The PSX version behaves
// differently.
camera->section = ship->section;
camera->position.x = ship->position.x + sin(ship->angle.y) * 512;
camera->position.y = ship->position.y + ((ship->angle.x * 512 / (M_PI * 2)) - 200);
camera->position.z = ship->position.z - cos(ship->angle.y) * 512;
camera->position.x += sin(camera->update_timer * 0.25) * 512;
camera->position.y -= 400;
camera->position.z += cos(camera->update_timer * 0.25) * 512;
camera->position = vec3_sub(camera->position, vec3_mulf(ship->dir_up, 256));
vec3_t target = vec3_sub(ship->position, camera->position);
float height = sqrt(target.x * target.x + target.z * target.z);
camera->angle.x = -atan2(target.y, height);
camera->angle.y = -atan2(target.x, target.z);
}
void camera_update_rescue(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->position = vec3_add(camera->section->center, vec3(300, -1500, 300));
vec3_t target = vec3_sub(droid->position, camera->position);
float height = sqrt(target.x * target.x + target.z * target.z);
camera->angle.x = -atan2(target.y, height);
camera->angle.y = -atan2(target.x, target.z);
}
void camera_update_attract_internal(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->update_timer -= system_tick();
if (camera->update_timer <= 0) {
camera->update_func = camera_update_attract_random;
}
camera->section = ship->section;
camera->position = ship_cockpit(ship);
camera->angle = vec3(ship->angle.x, ship->angle.y, 0); // No roll
}
void camera_update_static_follow(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->update_timer -= system_tick();
if (camera->update_timer <= 0) {
camera->update_func = camera_update_attract_random;
}
vec3_t target = vec3_sub(ship->position, camera->position);
float height = sqrt(target.x * target.x + target.z * target.z);
camera->angle.x = -atan2(target.y, height);
camera->angle.y = -atan2(target.x, target.z);
}
void camera_update_attract_random(camera_t *camera, ship_t *ship, droid_t *droid) {
flags_rm(ship->flags, SHIP_VIEW_INTERNAL);
if (rand() % 2) {
camera->update_func = camera_update_attract_circle;
camera->update_timer = 5;
}
else {
camera->update_func = camera_update_static_follow;
camera->update_timer = 5;
section_t *section = ship->section->next;
for (int i = 0; i < 10; i++) {
section = section->next;
}
camera->section = section;
camera->position = section->center;
camera->position.y -= 500;
}
(camera->update_func)(camera, ship, droid);
}
#include "../mem.h"
#include "../utils.h"
#include "../types.h"
#include "../render.h"
#include "../system.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "droid.h"
#include "camera.h"
void camera_init(camera_t *camera, section_t *section) {
camera->section = section;
for (int i = 0; i < 10; i++) {
camera->section = camera->section->next;
}
camera->position = camera->section->center;
camera->velocity = vec3(0, 0, 0);
camera->angle = vec3(0, 0, 0);
camera->angular_velocity = vec3(0, 0, 0);
camera->has_initial_section = false;
}
vec3_t camera_forward(camera_t *camera) {
float sx = sin(camera->angle.x);
float cx = cos(camera->angle.x);
float sy = sin(camera->angle.y);
float cy = cos(camera->angle.y);
return vec3(-(sy * cx), -sx, (cy * cx));
}
void camera_update(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->last_position = camera->position;
(camera->update_func)(camera, ship, droid);
camera->real_velocity = vec3_mulf(vec3_sub(camera->position, camera->last_position), 1.0/system_tick());
}
void camera_update_race_external(camera_t *camera, ship_t *ship, droid_t *droid) {
vec3_t pos = vec3_sub(ship->position, vec3_mulf(ship->dir_forward, 1024));
pos.y -= 200;
camera->section = track_nearest_section(pos, camera->section, NULL);
section_t *next = camera->section->next;
vec3_t target = vec3_project_to_ray(pos, next->center, camera->section->center);
vec3_t diff_from_center = vec3_sub(pos, target);
vec3_t acc = diff_from_center;
acc.y += vec3_len(diff_from_center) * 0.5;
camera->velocity = vec3_sub(camera->velocity, vec3_mulf(acc, 0.015625 * 30 * system_tick()));
camera->velocity = vec3_sub(camera->velocity, vec3_mulf(camera->velocity, 0.125 * 30 * system_tick()));
pos = vec3_add(pos, camera->velocity);
camera->position = pos;
camera->angle = vec3(ship->angle.x, ship->angle.y, 0);
}
void camera_update_race_internal(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->section = ship->section;
camera->position = ship_cockpit(ship);
camera->angle = vec3(ship->angle.x, ship->angle.y, ship->angle.z);
}
void camera_update_race_intro(camera_t *camera, ship_t *ship, droid_t *droid) {
// Set to final position
vec3_t pos = vec3_sub(ship->position, vec3_mulf(ship->dir_forward, 0.25 * 4096));
pos.x += sin(( (ship->update_timer - UPDATE_TIME_RACE_VIEW) * 30 * 3.0 * M_PI * 2) / 4096.0) * 4096;
pos.y -= (2 * (ship->update_timer - UPDATE_TIME_RACE_VIEW) * 30) + 200;
pos.z += sin(( (ship->update_timer - UPDATE_TIME_RACE_VIEW) * 30 * 3.0 * M_PI * 2) / 4096.0) * 4096;
if (!camera->has_initial_section) {
camera->section = ship->section;
camera->has_initial_section = true;
}
else {
camera->section = track_nearest_section(pos, camera->section, NULL);
}
camera->position = pos;
camera->angle.z = 0;
camera->angle.x = ship->angle.x * 0.5;
vec3_t target = vec3_sub(ship->position, pos);
camera->angle.y = -atan2(target.x, target.z);
if (ship->update_timer <= UPDATE_TIME_RACE_VIEW) {
flags_add(ship->flags, SHIP_VIEW_INTERNAL);
camera->update_func = camera_update_race_internal;
}
}
void camera_update_attract_circle(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->update_timer -= system_tick();
if (camera->update_timer <= 0) {
camera->update_func = camera_update_attract_random;
}
// FIXME: not exactly sure what I'm doing here. The PSX version behaves
// differently.
camera->section = ship->section;
camera->position.x = ship->position.x + sin(ship->angle.y) * 512;
camera->position.y = ship->position.y + ((ship->angle.x * 512 / (M_PI * 2)) - 200);
camera->position.z = ship->position.z - cos(ship->angle.y) * 512;
camera->position.x += sin(camera->update_timer * 0.25) * 512;
camera->position.y -= 400;
camera->position.z += cos(camera->update_timer * 0.25) * 512;
camera->position = vec3_sub(camera->position, vec3_mulf(ship->dir_up, 256));
vec3_t target = vec3_sub(ship->position, camera->position);
float height = sqrt(target.x * target.x + target.z * target.z);
camera->angle.x = -atan2(target.y, height);
camera->angle.y = -atan2(target.x, target.z);
}
void camera_update_rescue(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->position = vec3_add(camera->section->center, vec3(300, -1500, 300));
vec3_t target = vec3_sub(droid->position, camera->position);
float height = sqrt(target.x * target.x + target.z * target.z);
camera->angle.x = -atan2(target.y, height);
camera->angle.y = -atan2(target.x, target.z);
}
void camera_update_attract_internal(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->update_timer -= system_tick();
if (camera->update_timer <= 0) {
camera->update_func = camera_update_attract_random;
}
camera->section = ship->section;
camera->position = ship_cockpit(ship);
camera->angle = vec3(ship->angle.x, ship->angle.y, 0); // No roll
}
void camera_update_static_follow(camera_t *camera, ship_t *ship, droid_t *droid) {
camera->update_timer -= system_tick();
if (camera->update_timer <= 0) {
camera->update_func = camera_update_attract_random;
}
vec3_t target = vec3_sub(ship->position, camera->position);
float height = sqrt(target.x * target.x + target.z * target.z);
camera->angle.x = -atan2(target.y, height);
camera->angle.y = -atan2(target.x, target.z);
}
void camera_update_attract_random(camera_t *camera, ship_t *ship, droid_t *droid) {
flags_rm(ship->flags, SHIP_VIEW_INTERNAL);
if (rand() % 2) {
camera->update_func = camera_update_attract_circle;
camera->update_timer = 5;
}
else {
camera->update_func = camera_update_static_follow;
camera->update_timer = 5;
section_t *section = ship->section->next;
for (int i = 0; i < 10; i++) {
section = section->next;
}
camera->section = section;
camera->position = section->center;
camera->position.y -= 500;
}
(camera->update_func)(camera, ship, droid);
}

View file

@ -1,32 +1,32 @@
#ifndef CAMERA_H
#define CAMERA_H
#include "../types.h"
#include "droid.h"
typedef struct camera_t {
vec3_t position;
vec3_t velocity;
vec3_t angle;
vec3_t angular_velocity;
vec3_t last_position;
vec3_t real_velocity;
section_t *section;
bool has_initial_section;
float update_timer;
void (*update_func)(struct camera_t *, ship_t *, droid_t *);
} camera_t;
void camera_init(camera_t *camera, section_t *section);
vec3_t camera_forward(camera_t *camera);
void camera_update(camera_t *camera, ship_t *ship, droid_t *droid);
void camera_update_race_external(camera_t *, ship_t *camShip, droid_t *);
void camera_update_race_internal(camera_t *, ship_t *camShip, droid_t *);
void camera_update_race_intro(camera_t *, ship_t *camShip, droid_t *);
void camera_update_attract_circle(camera_t *, ship_t *camShip, droid_t *);
void camera_update_attract_internal(camera_t *, ship_t *camShip, droid_t *);
void camera_update_static_follow(camera_t *, ship_t *camShip, droid_t *);
void camera_update_attract_random(camera_t *, ship_t *camShip, droid_t *);
void camera_update_rescue(camera_t *, ship_t *camShip, droid_t *);
#endif
#ifndef CAMERA_H
#define CAMERA_H
#include "../types.h"
#include "droid.h"
typedef struct camera_t {
vec3_t position;
vec3_t velocity;
vec3_t angle;
vec3_t angular_velocity;
vec3_t last_position;
vec3_t real_velocity;
section_t *section;
bool has_initial_section;
float update_timer;
void (*update_func)(struct camera_t *, ship_t *, droid_t *);
} camera_t;
void camera_init(camera_t *camera, section_t *section);
vec3_t camera_forward(camera_t *camera);
void camera_update(camera_t *camera, ship_t *ship, droid_t *droid);
void camera_update_race_external(camera_t *, ship_t *camShip, droid_t *);
void camera_update_race_internal(camera_t *, ship_t *camShip, droid_t *);
void camera_update_race_intro(camera_t *, ship_t *camShip, droid_t *);
void camera_update_attract_circle(camera_t *, ship_t *camShip, droid_t *);
void camera_update_attract_internal(camera_t *, ship_t *camShip, droid_t *);
void camera_update_static_follow(camera_t *, ship_t *camShip, droid_t *);
void camera_update_attract_random(camera_t *, ship_t *camShip, droid_t *);
void camera_update_rescue(camera_t *, ship_t *camShip, droid_t *);
#endif

View file

@ -1,260 +1,260 @@
#include "../types.h"
#include "../mem.h"
#include "../system.h"
#include "../utils.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "hud.h"
#include "droid.h"
#include "camera.h"
#include "image.h"
#include "scene.h"
#include "object.h"
#include "game.h"
static Object *droid_model;
void droid_load(void) {
texture_list_t droid_textures = image_get_compressed_textures("wipeout/common/rescu.cmp");
droid_model = objects_load("wipeout/common/rescu.prm", droid_textures);
}
void droid_init(droid_t *droid, ship_t *ship) {
droid->section = g.track.sections;
while (flags_not(droid->section->flags, SECTION_JUMP)) {
droid->section = droid->section->next;
}
droid->position = vec3_add(ship->position, vec3(0, -200, 0));
droid->velocity = vec3(0, 0, 0);
droid->acceleration = vec3(0, 0, 0);
droid->angle = vec3(0, 0, 0);
droid->angular_velocity = vec3(0, 0, 0);
droid->update_timer = DROID_UPDATE_TIME_INITIAL;
droid->mat = mat4_identity();
droid->cycle_timer = 0;
droid->update_func = droid_update_intro;
droid->sfx_tractor = sfx_reserve_loop(SFX_TRACTOR);
flags_rm(droid->sfx_tractor->flags, SFX_PLAY);
}
void droid_draw(droid_t *droid) {
droid->cycle_timer += system_tick() * M_PI * 2;
Prm prm = {.primitive = droid_model->primitives};
int rf = sin(droid->cycle_timer) * 127 + 128;
int gf = sin(droid->cycle_timer + 0.2) * 127 + 128;
int bf = sin(droid->cycle_timer * 0.5 + 0.1) * 127 + 128;
int r, g, b;
for (int i = 0; i < 11; i++) {
if (i < 2) {
r = 40;
g = gf;
b = 40;
}
else if (i < 6) {
r = bf >> 1;
b = bf;
g = bf >> 1;
}
else {
r = rf;
b = 40;
g = 40;
}
switch (prm.f3->type) {
case PRM_TYPE_GT3:
prm.gt3->color[0].r = r;
prm.gt3->color[0].g = g;
prm.gt3->color[0].b = b;
prm.gt3->color[1].r = r;
prm.gt3->color[1].g = g;
prm.gt3->color[1].b = b;
prm.gt3->color[2].r = r;
prm.gt3->color[2].g = g;
prm.gt3->color[2].b = b;
prm.gt3++;
break;
case PRM_TYPE_GT4:
prm.gt4->color[0].r = r;
prm.gt4->color[0].g = g;
prm.gt4->color[0].b = b;
prm.gt4->color[1].r = r;
prm.gt4->color[1].g = g;
prm.gt4->color[1].b = b;
prm.gt4->color[2].r = r;
prm.gt4->color[2].g = g;
prm.gt4->color[2].b = b;
prm.gt4->color[3].r = 40;
prm.gt4->color[3].g = 40;
prm.gt4->color[3].b = 40;
prm.gt4++;
break;
}
}
mat4_set_translation(&droid->mat, droid->position);
mat4_set_yaw_pitch_roll(&droid->mat, droid->angle);
object_draw(droid_model, &droid->mat);
}
void droid_update(droid_t *droid, ship_t *ship) {
(droid->update_func)(droid, ship);
droid->velocity = vec3_add(droid->velocity, vec3_mulf(droid->acceleration, 30 * system_tick()));
droid->velocity = vec3_sub(droid->velocity, vec3_mulf(droid->velocity, 0.125 * 30 * system_tick()));
droid->position = vec3_add(droid->position, vec3_mulf(droid->velocity, 0.015625 * 30 * system_tick()));
droid->angle = vec3_add(droid->angle, vec3_mulf(droid->angular_velocity, system_tick()));
droid->angle = vec3_wrap_angle(droid->angle);
if (flags_is(droid->sfx_tractor->flags, SFX_PLAY)) {
sfx_set_position(droid->sfx_tractor, droid->position, droid->velocity, 0.5);
}
}
void droid_update_intro(droid_t *droid, ship_t *ship) {
droid->update_timer -= system_tick();
if (droid->update_timer < DROID_UPDATE_TIME_INTRO_3) {
droid->acceleration.x = (-sin(droid->angle.y) * cos(droid->angle.x)) * 0.25 * 4096.0;
droid->acceleration.y = 0;
droid->acceleration.z = (cos(droid->angle.y) * cos(droid->angle.x)) * 0.25 * 4096.0;
droid->angular_velocity.y = 0;
}
else if (droid->update_timer < DROID_UPDATE_TIME_INTRO_2) {
droid->acceleration.x = (-sin(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096.0;
droid->acceleration.y = -140;
droid->acceleration.z = (cos(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096.0;
droid->angular_velocity.y = (-8.0 / 4096.0) * M_PI * 2 * 30;
}
else if (droid->update_timer < DROID_UPDATE_TIME_INTRO_1) {
droid->acceleration.y -= 90 * system_tick();
droid->angular_velocity.y = (8.0 / 4096.0) * M_PI * 2 * 30;
}
if (droid->update_timer <= 0) {
droid->update_timer = DROID_UPDATE_TIME_INITIAL;
droid->update_func = droid_update_idle;
droid->position.x = droid->section->center.x;
droid->position.y = -3000;
droid->position.z = droid->section->center.z;
}
}
void droid_update_idle(droid_t *droid, ship_t *ship) {
section_t *next = droid->section->next;
vec3_t target = vec3(
(droid->section->center.x + next->center.x) * 0.5,
droid->section->center.y - 3000,
(droid->section->center.z + next->center.z) * 0.5
);
vec3_t target_vector = vec3_sub(target, droid->position);
float target_heading = -atan2(target_vector.x, target_vector.z);
float quickest_turn = target_heading - droid->angle.y;
float turn;
if (droid->angle.y < 0) {
turn = target_heading - (droid->angle.y + M_PI*2);
}
else {
turn = target_heading - (droid->angle.y - M_PI*2);
}
if (fabsf(turn) < fabsf(quickest_turn)) {
droid->angular_velocity.y = turn * 30 / 64.0;
}
else {
droid->angular_velocity.y = quickest_turn * 30.0 / 64.0;
}
droid->acceleration.x = (-sin(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096;
droid->acceleration.y = target_vector.y / 64.0;
droid->acceleration.z = (cos(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096;
if (flags_is(ship->flags, SHIP_IN_RESCUE)) {
flags_add(droid->sfx_tractor->flags, SFX_PLAY);
droid->update_func = droid_update_rescue;
droid->update_timer = DROID_UPDATE_TIME_INITIAL;
g.camera.update_func = camera_update_rescue;
flags_add(ship->flags, SHIP_VIEW_REMOTE);
if (flags_is(ship->section->flags, SECTION_JUMP)) {
g.camera.section = ship->section->next;
}
else {
g.camera.section = ship->section;
}
// If droid is not nearby the rescue position teleport it in!
if (droid->section != ship->section && droid->section != ship->section->prev) {
droid->section = ship->section;
section_t *next = droid->section->next;
droid->position.x = (droid->section->center.x + next->center.x) * 0.5;
droid->position.y = droid->section->center.y - 3000;
droid->position.z = (droid->section->center.z + next->center.z) * 0.5;
}
flags_rm(ship->flags, SHIP_IN_TOW);
droid->velocity = vec3(0,0,0);
droid->acceleration = vec3(0,0,0);
}
// AdjustDirectionalNote(START_SIREN, 0, 0, (VECTOR){droid->position.x, droid->position.y, droid->position.z});
}
void droid_update_rescue(droid_t *droid, ship_t *ship) {
droid->angular_velocity.y = 0;
droid->angle.y = ship->angle.y;
vec3_t target = vec3(ship->position.x, ship->position.y - 350, ship->position.z);
vec3_t distance = vec3_sub(target, droid->position);
if (flags_is(ship->flags, SHIP_IN_TOW)) {
droid->velocity = vec3(0,0,0);
droid->acceleration = vec3(0,0,0);
droid->position = target;
}
else if (vec3_len(distance) < 8) {
flags_add(ship->flags, SHIP_IN_TOW);
droid->velocity = vec3(0,0,0);
droid->acceleration = vec3(0,0,0);
droid->position = target;
}
else {
droid->velocity = vec3_mulf(distance, 16);
}
// Are we done rescuing?
if (flags_not(ship->flags, SHIP_IN_RESCUE)) {
flags_rm(droid->sfx_tractor->flags, SFX_PLAY);
droid->siren_started = false;
droid->update_func = droid_update_idle;
droid->update_timer = DROID_UPDATE_TIME_INITIAL;
while (flags_not(droid->section->flags, SECTION_JUMP)) {
droid->section = droid->section->prev;
}
}
}
#include "../types.h"
#include "../mem.h"
#include "../system.h"
#include "../utils.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "hud.h"
#include "droid.h"
#include "camera.h"
#include "image.h"
#include "scene.h"
#include "object.h"
#include "game.h"
static Object *droid_model;
void droid_load(void) {
texture_list_t droid_textures = image_get_compressed_textures("wipeout/common/rescu.cmp");
droid_model = objects_load("wipeout/common/rescu.prm", droid_textures);
}
void droid_init(droid_t *droid, ship_t *ship) {
droid->section = g.track.sections;
while (flags_not(droid->section->flags, SECTION_JUMP)) {
droid->section = droid->section->next;
}
droid->position = vec3_add(ship->position, vec3(0, -200, 0));
droid->velocity = vec3(0, 0, 0);
droid->acceleration = vec3(0, 0, 0);
droid->angle = vec3(0, 0, 0);
droid->angular_velocity = vec3(0, 0, 0);
droid->update_timer = DROID_UPDATE_TIME_INITIAL;
droid->mat = mat4_identity();
droid->cycle_timer = 0;
droid->update_func = droid_update_intro;
droid->sfx_tractor = sfx_reserve_loop(SFX_TRACTOR);
flags_rm(droid->sfx_tractor->flags, SFX_PLAY);
}
void droid_draw(droid_t *droid) {
droid->cycle_timer += system_tick() * M_PI * 2;
Prm prm = {.primitive = droid_model->primitives};
int rf = sin(droid->cycle_timer) * 127 + 128;
int gf = sin(droid->cycle_timer + 0.2) * 127 + 128;
int bf = sin(droid->cycle_timer * 0.5 + 0.1) * 127 + 128;
int r, g, b;
for (int i = 0; i < 11; i++) {
if (i < 2) {
r = 40;
g = gf;
b = 40;
}
else if (i < 6) {
r = bf >> 1;
b = bf;
g = bf >> 1;
}
else {
r = rf;
b = 40;
g = 40;
}
switch (prm.f3->type) {
case PRM_TYPE_GT3:
prm.gt3->color[0].r = r;
prm.gt3->color[0].g = g;
prm.gt3->color[0].b = b;
prm.gt3->color[1].r = r;
prm.gt3->color[1].g = g;
prm.gt3->color[1].b = b;
prm.gt3->color[2].r = r;
prm.gt3->color[2].g = g;
prm.gt3->color[2].b = b;
prm.gt3++;
break;
case PRM_TYPE_GT4:
prm.gt4->color[0].r = r;
prm.gt4->color[0].g = g;
prm.gt4->color[0].b = b;
prm.gt4->color[1].r = r;
prm.gt4->color[1].g = g;
prm.gt4->color[1].b = b;
prm.gt4->color[2].r = r;
prm.gt4->color[2].g = g;
prm.gt4->color[2].b = b;
prm.gt4->color[3].r = 40;
prm.gt4->color[3].g = 40;
prm.gt4->color[3].b = 40;
prm.gt4++;
break;
}
}
mat4_set_translation(&droid->mat, droid->position);
mat4_set_yaw_pitch_roll(&droid->mat, droid->angle);
object_draw(droid_model, &droid->mat);
}
void droid_update(droid_t *droid, ship_t *ship) {
(droid->update_func)(droid, ship);
droid->velocity = vec3_add(droid->velocity, vec3_mulf(droid->acceleration, 30 * system_tick()));
droid->velocity = vec3_sub(droid->velocity, vec3_mulf(droid->velocity, 0.125 * 30 * system_tick()));
droid->position = vec3_add(droid->position, vec3_mulf(droid->velocity, 0.015625 * 30 * system_tick()));
droid->angle = vec3_add(droid->angle, vec3_mulf(droid->angular_velocity, system_tick()));
droid->angle = vec3_wrap_angle(droid->angle);
if (flags_is(droid->sfx_tractor->flags, SFX_PLAY)) {
sfx_set_position(droid->sfx_tractor, droid->position, droid->velocity, 0.5);
}
}
void droid_update_intro(droid_t *droid, ship_t *ship) {
droid->update_timer -= system_tick();
if (droid->update_timer < DROID_UPDATE_TIME_INTRO_3) {
droid->acceleration.x = (-sin(droid->angle.y) * cos(droid->angle.x)) * 0.25 * 4096.0;
droid->acceleration.y = 0;
droid->acceleration.z = (cos(droid->angle.y) * cos(droid->angle.x)) * 0.25 * 4096.0;
droid->angular_velocity.y = 0;
}
else if (droid->update_timer < DROID_UPDATE_TIME_INTRO_2) {
droid->acceleration.x = (-sin(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096.0;
droid->acceleration.y = -140;
droid->acceleration.z = (cos(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096.0;
droid->angular_velocity.y = (-8.0 / 4096.0) * M_PI * 2 * 30;
}
else if (droid->update_timer < DROID_UPDATE_TIME_INTRO_1) {
droid->acceleration.y -= 90 * system_tick();
droid->angular_velocity.y = (8.0 / 4096.0) * M_PI * 2 * 30;
}
if (droid->update_timer <= 0) {
droid->update_timer = DROID_UPDATE_TIME_INITIAL;
droid->update_func = droid_update_idle;
droid->position.x = droid->section->center.x;
droid->position.y = -3000;
droid->position.z = droid->section->center.z;
}
}
void droid_update_idle(droid_t *droid, ship_t *ship) {
section_t *next = droid->section->next;
vec3_t target = vec3(
(droid->section->center.x + next->center.x) * 0.5,
droid->section->center.y - 3000,
(droid->section->center.z + next->center.z) * 0.5
);
vec3_t target_vector = vec3_sub(target, droid->position);
float target_heading = -atan2(target_vector.x, target_vector.z);
float quickest_turn = target_heading - droid->angle.y;
float turn;
if (droid->angle.y < 0) {
turn = target_heading - (droid->angle.y + M_PI*2);
}
else {
turn = target_heading - (droid->angle.y - M_PI*2);
}
if (fabsf(turn) < fabsf(quickest_turn)) {
droid->angular_velocity.y = turn * 30 / 64.0;
}
else {
droid->angular_velocity.y = quickest_turn * 30.0 / 64.0;
}
droid->acceleration.x = (-sin(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096;
droid->acceleration.y = target_vector.y / 64.0;
droid->acceleration.z = (cos(droid->angle.y) * cos(droid->angle.x)) * 0.125 * 4096;
if (flags_is(ship->flags, SHIP_IN_RESCUE)) {
flags_add(droid->sfx_tractor->flags, SFX_PLAY);
droid->update_func = droid_update_rescue;
droid->update_timer = DROID_UPDATE_TIME_INITIAL;
g.camera.update_func = camera_update_rescue;
flags_add(ship->flags, SHIP_VIEW_REMOTE);
if (flags_is(ship->section->flags, SECTION_JUMP)) {
g.camera.section = ship->section->next;
}
else {
g.camera.section = ship->section;
}
// If droid is not nearby the rescue position teleport it in!
if (droid->section != ship->section && droid->section != ship->section->prev) {
droid->section = ship->section;
section_t *next = droid->section->next;
droid->position.x = (droid->section->center.x + next->center.x) * 0.5;
droid->position.y = droid->section->center.y - 3000;
droid->position.z = (droid->section->center.z + next->center.z) * 0.5;
}
flags_rm(ship->flags, SHIP_IN_TOW);
droid->velocity = vec3(0,0,0);
droid->acceleration = vec3(0,0,0);
}
// AdjustDirectionalNote(START_SIREN, 0, 0, (VECTOR){droid->position.x, droid->position.y, droid->position.z});
}
void droid_update_rescue(droid_t *droid, ship_t *ship) {
droid->angular_velocity.y = 0;
droid->angle.y = ship->angle.y;
vec3_t target = vec3(ship->position.x, ship->position.y - 350, ship->position.z);
vec3_t distance = vec3_sub(target, droid->position);
if (flags_is(ship->flags, SHIP_IN_TOW)) {
droid->velocity = vec3(0,0,0);
droid->acceleration = vec3(0,0,0);
droid->position = target;
}
else if (vec3_len(distance) < 8) {
flags_add(ship->flags, SHIP_IN_TOW);
droid->velocity = vec3(0,0,0);
droid->acceleration = vec3(0,0,0);
droid->position = target;
}
else {
droid->velocity = vec3_mulf(distance, 16);
}
// Are we done rescuing?
if (flags_not(ship->flags, SHIP_IN_RESCUE)) {
flags_rm(droid->sfx_tractor->flags, SFX_PLAY);
droid->siren_started = false;
droid->update_func = droid_update_idle;
droid->update_timer = DROID_UPDATE_TIME_INITIAL;
while (flags_not(droid->section->flags, SECTION_JUMP)) {
droid->section = droid->section->prev;
}
}
}

View file

@ -1,39 +1,39 @@
#ifndef DROID_H
#define DROID_H
#include "../types.h"
#include "track.h"
#include "ship.h"
#include "sfx.h"
#define DROID_UPDATE_TIME_INITIAL (800 * (1.0/30.0))
#define DROID_UPDATE_TIME_INTRO_1 (770 * (1.0/30.0))
#define DROID_UPDATE_TIME_INTRO_2 (710 * (1.0/30.0))
#define DROID_UPDATE_TIME_INTRO_3 (400 * (1.0/30.0))
typedef struct droid_t {
section_t *section;
vec3_t position;
vec3_t velocity;
vec3_t acceleration;
vec3_t angle;
vec3_t angular_velocity;
bool siren_started;
float cycle_timer;
float update_timer;
void (*update_func)(struct droid_t *, ship_t *);
mat4_t mat;
Object *model;
sfx_t *sfx_tractor;
} droid_t;
void droid_draw(droid_t *droid);
void droid_load(void);
void droid_init(droid_t *droid, ship_t *ship);
void droid_update(droid_t *droid, ship_t *ship);
void droid_update_intro(droid_t *droid, ship_t *ship);
void droid_update_idle(droid_t *droid, ship_t *ship);
void droid_update_rescue(droid_t *droid, ship_t *ship);
#endif
#ifndef DROID_H
#define DROID_H
#include "../types.h"
#include "track.h"
#include "ship.h"
#include "sfx.h"
#define DROID_UPDATE_TIME_INITIAL (800 * (1.0/30.0))
#define DROID_UPDATE_TIME_INTRO_1 (770 * (1.0/30.0))
#define DROID_UPDATE_TIME_INTRO_2 (710 * (1.0/30.0))
#define DROID_UPDATE_TIME_INTRO_3 (400 * (1.0/30.0))
typedef struct droid_t {
section_t *section;
vec3_t position;
vec3_t velocity;
vec3_t acceleration;
vec3_t angle;
vec3_t angular_velocity;
bool siren_started;
float cycle_timer;
float update_timer;
void (*update_func)(struct droid_t *, ship_t *);
mat4_t mat;
Object *model;
sfx_t *sfx_tractor;
} droid_t;
void droid_draw(droid_t *droid);
void droid_load(void);
void droid_init(droid_t *droid, ship_t *ship);
void droid_update(droid_t *droid, ship_t *ship);
void droid_update_intro(droid_t *droid, ship_t *ship);
void droid_update_idle(droid_t *droid, ship_t *ship);
void droid_update_rescue(droid_t *droid, ship_t *ship);
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,270 +1,270 @@
#ifndef GAME_H
#define GAME_H
#include "../types.h"
#include "droid.h"
#include "ship.h"
#include "camera.h"
#include "track.h"
#define NUM_AI_OPPONENTS 7
#define NUM_PILOTS_PER_TEAM 2
#define NUM_NON_BONUS_CIRCUTS 6
#define NUM_MUSIC_TRACKS 11
#define NUM_HIGHSCORES 5
#define NUM_LAPS 3
#define NUM_LIVES 3
#define QUALIFYING_RANK 3
#define SAVE_DATA_MAGIC 0x64736f77
typedef enum {
A_UP,
A_DOWN,
A_LEFT,
A_RIGHT,
A_BRAKE_LEFT,
A_BRAKE_RIGHT,
A_THRUST,
A_FIRE,
A_CHANGE_VIEW,
NUM_GAME_ACTIONS,
A_MENU_UP,
A_MENU_DOWN,
A_MENU_LEFT,
A_MENU_RIGHT,
A_MENU_BACK,
A_MENU_SELECT,
A_MENU_START,
A_MENU_QUIT,
} action_t;
typedef enum {
GAME_SCENE_INTRO,
GAME_SCENE_TITLE,
GAME_SCENE_MAIN_MENU,
GAME_SCENE_HIGHSCORES,
GAME_SCENE_RACE,
GAME_SCENE_NONE,
NUM_GAME_SCENES
} game_scene_t;
enum race_class {
RACE_CLASS_VENOM,
RACE_CLASS_RAPIER,
NUM_RACE_CLASSES
};
enum race_type {
RACE_TYPE_CHAMPIONSHIP,
RACE_TYPE_SINGLE,
RACE_TYPE_TIME_TRIAL,
NUM_RACE_TYPES,
};
enum highscore_tab {
HIGHSCORE_TAB_TIME_TRIAL,
HIGHSCORE_TAB_RACE,
NUM_HIGHSCORE_TABS
};
enum pilot {
PILOT_JOHN_DEKKA,
PILOT_DANIEL_CHANG,
PILOT_ARIAL_TETSUO,
PILOT_ANASTASIA_CHEROVOSKI,
PILOT_KEL_SOLAAR,
PILOT_ARIAN_TETSUO,
PILOT_SOFIA_DE_LA_RENTE,
PILOT_PAUL_JACKSON,
NUM_PILOTS
};
enum team {
TEAM_AG_SYSTEMS,
TEAM_AURICOM,
TEAM_QIREX,
TEAM_FEISAR,
NUM_TEAMS
};
enum circut {
CIRCUT_ALTIMA_VII,
CIRCUT_KARBONIS_V,
CIRCUT_TERRAMAX,
CIRCUT_KORODERA,
CIRCUT_ARRIDOS_IV,
CIRCUT_SILVERSTREAM,
CIRCUT_FIRESTAR,
NUM_CIRCUTS
};
// Game definitions
typedef struct {
char *name;
} race_class_t;
typedef struct {
char *name;
} race_type_t;
typedef struct {
char *name;
char *portrait;
int logo_model;
int team;
} pilot_t;
typedef struct {
float thrust_max;
float thrust_magnitude;
bool fight_back;
} ai_setting_t;
typedef struct {
float mass;
float thrust_max;
float resistance;
float turn_rate;
float turn_rate_max;
float skid;
} team_attributes_t;
typedef struct {
char *name;
int logo_model;
int pilots[NUM_PILOTS_PER_TEAM];
team_attributes_t attributes[NUM_RACE_CLASSES];
} team_t;
typedef struct {
char *path;
float start_line_pos;
float behind_speed;
float spread_base;
float spread_factor;
float sky_y_offset;
} circut_settings_t;
typedef struct {
char *name;
bool is_bonus_circut;
circut_settings_t settings[NUM_RACE_CLASSES];
} circut_t;
typedef struct {
char *path;
char *name;
} music_track_t;
typedef struct {
race_class_t race_classes[NUM_RACE_CLASSES];
race_type_t race_types[NUM_RACE_TYPES];
pilot_t pilots[NUM_PILOTS];
team_t teams[NUM_TEAMS];
ai_setting_t ai_settings[NUM_RACE_CLASSES][NUM_AI_OPPONENTS];
circut_t circuts[NUM_CIRCUTS];
int ship_model_to_pilot[NUM_PILOTS];
int race_points_for_rank[NUM_PILOTS];
music_track_t music[NUM_MUSIC_TRACKS];
char *credits[104];
struct {
char *venom[15];
char *venom_all_circuts[19];
char *rapier[26];
char *rapier_all_circuts[24];
} congratulations;
} game_def_t;
// Running game data
typedef struct {
uint16_t pilot;
uint16_t points;
} pilot_points_t;
typedef struct {
float frame_time;
float frame_rate;
int race_class;
int race_type;
int highscore_tab;
int team;
int pilot;
int circut;
bool is_attract_mode;
bool show_credits;
bool is_new_lap_record;
bool is_new_race_record;
float best_lap;
float race_time;
int lives;
int race_position;
float lap_times[NUM_PILOTS][NUM_LAPS];
pilot_points_t race_ranks[NUM_PILOTS];
pilot_points_t championship_ranks[NUM_PILOTS];
camera_t camera;
droid_t droid;
ship_t ships[NUM_PILOTS];
track_t track;
} game_t;
// Save Data
typedef struct {
char name[4];
float time;
} highscores_entry_t;
typedef struct {
highscores_entry_t entries[NUM_HIGHSCORES];
float lap_record;
} highscores_t;
typedef struct {
uint32_t magic;
bool is_dirty;
float sfx_volume;
float music_volume;
uint8_t ui_scale;
bool show_fps;
bool fullscreen;
int screen_res;
int post_effect;
uint32_t has_rapier_class;
uint32_t has_bonus_circuts;
uint8_t buttons[NUM_GAME_ACTIONS][2];
char highscores_name[4];
highscores_t highscores[NUM_RACE_CLASSES][NUM_CIRCUTS][NUM_HIGHSCORE_TABS];
} save_t;
extern const game_def_t def;
extern game_t g;
extern save_t save;
void game_init(void);
void game_set_scene(game_scene_t scene);
void game_reset_championship(void);
void game_update(void);
#endif
#ifndef GAME_H
#define GAME_H
#include "../types.h"
#include "droid.h"
#include "ship.h"
#include "camera.h"
#include "track.h"
#define NUM_AI_OPPONENTS 7
#define NUM_PILOTS_PER_TEAM 2
#define NUM_NON_BONUS_CIRCUTS 6
#define NUM_MUSIC_TRACKS 11
#define NUM_HIGHSCORES 5
#define NUM_LAPS 3
#define NUM_LIVES 3
#define QUALIFYING_RANK 3
#define SAVE_DATA_MAGIC 0x64736f77
typedef enum {
A_UP,
A_DOWN,
A_LEFT,
A_RIGHT,
A_BRAKE_LEFT,
A_BRAKE_RIGHT,
A_THRUST,
A_FIRE,
A_CHANGE_VIEW,
NUM_GAME_ACTIONS,
A_MENU_UP,
A_MENU_DOWN,
A_MENU_LEFT,
A_MENU_RIGHT,
A_MENU_BACK,
A_MENU_SELECT,
A_MENU_START,
A_MENU_QUIT,
} action_t;
typedef enum {
GAME_SCENE_INTRO,
GAME_SCENE_TITLE,
GAME_SCENE_MAIN_MENU,
GAME_SCENE_HIGHSCORES,
GAME_SCENE_RACE,
GAME_SCENE_NONE,
NUM_GAME_SCENES
} game_scene_t;
enum race_class {
RACE_CLASS_VENOM,
RACE_CLASS_RAPIER,
NUM_RACE_CLASSES
};
enum race_type {
RACE_TYPE_CHAMPIONSHIP,
RACE_TYPE_SINGLE,
RACE_TYPE_TIME_TRIAL,
NUM_RACE_TYPES,
};
enum highscore_tab {
HIGHSCORE_TAB_TIME_TRIAL,
HIGHSCORE_TAB_RACE,
NUM_HIGHSCORE_TABS
};
enum pilot {
PILOT_JOHN_DEKKA,
PILOT_DANIEL_CHANG,
PILOT_ARIAL_TETSUO,
PILOT_ANASTASIA_CHEROVOSKI,
PILOT_KEL_SOLAAR,
PILOT_ARIAN_TETSUO,
PILOT_SOFIA_DE_LA_RENTE,
PILOT_PAUL_JACKSON,
NUM_PILOTS
};
enum team {
TEAM_AG_SYSTEMS,
TEAM_AURICOM,
TEAM_QIREX,
TEAM_FEISAR,
NUM_TEAMS
};
enum circut {
CIRCUT_ALTIMA_VII,
CIRCUT_KARBONIS_V,
CIRCUT_TERRAMAX,
CIRCUT_KORODERA,
CIRCUT_ARRIDOS_IV,
CIRCUT_SILVERSTREAM,
CIRCUT_FIRESTAR,
NUM_CIRCUTS
};
// Game definitions
typedef struct {
char *name;
} race_class_t;
typedef struct {
char *name;
} race_type_t;
typedef struct {
char *name;
char *portrait;
int logo_model;
int team;
} pilot_t;
typedef struct {
float thrust_max;
float thrust_magnitude;
bool fight_back;
} ai_setting_t;
typedef struct {
float mass;
float thrust_max;
float resistance;
float turn_rate;
float turn_rate_max;
float skid;
} team_attributes_t;
typedef struct {
char *name;
int logo_model;
int pilots[NUM_PILOTS_PER_TEAM];
team_attributes_t attributes[NUM_RACE_CLASSES];
} team_t;
typedef struct {
char *path;
float start_line_pos;
float behind_speed;
float spread_base;
float spread_factor;
float sky_y_offset;
} circut_settings_t;
typedef struct {
char *name;
bool is_bonus_circut;
circut_settings_t settings[NUM_RACE_CLASSES];
} circut_t;
typedef struct {
char *path;
char *name;
} music_track_t;
typedef struct {
race_class_t race_classes[NUM_RACE_CLASSES];
race_type_t race_types[NUM_RACE_TYPES];
pilot_t pilots[NUM_PILOTS];
team_t teams[NUM_TEAMS];
ai_setting_t ai_settings[NUM_RACE_CLASSES][NUM_AI_OPPONENTS];
circut_t circuts[NUM_CIRCUTS];
int ship_model_to_pilot[NUM_PILOTS];
int race_points_for_rank[NUM_PILOTS];
music_track_t music[NUM_MUSIC_TRACKS];
char *credits[104];
struct {
char *venom[15];
char *venom_all_circuts[19];
char *rapier[26];
char *rapier_all_circuts[24];
} congratulations;
} game_def_t;
// Running game data
typedef struct {
uint16_t pilot;
uint16_t points;
} pilot_points_t;
typedef struct {
float frame_time;
float frame_rate;
int race_class;
int race_type;
int highscore_tab;
int team;
int pilot;
int circut;
bool is_attract_mode;
bool show_credits;
bool is_new_lap_record;
bool is_new_race_record;
float best_lap;
float race_time;
int lives;
int race_position;
float lap_times[NUM_PILOTS][NUM_LAPS];
pilot_points_t race_ranks[NUM_PILOTS];
pilot_points_t championship_ranks[NUM_PILOTS];
camera_t camera;
droid_t droid;
ship_t ships[NUM_PILOTS];
track_t track;
} game_t;
// Save Data
typedef struct {
char name[4];
float time;
} highscores_entry_t;
typedef struct {
highscores_entry_t entries[NUM_HIGHSCORES];
float lap_record;
} highscores_t;
typedef struct {
uint32_t magic;
bool is_dirty;
float sfx_volume;
float music_volume;
uint8_t ui_scale;
bool show_fps;
bool fullscreen;
int screen_res;
int post_effect;
uint32_t has_rapier_class;
uint32_t has_bonus_circuts;
uint8_t buttons[NUM_GAME_ACTIONS][2];
char highscores_name[4];
highscores_t highscores[NUM_RACE_CLASSES][NUM_CIRCUTS][NUM_HIGHSCORE_TABS];
} save_t;
extern const game_def_t def;
extern game_t g;
extern save_t save;
void game_init(void);
void game_set_scene(game_scene_t scene);
void game_reset_championship(void);
void game_update(void);
#endif

View file

@ -1,253 +1,253 @@
#include "../types.h"
#include "../mem.h"
#include "../utils.h"
#include "../system.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "hud.h"
#include "droid.h"
#include "camera.h"
#include "image.h"
#include "ship_ai.h"
#include "game.h"
#include "ui.h"
static texture_list_t weapon_icon_textures;
static uint16_t target_reticle;
typedef struct {
vec2i_t offset;
uint16_t height;
rgba_t color;
} speedo_bar_t;
const struct {
uint16_t width;
uint16_t skew;
speedo_bar_t bars[13];
} speedo = {
.width = 121,
.skew = 2,
.bars = {
{{.x = 6, .y = 12}, .height = 10, .color = rgba( 66, 16, 49, 255)},
{{.x = 13, .y = 12}, .height = 10, .color = rgba(115, 33, 90, 255)},
{{.x = 20, .y = 12}, .height = 10, .color = rgba(132, 58, 164, 255)},
{{.x = 27, .y = 12}, .height = 10, .color = rgba( 99, 90, 197, 255)},
{{.x = 34, .y = 12}, .height = 10, .color = rgba( 74, 148, 181, 255)},
{{.x = 41, .y = 12}, .height = 10, .color = rgba( 66, 173, 115, 255)},
{{.x = 50, .y = 10}, .height = 12, .color = rgba( 99, 206, 58, 255)},
{{.x = 59, .y = 8}, .height = 12, .color = rgba(189, 206, 41, 255)},
{{.x = 69, .y = 5}, .height = 13, .color = rgba(247, 140, 33, 255)},
{{.x = 81, .y = 2}, .height = 15, .color = rgba(255, 197, 49, 255)},
{{.x = 95, .y = 1}, .height = 16, .color = rgba(255, 222, 115, 255)},
{{.x = 110, .y = 1}, .height = 16, .color = rgba(255, 239, 181, 255)},
{{.x = 126, .y = 1}, .height = 16, .color = rgba(255, 255, 255, 255)}
}
};
static uint16_t speedo_facia_texture;
void hud_load(void) {
speedo_facia_texture = image_get_texture("wipeout/textures/speedo.tim");
target_reticle = image_get_texture_semi_trans("wipeout/textures/target2.tim");
weapon_icon_textures = image_get_compressed_textures("wipeout/common/wicons.cmp");
}
static void hud_draw_speedo_bar(vec2i_t *pos, const speedo_bar_t *a, const speedo_bar_t *b, float f, rgba_t color_override) {
rgba_t left_color, right_color;
if (color_override.a > 0) {
left_color = color_override;
right_color = color_override;
}
else {
left_color = a->color;
right_color = rgba(
lerp(a->color.r, b->color.r, f),
lerp(a->color.g, b->color.g, f),
lerp(a->color.b, b->color.b, f),
lerp(a->color.a, b->color.a, f)
);
}
float right_h = lerp(a->height, b->height, f);
vec2i_t top_left = vec2i(a->offset.x + 1, a->offset.y);
vec2i_t bottom_left = vec2i(a->offset.x + 1 - a->height / speedo.skew, a->offset.y + a->height);
vec2i_t top_right = vec2i(lerp(a->offset.x + 1, b->offset.x, f), lerp(a->offset.y, b->offset.y, f));
vec2i_t bottom_right = vec2i(top_right.x - right_h / speedo.skew, top_right.y + right_h);
top_left = ui_scaled(top_left);
bottom_left = ui_scaled(bottom_left);
top_right = ui_scaled(top_right);
bottom_right = ui_scaled(bottom_right);
render_push_tris((tris_t) {
.vertices = {
{
.pos = {pos->x + bottom_left.x, pos->y + bottom_left.y, 0},
.uv = {0, 0},
.color = left_color
},
{
.pos = {pos->x + top_right.x, pos->y + top_right.y, 0},
.uv = {0, 0},
.color = right_color
},
{
.pos = {pos->x + top_left.x, pos->y + top_left.y, 0},
.uv = {0, 0},
.color = left_color
},
}
}, RENDER_NO_TEXTURE);
render_push_tris((tris_t) {
.vertices = {
{
.pos = {pos->x + bottom_right.x, pos->y + bottom_right.y, 0},
.uv = {0, 0},
.color = right_color
},
{
.pos = {pos->x + top_right.x, pos->y + top_right.y, 0},
.uv = {0, 0},
.color = right_color
},
{
.pos = {pos->x + bottom_left.x, pos->y + bottom_left.y, 0},
.uv = {0, 0},
.color = left_color
},
}
}, RENDER_NO_TEXTURE);
}
static void hud_draw_speedo_bars(vec2i_t *pos, float f, rgba_t color_override) {
if (f <= 0) {
return;
}
if (f - floor(f) > 0.9) {
f = ceil(f);
}
if (f > 13) {
f = 13;
}
int bars = f;
for (int i = 1; i < bars; i++) {
hud_draw_speedo_bar(pos, &speedo.bars[i - 1], &speedo.bars[i], 1, color_override);
}
if (bars > 12) {
return;
}
float last_bar_fraction = f - bars + 0.1;
if (last_bar_fraction <= 0) {
return;
}
if (last_bar_fraction > 1) {
last_bar_fraction = 1;
}
int last_bar = bars == 0 ? 1 : bars;
hud_draw_speedo_bar(pos, &speedo.bars[last_bar - 1], &speedo.bars[last_bar], last_bar_fraction, color_override);
}
static void hud_draw_speedo(int speed, int thrust) {
vec2i_t facia_pos = ui_scaled_pos(UI_POS_BOTTOM | UI_POS_RIGHT, vec2i(-141, -45));
vec2i_t bar_pos = ui_scaled_pos(UI_POS_BOTTOM | UI_POS_RIGHT, vec2i(-141, -40));
hud_draw_speedo_bars(&bar_pos, thrust / 65.0, rgba(255, 0, 0, 128));
hud_draw_speedo_bars(&bar_pos, speed / 2166.0, rgba(0, 0, 0, 0));
render_push_2d(facia_pos, ui_scaled(render_texture_size(speedo_facia_texture)), rgba(128, 128, 128, 255), speedo_facia_texture);
}
static void hud_draw_target_icon(vec3_t position) {
vec2i_t screen_size = render_size();
vec2i_t size = ui_scaled(render_texture_size(target_reticle));
vec3_t projected = render_transform(position);
// Not on screen?
if (
projected.x < -1 || projected.x > 1 ||
projected.y < -1 || projected.y > 1 ||
projected.z >= 1
) {
return;
}
vec2i_t pos = vec2i(
(( projected.x + 1.0) / 2.0) * screen_size.x - size.x / 2,
((-projected.y + 1.0) / 2.0) * screen_size.y - size.y / 2
);
render_push_2d(pos, size, rgba(128, 128, 128, 128), target_reticle);
}
void hud_draw(ship_t *ship) {
// Current lap time
if (ship->lap >= 0) {
ui_draw_time(ship->lap_time, ui_scaled_pos(UI_POS_BOTTOM | UI_POS_LEFT, vec2i(16, -30)), UI_SIZE_16, UI_COLOR_DEFAULT);
for (int i = 0; i < ship->lap && i < NUM_LAPS-1; i++) {
ui_draw_time(g.lap_times[ship->pilot][i], ui_scaled_pos(UI_POS_BOTTOM | UI_POS_LEFT, vec2i(16, -45 - (10 * i))), UI_SIZE_8, UI_COLOR_ACCENT);
}
}
// Current Lap
int display_lap = max(0, ship->lap + 1);
ui_draw_text("LAP", ui_scaled(vec2i(15, 8)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number(display_lap, ui_scaled(vec2i(10, 19)), UI_SIZE_16, UI_COLOR_DEFAULT);
int width = ui_char_width('0' + display_lap, UI_SIZE_16);
ui_draw_text("OF", ui_scaled(vec2i((10 + width), 27)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number(NUM_LAPS, ui_scaled(vec2i((32 + width), 19)), UI_SIZE_16, UI_COLOR_DEFAULT);
// Race Position
if (g.race_type != RACE_TYPE_TIME_TRIAL) {
ui_draw_text("POSITION", ui_scaled_pos(UI_POS_TOP | UI_POS_RIGHT, vec2i(-90, 8)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number(ship->position_rank, ui_scaled_pos(UI_POS_TOP | UI_POS_RIGHT, vec2i(-60, 19)), UI_SIZE_16, UI_COLOR_DEFAULT);
}
// Framerate
if (save.show_fps) {
ui_draw_text("FPS", ui_scaled(vec2i(16, 78)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number((int)(g.frame_rate), ui_scaled(vec2i(16, 90)), UI_SIZE_8, UI_COLOR_DEFAULT);
}
// Lap Record
ui_draw_text("LAP RECORD", ui_scaled(vec2i(15, 43)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_time(save.highscores[g.race_class][g.circut][g.highscore_tab].lap_record, ui_scaled(vec2i(15, 55)), UI_SIZE_8, UI_COLOR_DEFAULT);
// Wrong way
if (flags_not(ship->flags, SHIP_DIRECTION_FORWARD)) {
ui_draw_text_centered("WRONG WAY", ui_scaled_pos(UI_POS_MIDDLE | UI_POS_CENTER, vec2i(-20, 0)), UI_SIZE_16, UI_COLOR_ACCENT);
}
// Speedo
int speedo_speed = (g.camera.update_func == camera_update_attract_internal)
? ship->speed * 7
: ship->speed;
hud_draw_speedo(speedo_speed, ship->thrust_mag);
// Weapon icon
if (ship->weapon_type != WEAPON_TYPE_NONE) {
vec2i_t pos = ui_scaled_pos(UI_POS_TOP | UI_POS_CENTER, vec2i(-16, 20));
vec2i_t size = ui_scaled(vec2i(32, 32));
uint16_t icon = texture_from_list(weapon_icon_textures, ship->weapon_type-1);
render_push_2d(pos, size, rgba(128,128,128,255), icon);
}
// Lives
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
for (int i = 0; i < g.lives; i++) {
ui_draw_icon(UI_ICON_STAR, ui_scaled_pos(UI_POS_BOTTOM | UI_POS_RIGHT, vec2i(-26 - 13 * i, -50)), UI_COLOR_DEFAULT);
}
}
// Weapon target reticle
if (ship->weapon_target) {
hud_draw_target_icon(ship->weapon_target->position);
}
}
#include "../types.h"
#include "../mem.h"
#include "../utils.h"
#include "../system.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "hud.h"
#include "droid.h"
#include "camera.h"
#include "image.h"
#include "ship_ai.h"
#include "game.h"
#include "ui.h"
static texture_list_t weapon_icon_textures;
static uint16_t target_reticle;
typedef struct {
vec2i_t offset;
uint16_t height;
rgba_t color;
} speedo_bar_t;
const struct {
uint16_t width;
uint16_t skew;
speedo_bar_t bars[13];
} speedo = {
.width = 121,
.skew = 2,
.bars = {
{{.x = 6, .y = 12}, .height = 10, .color = rgba( 66, 16, 49, 255)},
{{.x = 13, .y = 12}, .height = 10, .color = rgba(115, 33, 90, 255)},
{{.x = 20, .y = 12}, .height = 10, .color = rgba(132, 58, 164, 255)},
{{.x = 27, .y = 12}, .height = 10, .color = rgba( 99, 90, 197, 255)},
{{.x = 34, .y = 12}, .height = 10, .color = rgba( 74, 148, 181, 255)},
{{.x = 41, .y = 12}, .height = 10, .color = rgba( 66, 173, 115, 255)},
{{.x = 50, .y = 10}, .height = 12, .color = rgba( 99, 206, 58, 255)},
{{.x = 59, .y = 8}, .height = 12, .color = rgba(189, 206, 41, 255)},
{{.x = 69, .y = 5}, .height = 13, .color = rgba(247, 140, 33, 255)},
{{.x = 81, .y = 2}, .height = 15, .color = rgba(255, 197, 49, 255)},
{{.x = 95, .y = 1}, .height = 16, .color = rgba(255, 222, 115, 255)},
{{.x = 110, .y = 1}, .height = 16, .color = rgba(255, 239, 181, 255)},
{{.x = 126, .y = 1}, .height = 16, .color = rgba(255, 255, 255, 255)}
}
};
static uint16_t speedo_facia_texture;
void hud_load(void) {
speedo_facia_texture = image_get_texture("wipeout/textures/speedo.tim");
target_reticle = image_get_texture_semi_trans("wipeout/textures/target2.tim");
weapon_icon_textures = image_get_compressed_textures("wipeout/common/wicons.cmp");
}
static void hud_draw_speedo_bar(vec2i_t *pos, const speedo_bar_t *a, const speedo_bar_t *b, float f, rgba_t color_override) {
rgba_t left_color, right_color;
if (color_override.a > 0) {
left_color = color_override;
right_color = color_override;
}
else {
left_color = a->color;
right_color = rgba(
lerp(a->color.r, b->color.r, f),
lerp(a->color.g, b->color.g, f),
lerp(a->color.b, b->color.b, f),
lerp(a->color.a, b->color.a, f)
);
}
float right_h = lerp(a->height, b->height, f);
vec2i_t top_left = vec2i(a->offset.x + 1, a->offset.y);
vec2i_t bottom_left = vec2i(a->offset.x + 1 - a->height / speedo.skew, a->offset.y + a->height);
vec2i_t top_right = vec2i(lerp(a->offset.x + 1, b->offset.x, f), lerp(a->offset.y, b->offset.y, f));
vec2i_t bottom_right = vec2i(top_right.x - right_h / speedo.skew, top_right.y + right_h);
top_left = ui_scaled(top_left);
bottom_left = ui_scaled(bottom_left);
top_right = ui_scaled(top_right);
bottom_right = ui_scaled(bottom_right);
render_push_tris((tris_t) {
.vertices = {
{
.pos = {pos->x + bottom_left.x, pos->y + bottom_left.y, 0},
.uv = {0, 0},
.color = left_color
},
{
.pos = {pos->x + top_right.x, pos->y + top_right.y, 0},
.uv = {0, 0},
.color = right_color
},
{
.pos = {pos->x + top_left.x, pos->y + top_left.y, 0},
.uv = {0, 0},
.color = left_color
},
}
}, RENDER_NO_TEXTURE);
render_push_tris((tris_t) {
.vertices = {
{
.pos = {pos->x + bottom_right.x, pos->y + bottom_right.y, 0},
.uv = {0, 0},
.color = right_color
},
{
.pos = {pos->x + top_right.x, pos->y + top_right.y, 0},
.uv = {0, 0},
.color = right_color
},
{
.pos = {pos->x + bottom_left.x, pos->y + bottom_left.y, 0},
.uv = {0, 0},
.color = left_color
},
}
}, RENDER_NO_TEXTURE);
}
static void hud_draw_speedo_bars(vec2i_t *pos, float f, rgba_t color_override) {
if (f <= 0) {
return;
}
if (f - floor(f) > 0.9) {
f = ceil(f);
}
if (f > 13) {
f = 13;
}
int bars = f;
for (int i = 1; i < bars; i++) {
hud_draw_speedo_bar(pos, &speedo.bars[i - 1], &speedo.bars[i], 1, color_override);
}
if (bars > 12) {
return;
}
float last_bar_fraction = f - bars + 0.1;
if (last_bar_fraction <= 0) {
return;
}
if (last_bar_fraction > 1) {
last_bar_fraction = 1;
}
int last_bar = bars == 0 ? 1 : bars;
hud_draw_speedo_bar(pos, &speedo.bars[last_bar - 1], &speedo.bars[last_bar], last_bar_fraction, color_override);
}
static void hud_draw_speedo(int speed, int thrust) {
vec2i_t facia_pos = ui_scaled_pos(UI_POS_BOTTOM | UI_POS_RIGHT, vec2i(-141, -45));
vec2i_t bar_pos = ui_scaled_pos(UI_POS_BOTTOM | UI_POS_RIGHT, vec2i(-141, -40));
hud_draw_speedo_bars(&bar_pos, thrust / 65.0, rgba(255, 0, 0, 128));
hud_draw_speedo_bars(&bar_pos, speed / 2166.0, rgba(0, 0, 0, 0));
render_push_2d(facia_pos, ui_scaled(render_texture_size(speedo_facia_texture)), rgba(128, 128, 128, 255), speedo_facia_texture);
}
static void hud_draw_target_icon(vec3_t position) {
vec2i_t screen_size = render_size();
vec2i_t size = ui_scaled(render_texture_size(target_reticle));
vec3_t projected = render_transform(position);
// Not on screen?
if (
projected.x < -1 || projected.x > 1 ||
projected.y < -1 || projected.y > 1 ||
projected.z >= 1
) {
return;
}
vec2i_t pos = vec2i(
(( projected.x + 1.0) / 2.0) * screen_size.x - size.x / 2,
((-projected.y + 1.0) / 2.0) * screen_size.y - size.y / 2
);
render_push_2d(pos, size, rgba(128, 128, 128, 128), target_reticle);
}
void hud_draw(ship_t *ship) {
// Current lap time
if (ship->lap >= 0) {
ui_draw_time(ship->lap_time, ui_scaled_pos(UI_POS_BOTTOM | UI_POS_LEFT, vec2i(16, -30)), UI_SIZE_16, UI_COLOR_DEFAULT);
for (int i = 0; i < ship->lap && i < NUM_LAPS-1; i++) {
ui_draw_time(g.lap_times[ship->pilot][i], ui_scaled_pos(UI_POS_BOTTOM | UI_POS_LEFT, vec2i(16, -45 - (10 * i))), UI_SIZE_8, UI_COLOR_ACCENT);
}
}
// Current Lap
int display_lap = max(0, ship->lap + 1);
ui_draw_text("LAP", ui_scaled(vec2i(15, 8)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number(display_lap, ui_scaled(vec2i(10, 19)), UI_SIZE_16, UI_COLOR_DEFAULT);
int width = ui_char_width('0' + display_lap, UI_SIZE_16);
ui_draw_text("OF", ui_scaled(vec2i((10 + width), 27)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number(NUM_LAPS, ui_scaled(vec2i((32 + width), 19)), UI_SIZE_16, UI_COLOR_DEFAULT);
// Race Position
if (g.race_type != RACE_TYPE_TIME_TRIAL) {
ui_draw_text("POSITION", ui_scaled_pos(UI_POS_TOP | UI_POS_RIGHT, vec2i(-90, 8)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number(ship->position_rank, ui_scaled_pos(UI_POS_TOP | UI_POS_RIGHT, vec2i(-60, 19)), UI_SIZE_16, UI_COLOR_DEFAULT);
}
// Framerate
if (save.show_fps) {
ui_draw_text("FPS", ui_scaled(vec2i(16, 78)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number((int)(g.frame_rate), ui_scaled(vec2i(16, 90)), UI_SIZE_8, UI_COLOR_DEFAULT);
}
// Lap Record
ui_draw_text("LAP RECORD", ui_scaled(vec2i(15, 43)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_time(save.highscores[g.race_class][g.circut][g.highscore_tab].lap_record, ui_scaled(vec2i(15, 55)), UI_SIZE_8, UI_COLOR_DEFAULT);
// Wrong way
if (flags_not(ship->flags, SHIP_DIRECTION_FORWARD)) {
ui_draw_text_centered("WRONG WAY", ui_scaled_pos(UI_POS_MIDDLE | UI_POS_CENTER, vec2i(-20, 0)), UI_SIZE_16, UI_COLOR_ACCENT);
}
// Speedo
int speedo_speed = (g.camera.update_func == camera_update_attract_internal)
? ship->speed * 7
: ship->speed;
hud_draw_speedo(speedo_speed, ship->thrust_mag);
// Weapon icon
if (ship->weapon_type != WEAPON_TYPE_NONE) {
vec2i_t pos = ui_scaled_pos(UI_POS_TOP | UI_POS_CENTER, vec2i(-16, 20));
vec2i_t size = ui_scaled(vec2i(32, 32));
uint16_t icon = texture_from_list(weapon_icon_textures, ship->weapon_type-1);
render_push_2d(pos, size, rgba(128,128,128,255), icon);
}
// Lives
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
for (int i = 0; i < g.lives; i++) {
ui_draw_icon(UI_ICON_STAR, ui_scaled_pos(UI_POS_BOTTOM | UI_POS_RIGHT, vec2i(-26 - 13 * i, -50)), UI_COLOR_DEFAULT);
}
}
// Weapon target reticle
if (ship->weapon_target) {
hud_draw_target_icon(ship->weapon_target->position);
}
}

View file

@ -1,9 +1,9 @@
#ifndef HUD_H
#define HUD_H
#include "ship.h"
void hud_load(void);
void hud_draw(ship_t *ship);
#endif
#ifndef HUD_H
#define HUD_H
#include "ship.h"
void hud_load(void);
void hud_draw(ship_t *ship);
#endif

View file

@ -1,322 +1,322 @@
#include "../types.h"
#include "../mem.h"
#include "../utils.h"
#include "../platform.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "droid.h"
#include "camera.h"
#include "object.h"
#include "scene.h"
#include "game.h"
#include "hud.h"
#include "image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#define TIM_TYPE_PALETTED_4_BPP 0x08
#define TIM_TYPE_PALETTED_8_BPP 0x09
#define TIM_TYPE_TRUE_COLOR_16_BPP 0x02
static inline rgba_t tim_16bit_to_rgba(uint16_t c, bool transparent_bit) {
return rgba(
((c >> 0) & 0x1f) << 3,
((c >> 5) & 0x1f) << 3,
((c >> 10) & 0x1f) << 3,
(c == 0
? 0x00
: transparent_bit && (c & 0x7fff) == 0 ? 0x00 : 0xff
)
);
}
image_t *image_alloc(uint32_t width, uint32_t height) {
image_t *image = mem_temp_alloc(sizeof(image_t) + width * height * sizeof(rgba_t));
image->width = width;
image->height = height;
image->pixels = (rgba_t *)(((uint8_t *)image) + sizeof(image_t));
return image;
}
image_t *image_load_from_bytes(uint8_t *bytes, bool transparent) {
uint32_t p = 0;
uint32_t magic = get_i32_le(bytes, &p);
uint32_t type = get_i32_le(bytes, &p);
rgba_t palette[256];
if (
type == TIM_TYPE_PALETTED_4_BPP ||
type == TIM_TYPE_PALETTED_8_BPP
) {
uint32_t header_length = get_i32_le(bytes, &p);
uint16_t palette_x = get_i16_le(bytes, &p);
uint16_t palette_y = get_i16_le(bytes, &p);
uint16_t palette_colors = get_i16_le(bytes, &p);
uint16_t palettes = get_i16_le(bytes, &p);
for (int i = 0; i < palette_colors; i++) {
palette[i] = tim_16bit_to_rgba(get_u16_le(bytes, &p), transparent);
}
}
uint32_t data_size = get_i32_le(bytes, &p);
int32_t pixels_per_16bit = 1;
if (type == TIM_TYPE_PALETTED_8_BPP) {
pixels_per_16bit = 2;
}
else if (type == TIM_TYPE_PALETTED_4_BPP) {
pixels_per_16bit = 4;
}
uint16_t skip_x = get_i16_le(bytes, &p);
uint16_t skip_y = get_i16_le(bytes, &p);
uint16_t entries_per_row = get_i16_le(bytes, &p);
uint16_t rows = get_i16_le(bytes, &p);
int32_t width = entries_per_row * pixels_per_16bit;
int32_t height = rows;
int32_t entries = entries_per_row * rows;
image_t *image = image_alloc(width, height);
int32_t pixel_pos = 0;
if (type == TIM_TYPE_TRUE_COLOR_16_BPP) {
for (int i = 0; i < entries; i++) {
image->pixels[pixel_pos++] = tim_16bit_to_rgba(get_u16_le(bytes, &p), transparent);
}
}
else if (type == TIM_TYPE_PALETTED_8_BPP) {
for (int i = 0; i < entries; i++) {
int32_t palette_pos = get_i16_le(bytes, &p);
image->pixels[pixel_pos++] = palette[(palette_pos >> 0) & 0xff];
image->pixels[pixel_pos++] = palette[(palette_pos >> 8) & 0xff];
}
}
else if (type == TIM_TYPE_PALETTED_4_BPP) {
for (int i = 0; i < entries; i++) {
int32_t palette_pos = get_i16_le(bytes, &p);
image->pixels[pixel_pos++] = palette[(palette_pos >> 0) & 0xf];
image->pixels[pixel_pos++] = palette[(palette_pos >> 4) & 0xf];
image->pixels[pixel_pos++] = palette[(palette_pos >> 8) & 0xf];
image->pixels[pixel_pos++] = palette[(palette_pos >> 12) & 0xf];
}
}
return image;
}
#define LZSS_INDEX_BIT_COUNT 13
#define LZSS_LENGTH_BIT_COUNT 4
#define LZSS_WINDOW_SIZE (1 << LZSS_INDEX_BIT_COUNT)
#define LZSS_BREAK_EVEN ((1 + LZSS_INDEX_BIT_COUNT + LZSS_LENGTH_BIT_COUNT) / 9)
#define LZSS_END_OF_STREAM 0
#define LZSS_MOD_WINDOW(a) ((a) & (LZSS_WINDOW_SIZE - 1))
void lzss_decompress(uint8_t *in_data, uint8_t *out_data) {
int16_t i;
int16_t current_position;
uint8_t cc;
int16_t match_length;
int16_t match_position;
uint32_t mask;
uint32_t return_value;
uint8_t in_bfile_mask;
int16_t in_bfile_rack;
int16_t value;
uint8_t window[LZSS_WINDOW_SIZE];
in_bfile_rack = 0;
in_bfile_mask = 0x80;
current_position = 1;
while (true) {
if (in_bfile_mask == 0x80) {
in_bfile_rack = (int16_t) * in_data++;
}
value = in_bfile_rack & in_bfile_mask;
in_bfile_mask >>= 1;
if (in_bfile_mask == 0) {
in_bfile_mask = 0x80;
}
if (value) {
mask = 1L << (8 - 1);
return_value = 0;
while (mask != 0) {
if (in_bfile_mask == 0x80) {
in_bfile_rack = (int16_t) * in_data++;
}
if (in_bfile_rack & in_bfile_mask) {
return_value |= mask;
}
mask >>= 1;
in_bfile_mask >>= 1;
if (in_bfile_mask == 0) {
in_bfile_mask = 0x80;
}
}
cc = (uint8_t) return_value;
*out_data++ = cc;
window[ current_position ] = cc;
current_position = LZSS_MOD_WINDOW(current_position + 1);
}
else {
mask = 1L << (LZSS_INDEX_BIT_COUNT - 1);
return_value = 0;
while (mask != 0) {
if (in_bfile_mask == 0x80) {
in_bfile_rack = (int16_t) * in_data++;
}
if (in_bfile_rack & in_bfile_mask) {
return_value |= mask;
}
mask >>= 1;
in_bfile_mask >>= 1;
if (in_bfile_mask == 0) {
in_bfile_mask = 0x80;
}
}
match_position = (int16_t) return_value;
if (match_position == LZSS_END_OF_STREAM) {
break;
}
mask = 1L << (LZSS_LENGTH_BIT_COUNT - 1);
return_value = 0;
while (mask != 0) {
if (in_bfile_mask == 0x80) {
in_bfile_rack = (int16_t) * in_data++;
}
if (in_bfile_rack & in_bfile_mask) {
return_value |= mask;
}
mask >>= 1;
in_bfile_mask >>= 1;
if (in_bfile_mask == 0) {
in_bfile_mask = 0x80;
}
}
match_length = (int16_t) return_value;
match_length += LZSS_BREAK_EVEN;
for (i = 0 ; i <= match_length ; i++) {
cc = window[LZSS_MOD_WINDOW(match_position + i)];
*out_data++ = cc;
window[current_position] = cc;
current_position = LZSS_MOD_WINDOW(current_position + 1);
}
}
}
}
cmp_t *image_load_compressed(char *name) {
printf("load cmp %s\n", name);
uint32_t compressed_size;
uint8_t *compressed_bytes = platform_load_asset(name, &compressed_size);
uint32_t p = 0;
int32_t decompressed_size = 0;
int32_t image_count = get_i32_le(compressed_bytes, &p);
// Calculate the total uncompressed size
for (int i = 0; i < image_count; i++) {
decompressed_size += get_i32_le(compressed_bytes, &p);
}
uint32_t struct_size = sizeof(cmp_t) + sizeof(uint8_t *) * image_count;
cmp_t *cmp = mem_temp_alloc(struct_size + decompressed_size);
cmp->len = image_count;
uint8_t *decompressed_bytes = ((uint8_t *)cmp) + struct_size;
// Rewind and load all offsets
p = 4;
uint32_t offset = 0;
for (int i = 0; i < image_count; i++) {
cmp->entries[i] = decompressed_bytes + offset;
offset += get_i32_le(compressed_bytes, &p);
}
lzss_decompress(compressed_bytes + p, decompressed_bytes);
mem_temp_free(compressed_bytes);
return cmp;
}
uint16_t image_get_texture(char *name) {
printf("load: %s\n", name);
uint32_t size;
uint8_t *bytes = platform_load_asset(name, &size);
image_t *image = image_load_from_bytes(bytes, false);
uint32_t texture_index = render_texture_create(image->width, image->height, image->pixels);
mem_temp_free(image);
mem_temp_free(bytes);
return texture_index;
}
uint16_t image_get_texture_semi_trans(char *name) {
printf("load: %s\n", name);
uint32_t size;
uint8_t *bytes = platform_load_asset(name, &size);
image_t *image = image_load_from_bytes(bytes, true);
uint32_t texture_index = render_texture_create(image->width, image->height, image->pixels);
mem_temp_free(image);
mem_temp_free(bytes);
return texture_index;
}
texture_list_t image_get_compressed_textures(char *name) {
cmp_t *cmp = image_load_compressed(name);
texture_list_t list = {.start = render_textures_len(), .len = cmp->len};
for (int i = 0; i < cmp->len; i++) {
int32_t width, height;
image_t *image = image_load_from_bytes(cmp->entries[i], false);
// char png_name[1024] = {0};
// sprintf(png_name, "%s.%d.png", name, i);
// stbi_write_png(png_name, image->width, image->height, 4, image->pixels, 0);
render_texture_create(image->width, image->height, image->pixels);
mem_temp_free(image);
}
mem_temp_free(cmp);
return list;
}
uint16_t texture_from_list(texture_list_t tl, uint16_t index) {
error_if(index >= tl.len, "Texture %d not in list of len %d", index, tl.len);
return tl.start + index;
}
void image_copy(image_t *src, image_t *dst, uint32_t sx, uint32_t sy, uint32_t sw, uint32_t sh, uint32_t dx, uint32_t dy) {
rgba_t *src_pixels = src->pixels + sy * src->width + sx;
rgba_t *dst_pixels = dst->pixels + dy * dst->width + dx;
for (uint32_t y = 0; y < sh; y++) {
for (uint32_t x = 0; x < sw; x++) {
*(dst_pixels++) = *(src_pixels++);
}
src_pixels += src->width - sw;
dst_pixels += dst->width - sw;
}
}
#include "../types.h"
#include "../mem.h"
#include "../utils.h"
#include "../platform.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "droid.h"
#include "camera.h"
#include "object.h"
#include "scene.h"
#include "game.h"
#include "hud.h"
#include "image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#define TIM_TYPE_PALETTED_4_BPP 0x08
#define TIM_TYPE_PALETTED_8_BPP 0x09
#define TIM_TYPE_TRUE_COLOR_16_BPP 0x02
static inline rgba_t tim_16bit_to_rgba(uint16_t c, bool transparent_bit) {
return rgba(
((c >> 0) & 0x1f) << 3,
((c >> 5) & 0x1f) << 3,
((c >> 10) & 0x1f) << 3,
(c == 0
? 0x00
: transparent_bit && (c & 0x7fff) == 0 ? 0x00 : 0xff
)
);
}
image_t *image_alloc(uint32_t width, uint32_t height) {
image_t *image = mem_temp_alloc(sizeof(image_t) + width * height * sizeof(rgba_t));
image->width = width;
image->height = height;
image->pixels = (rgba_t *)(((uint8_t *)image) + sizeof(image_t));
return image;
}
image_t *image_load_from_bytes(uint8_t *bytes, bool transparent) {
uint32_t p = 0;
uint32_t magic = get_i32_le(bytes, &p);
uint32_t type = get_i32_le(bytes, &p);
rgba_t palette[256];
if (
type == TIM_TYPE_PALETTED_4_BPP ||
type == TIM_TYPE_PALETTED_8_BPP
) {
uint32_t header_length = get_i32_le(bytes, &p);
uint16_t palette_x = get_i16_le(bytes, &p);
uint16_t palette_y = get_i16_le(bytes, &p);
uint16_t palette_colors = get_i16_le(bytes, &p);
uint16_t palettes = get_i16_le(bytes, &p);
for (int i = 0; i < palette_colors; i++) {
palette[i] = tim_16bit_to_rgba(get_u16_le(bytes, &p), transparent);
}
}
uint32_t data_size = get_i32_le(bytes, &p);
int32_t pixels_per_16bit = 1;
if (type == TIM_TYPE_PALETTED_8_BPP) {
pixels_per_16bit = 2;
}
else if (type == TIM_TYPE_PALETTED_4_BPP) {
pixels_per_16bit = 4;
}
uint16_t skip_x = get_i16_le(bytes, &p);
uint16_t skip_y = get_i16_le(bytes, &p);
uint16_t entries_per_row = get_i16_le(bytes, &p);
uint16_t rows = get_i16_le(bytes, &p);
int32_t width = entries_per_row * pixels_per_16bit;
int32_t height = rows;
int32_t entries = entries_per_row * rows;
image_t *image = image_alloc(width, height);
int32_t pixel_pos = 0;
if (type == TIM_TYPE_TRUE_COLOR_16_BPP) {
for (int i = 0; i < entries; i++) {
image->pixels[pixel_pos++] = tim_16bit_to_rgba(get_u16_le(bytes, &p), transparent);
}
}
else if (type == TIM_TYPE_PALETTED_8_BPP) {
for (int i = 0; i < entries; i++) {
int32_t palette_pos = get_i16_le(bytes, &p);
image->pixels[pixel_pos++] = palette[(palette_pos >> 0) & 0xff];
image->pixels[pixel_pos++] = palette[(palette_pos >> 8) & 0xff];
}
}
else if (type == TIM_TYPE_PALETTED_4_BPP) {
for (int i = 0; i < entries; i++) {
int32_t palette_pos = get_i16_le(bytes, &p);
image->pixels[pixel_pos++] = palette[(palette_pos >> 0) & 0xf];
image->pixels[pixel_pos++] = palette[(palette_pos >> 4) & 0xf];
image->pixels[pixel_pos++] = palette[(palette_pos >> 8) & 0xf];
image->pixels[pixel_pos++] = palette[(palette_pos >> 12) & 0xf];
}
}
return image;
}
#define LZSS_INDEX_BIT_COUNT 13
#define LZSS_LENGTH_BIT_COUNT 4
#define LZSS_WINDOW_SIZE (1 << LZSS_INDEX_BIT_COUNT)
#define LZSS_BREAK_EVEN ((1 + LZSS_INDEX_BIT_COUNT + LZSS_LENGTH_BIT_COUNT) / 9)
#define LZSS_END_OF_STREAM 0
#define LZSS_MOD_WINDOW(a) ((a) & (LZSS_WINDOW_SIZE - 1))
void lzss_decompress(uint8_t *in_data, uint8_t *out_data) {
int16_t i;
int16_t current_position;
uint8_t cc;
int16_t match_length;
int16_t match_position;
uint32_t mask;
uint32_t return_value;
uint8_t in_bfile_mask;
int16_t in_bfile_rack;
int16_t value;
uint8_t window[LZSS_WINDOW_SIZE];
in_bfile_rack = 0;
in_bfile_mask = 0x80;
current_position = 1;
while (true) {
if (in_bfile_mask == 0x80) {
in_bfile_rack = (int16_t) * in_data++;
}
value = in_bfile_rack & in_bfile_mask;
in_bfile_mask >>= 1;
if (in_bfile_mask == 0) {
in_bfile_mask = 0x80;
}
if (value) {
mask = 1L << (8 - 1);
return_value = 0;
while (mask != 0) {
if (in_bfile_mask == 0x80) {
in_bfile_rack = (int16_t) * in_data++;
}
if (in_bfile_rack & in_bfile_mask) {
return_value |= mask;
}
mask >>= 1;
in_bfile_mask >>= 1;
if (in_bfile_mask == 0) {
in_bfile_mask = 0x80;
}
}
cc = (uint8_t) return_value;
*out_data++ = cc;
window[ current_position ] = cc;
current_position = LZSS_MOD_WINDOW(current_position + 1);
}
else {
mask = 1L << (LZSS_INDEX_BIT_COUNT - 1);
return_value = 0;
while (mask != 0) {
if (in_bfile_mask == 0x80) {
in_bfile_rack = (int16_t) * in_data++;
}
if (in_bfile_rack & in_bfile_mask) {
return_value |= mask;
}
mask >>= 1;
in_bfile_mask >>= 1;
if (in_bfile_mask == 0) {
in_bfile_mask = 0x80;
}
}
match_position = (int16_t) return_value;
if (match_position == LZSS_END_OF_STREAM) {
break;
}
mask = 1L << (LZSS_LENGTH_BIT_COUNT - 1);
return_value = 0;
while (mask != 0) {
if (in_bfile_mask == 0x80) {
in_bfile_rack = (int16_t) * in_data++;
}
if (in_bfile_rack & in_bfile_mask) {
return_value |= mask;
}
mask >>= 1;
in_bfile_mask >>= 1;
if (in_bfile_mask == 0) {
in_bfile_mask = 0x80;
}
}
match_length = (int16_t) return_value;
match_length += LZSS_BREAK_EVEN;
for (i = 0 ; i <= match_length ; i++) {
cc = window[LZSS_MOD_WINDOW(match_position + i)];
*out_data++ = cc;
window[current_position] = cc;
current_position = LZSS_MOD_WINDOW(current_position + 1);
}
}
}
}
cmp_t *image_load_compressed(char *name) {
printf("load cmp %s\n", name);
uint32_t compressed_size;
uint8_t *compressed_bytes = platform_load_asset(name, &compressed_size);
uint32_t p = 0;
int32_t decompressed_size = 0;
int32_t image_count = get_i32_le(compressed_bytes, &p);
// Calculate the total uncompressed size
for (int i = 0; i < image_count; i++) {
decompressed_size += get_i32_le(compressed_bytes, &p);
}
uint32_t struct_size = sizeof(cmp_t) + sizeof(uint8_t *) * image_count;
cmp_t *cmp = mem_temp_alloc(struct_size + decompressed_size);
cmp->len = image_count;
uint8_t *decompressed_bytes = ((uint8_t *)cmp) + struct_size;
// Rewind and load all offsets
p = 4;
uint32_t offset = 0;
for (int i = 0; i < image_count; i++) {
cmp->entries[i] = decompressed_bytes + offset;
offset += get_i32_le(compressed_bytes, &p);
}
lzss_decompress(compressed_bytes + p, decompressed_bytes);
mem_temp_free(compressed_bytes);
return cmp;
}
uint16_t image_get_texture(char *name) {
printf("load: %s\n", name);
uint32_t size;
uint8_t *bytes = platform_load_asset(name, &size);
image_t *image = image_load_from_bytes(bytes, false);
uint32_t texture_index = render_texture_create(image->width, image->height, image->pixels);
mem_temp_free(image);
mem_temp_free(bytes);
return texture_index;
}
uint16_t image_get_texture_semi_trans(char *name) {
printf("load: %s\n", name);
uint32_t size;
uint8_t *bytes = platform_load_asset(name, &size);
image_t *image = image_load_from_bytes(bytes, true);
uint32_t texture_index = render_texture_create(image->width, image->height, image->pixels);
mem_temp_free(image);
mem_temp_free(bytes);
return texture_index;
}
texture_list_t image_get_compressed_textures(char *name) {
cmp_t *cmp = image_load_compressed(name);
texture_list_t list = {.start = render_textures_len(), .len = cmp->len};
for (int i = 0; i < cmp->len; i++) {
int32_t width, height;
image_t *image = image_load_from_bytes(cmp->entries[i], false);
// char png_name[1024] = {0};
// sprintf(png_name, "%s.%d.png", name, i);
// stbi_write_png(png_name, image->width, image->height, 4, image->pixels, 0);
render_texture_create(image->width, image->height, image->pixels);
mem_temp_free(image);
}
mem_temp_free(cmp);
return list;
}
uint16_t texture_from_list(texture_list_t tl, uint16_t index) {
error_if(index >= tl.len, "Texture %d not in list of len %d", index, tl.len);
return tl.start + index;
}
void image_copy(image_t *src, image_t *dst, uint32_t sx, uint32_t sy, uint32_t sw, uint32_t sh, uint32_t dx, uint32_t dy) {
rgba_t *src_pixels = src->pixels + sy * src->width + sx;
rgba_t *dst_pixels = dst->pixels + dy * dst->width + dx;
for (uint32_t y = 0; y < sh; y++) {
for (uint32_t x = 0; x < sw; x++) {
*(dst_pixels++) = *(src_pixels++);
}
src_pixels += src->width - sw;
dst_pixels += dst->width - sw;
}
}

View file

@ -1,34 +1,34 @@
#ifndef INIT_H
#define INIT_H
#include "../types.h"
typedef struct {
uint16_t start;
uint16_t len;
} texture_list_t;
#define texture_list_empty() ((texture_list_t){0, 0})
typedef struct {
uint32_t width;
uint32_t height;
rgba_t *pixels;
} image_t;
typedef struct {
uint32_t len;
uint8_t *entries[];
} cmp_t;
image_t *image_alloc(uint32_t width, uint32_t height);
void image_copy(image_t *src, image_t *dst, uint32_t sx, uint32_t sy, uint32_t sw, uint32_t sh, uint32_t dx, uint32_t dy);
image_t *image_load_from_bytes(uint8_t *bytes, bool transparent);
cmp_t *image_load_compressed(char *name);
uint16_t image_get_texture(char *name);
uint16_t image_get_texture_semi_trans(char *name);
texture_list_t image_get_compressed_textures(char *name);
uint16_t texture_from_list(texture_list_t tl, uint16_t index);
#ifndef INIT_H
#define INIT_H
#include "../types.h"
typedef struct {
uint16_t start;
uint16_t len;
} texture_list_t;
#define texture_list_empty() ((texture_list_t){0, 0})
typedef struct {
uint32_t width;
uint32_t height;
rgba_t *pixels;
} image_t;
typedef struct {
uint32_t len;
uint8_t *entries[];
} cmp_t;
image_t *image_alloc(uint32_t width, uint32_t height);
void image_copy(image_t *src, image_t *dst, uint32_t sx, uint32_t sy, uint32_t sw, uint32_t sh, uint32_t dx, uint32_t dy);
image_t *image_load_from_bytes(uint8_t *bytes, bool transparent);
cmp_t *image_load_compressed(char *name);
uint16_t image_get_texture(char *name);
uint16_t image_get_texture_semi_trans(char *name);
texture_list_t image_get_compressed_textures(char *name);
uint16_t texture_from_list(texture_list_t tl, uint16_t index);
#endif

View file

@ -1,486 +1,486 @@
#include <string.h>
#include "../input.h"
#include "../system.h"
#include "../utils.h"
#include "../mem.h"
#include "menu.h"
#include "ingame_menus.h"
#include "game.h"
#include "image.h"
#include "ui.h"
#include "race.h"
static void page_race_points_init(menu_t * menu);
static void page_championship_points_init(menu_t * menu);
static void page_hall_of_fame_init(menu_t * menu);
static texture_list_t pilot_portraits;
static menu_t *ingame_menu;
void ingame_menus_load(void) {
pilot_portraits = image_get_compressed_textures(def.pilots[g.pilot].portrait);
ingame_menu = mem_bump(sizeof(menu_t));
}
// -----------------------------------------------------------------------------
// Pause Menu
static void button_continue(menu_t *menu, int data) {
race_unpause();
}
static void button_restart_confirm(menu_t *menu, int data) {
if (data) {
race_restart();
}
else {
menu_pop(menu);
}
}
static void button_restart_or_quit(menu_t *menu, int data) {
if (data) {
race_restart();
}
else {
game_set_scene(GAME_SCENE_MAIN_MENU);
}
}
static void button_restart(menu_t *menu, int data) {
menu_confirm(menu, "ARE YOU SURE YOU", "WANT TO RESTART", "YES", "NO", button_restart_confirm);
}
static void button_quit_confirm(menu_t *menu, int data) {
if (data) {
game_set_scene(GAME_SCENE_MAIN_MENU);
}
else {
menu_pop(menu);
}
}
static void button_quit(menu_t *menu, int data) {
menu_confirm(menu, "ARE YOU SURE YOU", "WANT TO QUIT", "YES", "NO", button_quit_confirm);
}
static void button_music_track(menu_t *menu, int data) {
sfx_music_play(data);
sfx_music_mode(SFX_MUSIC_LOOP);
}
static void button_music_random(menu_t *menu, int data) {
sfx_music_play(rand_int(0, len(def.music)));
sfx_music_mode(SFX_MUSIC_RANDOM);
}
static void button_music(menu_t *menu, int data) {
menu_page_t *page = menu_push(menu, "MUSIC", NULL);
for (int i = 0; i < len(def.music); i++) {
menu_page_add_button(page, i, def.music[i].name, button_music_track);
}
menu_page_add_button(page, 0, "RANDOM", button_music_random);
}
menu_t *pause_menu_init(void) {
sfx_play(SFX_MENU_SELECT);
menu_reset(ingame_menu);
menu_page_t *page = menu_push(ingame_menu, "PAUSED", NULL);
menu_page_add_button(page, 0, "CONTINUE", button_continue);
menu_page_add_button(page, 0, "RESTART", button_restart);
menu_page_add_button(page, 0, "QUIT", button_quit);
menu_page_add_button(page, 0, "MUSIC", button_music);
return ingame_menu;
}
// -----------------------------------------------------------------------------
// Game Over
menu_t *game_over_menu_init(void) {
sfx_play(SFX_MENU_SELECT);
menu_reset(ingame_menu);
menu_page_t *page = menu_push(ingame_menu, "GAME OVER", NULL);
menu_page_add_button(page, 1, "", button_quit_confirm);
return ingame_menu;
}
// -----------------------------------------------------------------------------
// Race Stats
static void button_qualify_confirm(menu_t *menu, int data) {
if (data) {
race_restart();
}
else {
game_set_scene(GAME_SCENE_MAIN_MENU);
}
}
static void button_race_stats_continue(menu_t *menu, int data) {
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
if (g.race_position <= QUALIFYING_RANK) {
page_race_points_init(menu);
}
else {
menu_page_t *page = menu_confirm(menu, "CONTINUE QUALIFYING OR QUIT", "", "QUALIFY", "QUIT", button_qualify_confirm);
page->index = 0;
}
}
else {
if (g.is_new_race_record) {
page_hall_of_fame_init(menu);
}
else {
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_restart_or_quit);
}
}
}
static void page_race_stats_draw(menu_t *menu, int data) {
menu_page_t *page = &menu->pages[menu->index];
vec2i_t pos = page->title_pos;
pos.x -= 140;
pos.y += 32;
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER;
// Pilot portrait and race position - only for championship or single race
if (g.race_type != RACE_TYPE_TIME_TRIAL) {
vec2i_t image_pos = ui_scaled_pos(anchor, vec2i(pos.x + 180, pos.y));
uint16_t image = texture_from_list(pilot_portraits, g.race_position <= QUALIFYING_RANK ? 1 : 0);
render_push_2d(image_pos, ui_scaled(render_texture_size(image)), rgba(0, 0, 0, 128), RENDER_NO_TEXTURE);
ui_draw_image(image_pos, image);
ui_draw_text("RACE POSITION", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number(g.race_position, ui_scaled_pos(anchor, vec2i(pos.x + ui_text_width("RACE POSITION", UI_SIZE_8)+8, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT);
}
pos.y += 32;
ui_draw_text("RACE STATISTICS", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
pos.y += 16;
for (int i = 0; i < NUM_LAPS; i++) {
ui_draw_text("LAP", ui_scaled_pos(anchor, vec2i(pos.x + 8, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number(i+1, ui_scaled_pos(anchor, vec2i(pos.x + 50, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_time(g.lap_times[g.pilot][i], ui_scaled_pos(anchor, vec2i(pos.x + 72, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT);
pos.y+= 12;
}
pos.y += 32;
ui_draw_text("RACE TIME", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
pos.y += 12;
ui_draw_time(g.race_time, ui_scaled_pos(anchor, vec2i(pos.x + 8, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT);
pos.y += 12;
ui_draw_text("BEST LAP", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
pos.y += 12;
ui_draw_time(g.best_lap, ui_scaled_pos(anchor, vec2i(pos.x + 8, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT);
pos.y += 12;
}
menu_t *race_stats_menu_init(void) {
sfx_play(SFX_MENU_SELECT);
menu_reset(ingame_menu);
char *title;
if (g.race_type == RACE_TYPE_TIME_TRIAL) {
title = "";
}
else if (g.race_position <= QUALIFYING_RANK) {
title = "CONGRATULATIONS";
}
else {
title = "FAILED TO QUALIFY";
}
menu_page_t *page = menu_push(ingame_menu, title, page_race_stats_draw);
flags_add(page->layout_flags, MENU_FIXED);
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->title_pos = vec2i(0, -100);
menu_page_add_button(page, 1, "", button_race_stats_continue);
return ingame_menu;
}
// -----------------------------------------------------------------------------
// Race Table
static void button_race_points_continue(menu_t *menu, int data) {
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
page_championship_points_init(menu);
}
else if (g.is_new_race_record) {
page_hall_of_fame_init(menu);
}
else {
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_restart_or_quit);
}
}
static void page_race_points_draw(menu_t *menu, int data) {
menu_page_t *page = &menu->pages[menu->index];
vec2i_t pos = page->title_pos;
pos.x -= 140;
pos.y += 32;
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER;
ui_draw_text("PILOT NAME", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_text("POINTS", ui_scaled_pos(anchor, vec2i(pos.x + 222, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT);
pos.y += 24;
for (int i = 0; i < len(g.race_ranks); i++) {
rgba_t color = g.race_ranks[i].pilot == g.pilot ? UI_COLOR_ACCENT : UI_COLOR_DEFAULT;
ui_draw_text(def.pilots[g.race_ranks[i].pilot].name, ui_scaled_pos(anchor, pos), UI_SIZE_8, color);
int w = ui_number_width(g.race_ranks[i].points, UI_SIZE_8);
ui_draw_number(g.race_ranks[i].points, ui_scaled_pos(anchor, vec2i(pos.x + 280 - w, pos.y)), UI_SIZE_8, color);
pos.y += 12;
}
}
static void page_race_points_init(menu_t *menu) {
menu_page_t *page = menu_push(menu, "RACE POINTS", page_race_points_draw);
flags_add(page->layout_flags, MENU_FIXED);
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->title_pos = vec2i(0, -100);
menu_page_add_button(page, 1, "", button_race_points_continue);
}
// -----------------------------------------------------------------------------
// Championship Table
static void button_championship_points_continue(menu_t *menu, int data) {
if (g.is_new_race_record) {
page_hall_of_fame_init(menu);
}
else if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
race_next();
}
else {
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_quit_confirm);
}
}
static void page_championship_points_draw(menu_t *menu, int data) {
menu_page_t *page = &menu->pages[menu->index];
vec2i_t pos = page->title_pos;
pos.x -= 140;
pos.y += 32;
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER;
ui_draw_text("PILOT NAME", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_text("POINTS", ui_scaled_pos(anchor, vec2i(pos.x + 222, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT);
pos.y += 24;
for (int i = 0; i < len(g.championship_ranks); i++) {
rgba_t color = g.championship_ranks[i].pilot == g.pilot ? UI_COLOR_ACCENT : UI_COLOR_DEFAULT;
ui_draw_text(def.pilots[g.championship_ranks[i].pilot].name, ui_scaled_pos(anchor, pos), UI_SIZE_8, color);
int w = ui_number_width(g.championship_ranks[i].points, UI_SIZE_8);
ui_draw_number(g.championship_ranks[i].points, ui_scaled_pos(anchor, vec2i(pos.x + 280 - w, pos.y)), UI_SIZE_8, color);
pos.y += 12;
}
}
static void page_championship_points_init(menu_t *menu) {
menu_page_t *page = menu_push(menu, "CHAMPIONSHIP TABLE", page_championship_points_draw);
flags_add(page->layout_flags, MENU_FIXED);
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->title_pos = vec2i(0, -100);
menu_page_add_button(page, 1, "", button_championship_points_continue);
}
// -----------------------------------------------------------------------------
// Hall of Fame
static highscores_entry_t hs_new_entry = {
.time = 0,
.name = ""
};
static const char *hs_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static int hs_char_index = 0;
static bool hs_entry_complete = false;
static void hall_of_fame_draw_name_entry(menu_t *menu, ui_pos_t anchor, vec2i_t pos) {
int entry_len = strlen(hs_new_entry.name);
int entry_width = ui_text_width(hs_new_entry.name, UI_SIZE_16);
vec2i_t c_pos = ui_scaled_pos(anchor, vec2i(pos.x + entry_width, pos.y));
int c_first = 0;
int c_last = 38;
if (entry_len == 0) {
c_last = 37;
}
else if (entry_len == 3) {
c_first = 36;
}
if (input_pressed(A_MENU_UP)) {
hs_char_index++;
}
else if (input_pressed(A_MENU_DOWN)) {
hs_char_index--;
}
if (hs_char_index < c_first) {
hs_char_index = c_last-1;
}
if (hs_char_index >= c_last) {
hs_char_index = c_first;
}
// DEL
if (hs_char_index == 36) {
ui_draw_icon(UI_ICON_DEL, c_pos, UI_COLOR_ACCENT);
if (input_pressed(A_MENU_SELECT)) {
sfx_play(SFX_MENU_SELECT);
if (entry_len > 0) {
hs_new_entry.name[entry_len-1] = '\0';
}
}
}
// END
else if (hs_char_index == 37) {
ui_draw_icon(UI_ICON_END, c_pos, UI_COLOR_ACCENT);
if (input_pressed(A_MENU_SELECT)) {
hs_entry_complete = true;
}
}
// A-Z, 0-9
else {
char selector[2] = {hs_charset[hs_char_index], '\0'};
ui_draw_text(selector, c_pos, UI_SIZE_16, UI_COLOR_ACCENT);
if (input_pressed(A_MENU_SELECT)) {
sfx_play(SFX_MENU_SELECT);
hs_new_entry.name[entry_len] = hs_charset[hs_char_index];
hs_new_entry.name[entry_len+1] = '\0';
}
}
ui_draw_text(hs_new_entry.name, ui_scaled_pos(anchor, pos), UI_SIZE_16, UI_COLOR_ACCENT);
}
static void page_hall_of_fame_draw(menu_t *menu, int data) {
// FIXME: doing this all in the draw() function leads to all kinds of
// complications
highscores_t *hs = &save.highscores[g.race_class][g.circut][g.highscore_tab];
if (hs_entry_complete) {
sfx_play(SFX_MENU_SELECT);
strncpy(save.highscores_name, hs_new_entry.name, 4);
save.is_dirty = true;
// Insert new highscore entry into the save struct
highscores_entry_t temp_entry = hs->entries[0];
for (int i = 0; i < NUM_HIGHSCORES; i++) {
if (hs_new_entry.time < hs->entries[i].time) {
for (int j = NUM_HIGHSCORES - 2; j >= i; j--) {
hs->entries[j+1] = hs->entries[j];
}
hs->entries[i] = hs_new_entry;
break;
}
}
save.is_dirty = true;
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
race_next();
}
else {
menu_reset(menu); // Can't go back!
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_restart_or_quit);
}
return;
}
menu_page_t *page = &menu->pages[menu->index];
vec2i_t pos = page->title_pos;
pos.x -= 120;
pos.y += 48;
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER;
bool has_shown_new_entry = false;
for (int i = 0, j = 0; i < NUM_HIGHSCORES; i++, j++) {
if (!has_shown_new_entry && hs_new_entry.time < hs->entries[i].time) {
hall_of_fame_draw_name_entry(menu, anchor, pos);
ui_draw_time(hs_new_entry.time, ui_scaled_pos(anchor, vec2i(pos.x + 120, pos.y)), UI_SIZE_16, UI_COLOR_DEFAULT);
has_shown_new_entry = true;
j--;
}
else {
ui_draw_text(hs->entries[j].name, ui_scaled_pos(anchor, pos), UI_SIZE_16, UI_COLOR_DEFAULT);
ui_draw_time(hs->entries[j].time, ui_scaled_pos(anchor, vec2i(pos.x + 120, pos.y)), UI_SIZE_16, UI_COLOR_DEFAULT);
}
pos.y += 24;
}
}
static void page_hall_of_fame_init(menu_t *menu) {
menu_reset(menu); // Can't go back!
menu_page_t *page = menu_push(menu, "HALL OF FAME", page_hall_of_fame_draw);
flags_add(page->layout_flags, MENU_FIXED);
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->title_pos = vec2i(0, -100);
hs_new_entry.time = g.race_time;
strncpy(hs_new_entry.name, save.highscores_name, 4);
hs_char_index = 0;
hs_entry_complete = false;
}
// -----------------------------------------------------------------------------
// Text scroller
static char * const *text_scroll_lines;
static int text_scroll_lines_len;
static double text_scroll_start_time;
static void text_scroll_menu_draw(menu_t *menu, int data) {
double time = system_time() - text_scroll_start_time;
int scale = ui_get_scale();
int speed = 32;
vec2i_t screen = render_size();
vec2i_t pos = vec2i(screen.x / 2, screen.y - time * scale * speed);
for (int i = 0; i < text_scroll_lines_len; i++) {
const char *line = text_scroll_lines[i];
if (line[0] == '#') {
pos.y += 48 * scale;
ui_draw_text_centered(line + 1, pos, UI_SIZE_16, UI_COLOR_ACCENT);
pos.y += 32 * scale;
}
else {
ui_draw_text_centered(line, pos, UI_SIZE_8, UI_COLOR_DEFAULT);
pos.y += 12 * scale;
}
}
}
menu_t *text_scroll_menu_init(char * const *lines, int len) {
text_scroll_lines = lines;
text_scroll_lines_len = len;
text_scroll_start_time = system_time();
menu_reset(ingame_menu);
menu_page_t *page = menu_push(ingame_menu, "", text_scroll_menu_draw);
menu_page_add_button(page, 1, "", button_quit_confirm);
return ingame_menu;
}
#include <string.h>
#include "../input.h"
#include "../system.h"
#include "../utils.h"
#include "../mem.h"
#include "menu.h"
#include "ingame_menus.h"
#include "game.h"
#include "image.h"
#include "ui.h"
#include "race.h"
static void page_race_points_init(menu_t * menu);
static void page_championship_points_init(menu_t * menu);
static void page_hall_of_fame_init(menu_t * menu);
static texture_list_t pilot_portraits;
static menu_t *ingame_menu;
void ingame_menus_load(void) {
pilot_portraits = image_get_compressed_textures(def.pilots[g.pilot].portrait);
ingame_menu = mem_bump(sizeof(menu_t));
}
// -----------------------------------------------------------------------------
// Pause Menu
static void button_continue(menu_t *menu, int data) {
race_unpause();
}
static void button_restart_confirm(menu_t *menu, int data) {
if (data) {
race_restart();
}
else {
menu_pop(menu);
}
}
static void button_restart_or_quit(menu_t *menu, int data) {
if (data) {
race_restart();
}
else {
game_set_scene(GAME_SCENE_MAIN_MENU);
}
}
static void button_restart(menu_t *menu, int data) {
menu_confirm(menu, "ARE YOU SURE YOU", "WANT TO RESTART", "YES", "NO", button_restart_confirm);
}
static void button_quit_confirm(menu_t *menu, int data) {
if (data) {
game_set_scene(GAME_SCENE_MAIN_MENU);
}
else {
menu_pop(menu);
}
}
static void button_quit(menu_t *menu, int data) {
menu_confirm(menu, "ARE YOU SURE YOU", "WANT TO QUIT", "YES", "NO", button_quit_confirm);
}
static void button_music_track(menu_t *menu, int data) {
sfx_music_play(data);
sfx_music_mode(SFX_MUSIC_LOOP);
}
static void button_music_random(menu_t *menu, int data) {
sfx_music_play(rand_int(0, len(def.music)));
sfx_music_mode(SFX_MUSIC_RANDOM);
}
static void button_music(menu_t *menu, int data) {
menu_page_t *page = menu_push(menu, "MUSIC", NULL);
for (int i = 0; i < len(def.music); i++) {
menu_page_add_button(page, i, def.music[i].name, button_music_track);
}
menu_page_add_button(page, 0, "RANDOM", button_music_random);
}
menu_t *pause_menu_init(void) {
sfx_play(SFX_MENU_SELECT);
menu_reset(ingame_menu);
menu_page_t *page = menu_push(ingame_menu, "PAUSED", NULL);
menu_page_add_button(page, 0, "CONTINUE", button_continue);
menu_page_add_button(page, 0, "RESTART", button_restart);
menu_page_add_button(page, 0, "QUIT", button_quit);
menu_page_add_button(page, 0, "MUSIC", button_music);
return ingame_menu;
}
// -----------------------------------------------------------------------------
// Game Over
menu_t *game_over_menu_init(void) {
sfx_play(SFX_MENU_SELECT);
menu_reset(ingame_menu);
menu_page_t *page = menu_push(ingame_menu, "GAME OVER", NULL);
menu_page_add_button(page, 1, "", button_quit_confirm);
return ingame_menu;
}
// -----------------------------------------------------------------------------
// Race Stats
static void button_qualify_confirm(menu_t *menu, int data) {
if (data) {
race_restart();
}
else {
game_set_scene(GAME_SCENE_MAIN_MENU);
}
}
static void button_race_stats_continue(menu_t *menu, int data) {
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
if (g.race_position <= QUALIFYING_RANK) {
page_race_points_init(menu);
}
else {
menu_page_t *page = menu_confirm(menu, "CONTINUE QUALIFYING OR QUIT", "", "QUALIFY", "QUIT", button_qualify_confirm);
page->index = 0;
}
}
else {
if (g.is_new_race_record) {
page_hall_of_fame_init(menu);
}
else {
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_restart_or_quit);
}
}
}
static void page_race_stats_draw(menu_t *menu, int data) {
menu_page_t *page = &menu->pages[menu->index];
vec2i_t pos = page->title_pos;
pos.x -= 140;
pos.y += 32;
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER;
// Pilot portrait and race position - only for championship or single race
if (g.race_type != RACE_TYPE_TIME_TRIAL) {
vec2i_t image_pos = ui_scaled_pos(anchor, vec2i(pos.x + 180, pos.y));
uint16_t image = texture_from_list(pilot_portraits, g.race_position <= QUALIFYING_RANK ? 1 : 0);
render_push_2d(image_pos, ui_scaled(render_texture_size(image)), rgba(0, 0, 0, 128), RENDER_NO_TEXTURE);
ui_draw_image(image_pos, image);
ui_draw_text("RACE POSITION", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number(g.race_position, ui_scaled_pos(anchor, vec2i(pos.x + ui_text_width("RACE POSITION", UI_SIZE_8)+8, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT);
}
pos.y += 32;
ui_draw_text("RACE STATISTICS", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
pos.y += 16;
for (int i = 0; i < NUM_LAPS; i++) {
ui_draw_text("LAP", ui_scaled_pos(anchor, vec2i(pos.x + 8, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_number(i+1, ui_scaled_pos(anchor, vec2i(pos.x + 50, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_time(g.lap_times[g.pilot][i], ui_scaled_pos(anchor, vec2i(pos.x + 72, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT);
pos.y+= 12;
}
pos.y += 32;
ui_draw_text("RACE TIME", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
pos.y += 12;
ui_draw_time(g.race_time, ui_scaled_pos(anchor, vec2i(pos.x + 8, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT);
pos.y += 12;
ui_draw_text("BEST LAP", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
pos.y += 12;
ui_draw_time(g.best_lap, ui_scaled_pos(anchor, vec2i(pos.x + 8, pos.y)), UI_SIZE_8, UI_COLOR_DEFAULT);
pos.y += 12;
}
menu_t *race_stats_menu_init(void) {
sfx_play(SFX_MENU_SELECT);
menu_reset(ingame_menu);
char *title;
if (g.race_type == RACE_TYPE_TIME_TRIAL) {
title = "";
}
else if (g.race_position <= QUALIFYING_RANK) {
title = "CONGRATULATIONS";
}
else {
title = "FAILED TO QUALIFY";
}
menu_page_t *page = menu_push(ingame_menu, title, page_race_stats_draw);
flags_add(page->layout_flags, MENU_FIXED);
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->title_pos = vec2i(0, -100);
menu_page_add_button(page, 1, "", button_race_stats_continue);
return ingame_menu;
}
// -----------------------------------------------------------------------------
// Race Table
static void button_race_points_continue(menu_t *menu, int data) {
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
page_championship_points_init(menu);
}
else if (g.is_new_race_record) {
page_hall_of_fame_init(menu);
}
else {
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_restart_or_quit);
}
}
static void page_race_points_draw(menu_t *menu, int data) {
menu_page_t *page = &menu->pages[menu->index];
vec2i_t pos = page->title_pos;
pos.x -= 140;
pos.y += 32;
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER;
ui_draw_text("PILOT NAME", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_text("POINTS", ui_scaled_pos(anchor, vec2i(pos.x + 222, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT);
pos.y += 24;
for (int i = 0; i < len(g.race_ranks); i++) {
rgba_t color = g.race_ranks[i].pilot == g.pilot ? UI_COLOR_ACCENT : UI_COLOR_DEFAULT;
ui_draw_text(def.pilots[g.race_ranks[i].pilot].name, ui_scaled_pos(anchor, pos), UI_SIZE_8, color);
int w = ui_number_width(g.race_ranks[i].points, UI_SIZE_8);
ui_draw_number(g.race_ranks[i].points, ui_scaled_pos(anchor, vec2i(pos.x + 280 - w, pos.y)), UI_SIZE_8, color);
pos.y += 12;
}
}
static void page_race_points_init(menu_t *menu) {
menu_page_t *page = menu_push(menu, "RACE POINTS", page_race_points_draw);
flags_add(page->layout_flags, MENU_FIXED);
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->title_pos = vec2i(0, -100);
menu_page_add_button(page, 1, "", button_race_points_continue);
}
// -----------------------------------------------------------------------------
// Championship Table
static void button_championship_points_continue(menu_t *menu, int data) {
if (g.is_new_race_record) {
page_hall_of_fame_init(menu);
}
else if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
race_next();
}
else {
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_quit_confirm);
}
}
static void page_championship_points_draw(menu_t *menu, int data) {
menu_page_t *page = &menu->pages[menu->index];
vec2i_t pos = page->title_pos;
pos.x -= 140;
pos.y += 32;
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER;
ui_draw_text("PILOT NAME", ui_scaled_pos(anchor, pos), UI_SIZE_8, UI_COLOR_ACCENT);
ui_draw_text("POINTS", ui_scaled_pos(anchor, vec2i(pos.x + 222, pos.y)), UI_SIZE_8, UI_COLOR_ACCENT);
pos.y += 24;
for (int i = 0; i < len(g.championship_ranks); i++) {
rgba_t color = g.championship_ranks[i].pilot == g.pilot ? UI_COLOR_ACCENT : UI_COLOR_DEFAULT;
ui_draw_text(def.pilots[g.championship_ranks[i].pilot].name, ui_scaled_pos(anchor, pos), UI_SIZE_8, color);
int w = ui_number_width(g.championship_ranks[i].points, UI_SIZE_8);
ui_draw_number(g.championship_ranks[i].points, ui_scaled_pos(anchor, vec2i(pos.x + 280 - w, pos.y)), UI_SIZE_8, color);
pos.y += 12;
}
}
static void page_championship_points_init(menu_t *menu) {
menu_page_t *page = menu_push(menu, "CHAMPIONSHIP TABLE", page_championship_points_draw);
flags_add(page->layout_flags, MENU_FIXED);
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->title_pos = vec2i(0, -100);
menu_page_add_button(page, 1, "", button_championship_points_continue);
}
// -----------------------------------------------------------------------------
// Hall of Fame
static highscores_entry_t hs_new_entry = {
.time = 0,
.name = ""
};
static const char *hs_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static int hs_char_index = 0;
static bool hs_entry_complete = false;
static void hall_of_fame_draw_name_entry(menu_t *menu, ui_pos_t anchor, vec2i_t pos) {
int entry_len = strlen(hs_new_entry.name);
int entry_width = ui_text_width(hs_new_entry.name, UI_SIZE_16);
vec2i_t c_pos = ui_scaled_pos(anchor, vec2i(pos.x + entry_width, pos.y));
int c_first = 0;
int c_last = 38;
if (entry_len == 0) {
c_last = 37;
}
else if (entry_len == 3) {
c_first = 36;
}
if (input_pressed(A_MENU_UP)) {
hs_char_index++;
}
else if (input_pressed(A_MENU_DOWN)) {
hs_char_index--;
}
if (hs_char_index < c_first) {
hs_char_index = c_last-1;
}
if (hs_char_index >= c_last) {
hs_char_index = c_first;
}
// DEL
if (hs_char_index == 36) {
ui_draw_icon(UI_ICON_DEL, c_pos, UI_COLOR_ACCENT);
if (input_pressed(A_MENU_SELECT)) {
sfx_play(SFX_MENU_SELECT);
if (entry_len > 0) {
hs_new_entry.name[entry_len-1] = '\0';
}
}
}
// END
else if (hs_char_index == 37) {
ui_draw_icon(UI_ICON_END, c_pos, UI_COLOR_ACCENT);
if (input_pressed(A_MENU_SELECT)) {
hs_entry_complete = true;
}
}
// A-Z, 0-9
else {
char selector[2] = {hs_charset[hs_char_index], '\0'};
ui_draw_text(selector, c_pos, UI_SIZE_16, UI_COLOR_ACCENT);
if (input_pressed(A_MENU_SELECT)) {
sfx_play(SFX_MENU_SELECT);
hs_new_entry.name[entry_len] = hs_charset[hs_char_index];
hs_new_entry.name[entry_len+1] = '\0';
}
}
ui_draw_text(hs_new_entry.name, ui_scaled_pos(anchor, pos), UI_SIZE_16, UI_COLOR_ACCENT);
}
static void page_hall_of_fame_draw(menu_t *menu, int data) {
// FIXME: doing this all in the draw() function leads to all kinds of
// complications
highscores_t *hs = &save.highscores[g.race_class][g.circut][g.highscore_tab];
if (hs_entry_complete) {
sfx_play(SFX_MENU_SELECT);
strncpy(save.highscores_name, hs_new_entry.name, 4);
save.is_dirty = true;
// Insert new highscore entry into the save struct
highscores_entry_t temp_entry = hs->entries[0];
for (int i = 0; i < NUM_HIGHSCORES; i++) {
if (hs_new_entry.time < hs->entries[i].time) {
for (int j = NUM_HIGHSCORES - 2; j >= i; j--) {
hs->entries[j+1] = hs->entries[j];
}
hs->entries[i] = hs_new_entry;
break;
}
}
save.is_dirty = true;
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
race_next();
}
else {
menu_reset(menu); // Can't go back!
menu_confirm(menu, "", "RESTART RACE", "RESTART", "QUIT", button_restart_or_quit);
}
return;
}
menu_page_t *page = &menu->pages[menu->index];
vec2i_t pos = page->title_pos;
pos.x -= 120;
pos.y += 48;
ui_pos_t anchor = UI_POS_MIDDLE | UI_POS_CENTER;
bool has_shown_new_entry = false;
for (int i = 0, j = 0; i < NUM_HIGHSCORES; i++, j++) {
if (!has_shown_new_entry && hs_new_entry.time < hs->entries[i].time) {
hall_of_fame_draw_name_entry(menu, anchor, pos);
ui_draw_time(hs_new_entry.time, ui_scaled_pos(anchor, vec2i(pos.x + 120, pos.y)), UI_SIZE_16, UI_COLOR_DEFAULT);
has_shown_new_entry = true;
j--;
}
else {
ui_draw_text(hs->entries[j].name, ui_scaled_pos(anchor, pos), UI_SIZE_16, UI_COLOR_DEFAULT);
ui_draw_time(hs->entries[j].time, ui_scaled_pos(anchor, vec2i(pos.x + 120, pos.y)), UI_SIZE_16, UI_COLOR_DEFAULT);
}
pos.y += 24;
}
}
static void page_hall_of_fame_init(menu_t *menu) {
menu_reset(menu); // Can't go back!
menu_page_t *page = menu_push(menu, "HALL OF FAME", page_hall_of_fame_draw);
flags_add(page->layout_flags, MENU_FIXED);
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->title_pos = vec2i(0, -100);
hs_new_entry.time = g.race_time;
strncpy(hs_new_entry.name, save.highscores_name, 4);
hs_char_index = 0;
hs_entry_complete = false;
}
// -----------------------------------------------------------------------------
// Text scroller
static char * const *text_scroll_lines;
static int text_scroll_lines_len;
static double text_scroll_start_time;
static void text_scroll_menu_draw(menu_t *menu, int data) {
double time = system_time() - text_scroll_start_time;
int scale = ui_get_scale();
int speed = 32;
vec2i_t screen = render_size();
vec2i_t pos = vec2i(screen.x / 2, screen.y - time * scale * speed);
for (int i = 0; i < text_scroll_lines_len; i++) {
const char *line = text_scroll_lines[i];
if (line[0] == '#') {
pos.y += 48 * scale;
ui_draw_text_centered(line + 1, pos, UI_SIZE_16, UI_COLOR_ACCENT);
pos.y += 32 * scale;
}
else {
ui_draw_text_centered(line, pos, UI_SIZE_8, UI_COLOR_DEFAULT);
pos.y += 12 * scale;
}
}
}
menu_t *text_scroll_menu_init(char * const *lines, int len) {
text_scroll_lines = lines;
text_scroll_lines_len = len;
text_scroll_start_time = system_time();
menu_reset(ingame_menu);
menu_page_t *page = menu_push(ingame_menu, "", text_scroll_menu_draw);
menu_page_add_button(page, 1, "", button_quit_confirm);
return ingame_menu;
}

View file

@ -1,13 +1,13 @@
#ifndef PAUSE_MENU_H
#define PAUSE_MENU_H
#include "menu.h"
void ingame_menus_load(void);
menu_t *pause_menu_init(void);
menu_t *game_over_menu_init(void);
menu_t *race_stats_menu_init(void);
menu_t *text_scroll_menu_init(char * const *lines, int len);
#endif
#ifndef PAUSE_MENU_H
#define PAUSE_MENU_H
#include "menu.h"
void ingame_menus_load(void);
menu_t *pause_menu_init(void);
menu_t *game_over_menu_init(void);
menu_t *race_stats_menu_init(void);
menu_t *text_scroll_menu_init(char * const *lines, int len);
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
#ifndef MAIN_MENU_H
#define MAIN_MENU_H
void main_menu_init(void);
void main_menu_update(void);
#endif
#ifndef MAIN_MENU_H
#define MAIN_MENU_H
void main_menu_init(void);
void main_menu_update(void);
#endif

View file

@ -1,245 +1,245 @@
#include "../system.h"
#include "../input.h"
#include "../utils.h"
#include "game.h"
#include "menu.h"
#include "ui.h"
#include "sfx.h"
bool blink(void) {
// blink 30 times per second
return fmod(system_cycle_time(), 1.0/15.0) < 1.0/30.0;
}
void menu_reset(menu_t *menu) {
menu->index = -1;
}
menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int)) {
error_if(menu->index >= MENU_PAGES_MAX-1, "MENU_PAGES_MAX exceeded");
menu_page_t *page = &menu->pages[++menu->index];
page->layout_flags = MENU_VERTICAL | MENU_ALIGN_CENTER;
page->block_width = 320;
page->title = title;
page->subtitle = NULL;
page->draw_func = draw_func;
page->entries_len = 0;
page->index = 0;
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
return page;
}
menu_page_t *menu_confirm(menu_t *menu, char *title, char *subtitle, char *yes, char *no, void(*confirm_func)(menu_t *, int)) {
error_if(menu->index >= MENU_PAGES_MAX-1, "MENU_PAGES_MAX exceeded");
menu_page_t *page = &menu->pages[++menu->index];
page->layout_flags = MENU_HORIZONTAL;
page->title = title;
page->subtitle = subtitle;
page->draw_func = NULL;
page->entries_len = 0;
menu_page_add_button(page, 1, yes, confirm_func);
menu_page_add_button(page, 0, no, confirm_func);
page->index = 1;
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
return page;
}
void menu_pop(menu_t *menu) {
if (menu->index == 0) {
return;
}
menu->index--;
}
void menu_page_add_button(menu_page_t *page, int data, char *text, void(*select_func)(menu_t *, int)) {
error_if(page->entries_len >= MENU_ENTRIES_MAX-1, "MENU_ENTRIES_MAX exceeded");
menu_entry_t *entry = &page->entries[page->entries_len++];
entry->data = data;
entry->text = text;
entry->select_func = select_func;
entry->type = MENU_ENTRY_BUTTON;
}
void menu_page_add_toggle(menu_page_t *page, int data, char *text, const char **options, int len, void(*select_func)(menu_t *, int)) {
error_if(page->entries_len >= MENU_ENTRIES_MAX-1, "MENU_ENTRIES_MAX exceeded");
menu_entry_t *entry = &page->entries[page->entries_len++];
entry->data = data;
entry->text = text;
entry->select_func = select_func;
entry->type = MENU_ENTRY_TOGGLE;
entry->options = options;
entry->options_len = len;
}
void menu_update(menu_t *menu) {
render_set_view_2d();
error_if(menu->index < 0, "Attempt to update menu without a page");
menu_page_t *page = &menu->pages[menu->index];
// Handle menu entry selecting
int last_index = page->index;
int selected_data = 0;
if (page->entries_len > 0) {
if (flags_is(page->layout_flags, MENU_HORIZONTAL)) {
if (input_pressed(A_MENU_LEFT)) {
page->index--;
}
else if (input_pressed(A_MENU_RIGHT)) {
page->index++;
}
}
else {
if (input_pressed(A_MENU_UP)) {
page->index--;
}
if (input_pressed(A_MENU_DOWN)) {
page->index++;
}
}
if (page->index >= page->entries_len) {
page->index = 0;
}
if (page->index < 0) {
page->index = page->entries_len - 1;
}
if (last_index != page->index) {
sfx_play(SFX_MENU_MOVE);
}
selected_data = page->entries[page->index].data;
}
if (page->draw_func) {
page->draw_func(menu, selected_data);
}
render_set_view_2d();
// Draw Horizontal (confirm)
if (flags_is(page->layout_flags, MENU_HORIZONTAL)) {
vec2i_t pos = vec2i(0, -20);
ui_draw_text_centered(page->title, ui_scaled_pos(page->title_anchor, pos), UI_SIZE_8, UI_COLOR_DEFAULT);
if (page->subtitle) {
pos.y += 12;
ui_draw_text_centered(page->subtitle, ui_scaled_pos(page->title_anchor, pos), UI_SIZE_8, UI_COLOR_DEFAULT);
}
pos.y += 16;
page = &menu->pages[menu->index];
pos.x = -50;
for (int i = 0; i < page->entries_len; i++) {
menu_entry_t *entry = &page->entries[i];
rgba_t text_color;
if (i == page->index && blink()) {
text_color = UI_COLOR_ACCENT;
}
else {
text_color = UI_COLOR_DEFAULT;
}
ui_draw_text_centered(entry->text, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_16, text_color);
pos.x = 60;
}
}
// Draw Vertical
else {
vec2i_t title_pos, items_pos;
if (flags_not(page->layout_flags, MENU_FIXED)) {
int height = 20 + page->entries_len * 12;
title_pos = vec2i(0, -height/2);
items_pos = vec2i(0, -height/2 + 20);
}
else {
title_pos = page->title_pos;
items_pos = page->items_pos;
}
if (flags_is(page->layout_flags, MENU_ALIGN_CENTER)) {
ui_draw_text_centered(page->title, ui_scaled_pos(page->title_anchor, title_pos), UI_SIZE_12, UI_COLOR_ACCENT);
}
else {
ui_draw_text(page->title, ui_scaled_pos(page->title_anchor, title_pos), UI_SIZE_12, UI_COLOR_ACCENT);
}
page = &menu->pages[menu->index];
for (int i = 0; i < page->entries_len; i++) {
menu_entry_t *entry = &page->entries[i];
rgba_t text_color;
if (i == page->index && blink()) {
text_color = UI_COLOR_ACCENT;
}
else {
text_color = UI_COLOR_DEFAULT;
}
if (flags_is(page->layout_flags, MENU_ALIGN_CENTER)) {
ui_draw_text_centered(entry->text, ui_scaled_pos(page->items_anchor, items_pos), UI_SIZE_8, text_color);
}
else {
ui_draw_text(entry->text, ui_scaled_pos(page->items_anchor, items_pos), UI_SIZE_8, text_color);
}
if (entry->type == MENU_ENTRY_TOGGLE) {
vec2i_t toggle_pos = items_pos;
toggle_pos.x += page->block_width - ui_text_width(entry->options[entry->data], UI_SIZE_8);
ui_draw_text(entry->options[entry->data], ui_scaled_pos(page->items_anchor, toggle_pos), UI_SIZE_8, text_color);
}
items_pos.y += 12;
}
}
// Handle back buttons
if (input_pressed(A_MENU_BACK) || input_pressed(A_MENU_QUIT)) {
if (menu->index != 0) {
menu_pop(menu);
sfx_play(SFX_MENU_SELECT);
}
return;
}
if (page->entries_len == 0) {
return;
}
// Handle toggle entries
menu_entry_t *entry = &page->entries[page->index];
if (entry->type == MENU_ENTRY_TOGGLE) {
if (input_pressed(A_MENU_LEFT)) {
sfx_play(SFX_MENU_SELECT);
entry->data--;
if (entry->data < 0) {
entry->data = entry->options_len-1;
}
if (entry->select_func) {
entry->select_func(menu, entry->data);
}
}
else if (input_pressed(A_MENU_RIGHT) || input_pressed(A_MENU_SELECT) || input_pressed(A_MENU_START)) {
sfx_play(SFX_MENU_SELECT);
entry->data = (entry->data + 1) % entry->options_len;
if (entry->select_func) {
entry->select_func(menu, entry->data);
}
}
}
// Handle buttons
else {
if (input_pressed(A_MENU_SELECT) || input_pressed(A_MENU_START)) {
if (entry->select_func) {
sfx_play(SFX_MENU_SELECT);
if (entry->type == MENU_ENTRY_TOGGLE) {
entry->data = (entry->data + 1) % entry->options_len;
}
entry->select_func(menu, entry->data);
}
}
}
}
#include "../system.h"
#include "../input.h"
#include "../utils.h"
#include "game.h"
#include "menu.h"
#include "ui.h"
#include "sfx.h"
bool blink(void) {
// blink 30 times per second
return fmod(system_cycle_time(), 1.0/15.0) < 1.0/30.0;
}
void menu_reset(menu_t *menu) {
menu->index = -1;
}
menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int)) {
error_if(menu->index >= MENU_PAGES_MAX-1, "MENU_PAGES_MAX exceeded");
menu_page_t *page = &menu->pages[++menu->index];
page->layout_flags = MENU_VERTICAL | MENU_ALIGN_CENTER;
page->block_width = 320;
page->title = title;
page->subtitle = NULL;
page->draw_func = draw_func;
page->entries_len = 0;
page->index = 0;
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
return page;
}
menu_page_t *menu_confirm(menu_t *menu, char *title, char *subtitle, char *yes, char *no, void(*confirm_func)(menu_t *, int)) {
error_if(menu->index >= MENU_PAGES_MAX-1, "MENU_PAGES_MAX exceeded");
menu_page_t *page = &menu->pages[++menu->index];
page->layout_flags = MENU_HORIZONTAL;
page->title = title;
page->subtitle = subtitle;
page->draw_func = NULL;
page->entries_len = 0;
menu_page_add_button(page, 1, yes, confirm_func);
menu_page_add_button(page, 0, no, confirm_func);
page->index = 1;
page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER;
return page;
}
void menu_pop(menu_t *menu) {
if (menu->index == 0) {
return;
}
menu->index--;
}
void menu_page_add_button(menu_page_t *page, int data, char *text, void(*select_func)(menu_t *, int)) {
error_if(page->entries_len >= MENU_ENTRIES_MAX-1, "MENU_ENTRIES_MAX exceeded");
menu_entry_t *entry = &page->entries[page->entries_len++];
entry->data = data;
entry->text = text;
entry->select_func = select_func;
entry->type = MENU_ENTRY_BUTTON;
}
void menu_page_add_toggle(menu_page_t *page, int data, char *text, const char **options, int len, void(*select_func)(menu_t *, int)) {
error_if(page->entries_len >= MENU_ENTRIES_MAX-1, "MENU_ENTRIES_MAX exceeded");
menu_entry_t *entry = &page->entries[page->entries_len++];
entry->data = data;
entry->text = text;
entry->select_func = select_func;
entry->type = MENU_ENTRY_TOGGLE;
entry->options = options;
entry->options_len = len;
}
void menu_update(menu_t *menu) {
render_set_view_2d();
error_if(menu->index < 0, "Attempt to update menu without a page");
menu_page_t *page = &menu->pages[menu->index];
// Handle menu entry selecting
int last_index = page->index;
int selected_data = 0;
if (page->entries_len > 0) {
if (flags_is(page->layout_flags, MENU_HORIZONTAL)) {
if (input_pressed(A_MENU_LEFT)) {
page->index--;
}
else if (input_pressed(A_MENU_RIGHT)) {
page->index++;
}
}
else {
if (input_pressed(A_MENU_UP)) {
page->index--;
}
if (input_pressed(A_MENU_DOWN)) {
page->index++;
}
}
if (page->index >= page->entries_len) {
page->index = 0;
}
if (page->index < 0) {
page->index = page->entries_len - 1;
}
if (last_index != page->index) {
sfx_play(SFX_MENU_MOVE);
}
selected_data = page->entries[page->index].data;
}
if (page->draw_func) {
page->draw_func(menu, selected_data);
}
render_set_view_2d();
// Draw Horizontal (confirm)
if (flags_is(page->layout_flags, MENU_HORIZONTAL)) {
vec2i_t pos = vec2i(0, -20);
ui_draw_text_centered(page->title, ui_scaled_pos(page->title_anchor, pos), UI_SIZE_8, UI_COLOR_DEFAULT);
if (page->subtitle) {
pos.y += 12;
ui_draw_text_centered(page->subtitle, ui_scaled_pos(page->title_anchor, pos), UI_SIZE_8, UI_COLOR_DEFAULT);
}
pos.y += 16;
page = &menu->pages[menu->index];
pos.x = -50;
for (int i = 0; i < page->entries_len; i++) {
menu_entry_t *entry = &page->entries[i];
rgba_t text_color;
if (i == page->index && blink()) {
text_color = UI_COLOR_ACCENT;
}
else {
text_color = UI_COLOR_DEFAULT;
}
ui_draw_text_centered(entry->text, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_16, text_color);
pos.x = 60;
}
}
// Draw Vertical
else {
vec2i_t title_pos, items_pos;
if (flags_not(page->layout_flags, MENU_FIXED)) {
int height = 20 + page->entries_len * 12;
title_pos = vec2i(0, -height/2);
items_pos = vec2i(0, -height/2 + 20);
}
else {
title_pos = page->title_pos;
items_pos = page->items_pos;
}
if (flags_is(page->layout_flags, MENU_ALIGN_CENTER)) {
ui_draw_text_centered(page->title, ui_scaled_pos(page->title_anchor, title_pos), UI_SIZE_12, UI_COLOR_ACCENT);
}
else {
ui_draw_text(page->title, ui_scaled_pos(page->title_anchor, title_pos), UI_SIZE_12, UI_COLOR_ACCENT);
}
page = &menu->pages[menu->index];
for (int i = 0; i < page->entries_len; i++) {
menu_entry_t *entry = &page->entries[i];
rgba_t text_color;
if (i == page->index && blink()) {
text_color = UI_COLOR_ACCENT;
}
else {
text_color = UI_COLOR_DEFAULT;
}
if (flags_is(page->layout_flags, MENU_ALIGN_CENTER)) {
ui_draw_text_centered(entry->text, ui_scaled_pos(page->items_anchor, items_pos), UI_SIZE_8, text_color);
}
else {
ui_draw_text(entry->text, ui_scaled_pos(page->items_anchor, items_pos), UI_SIZE_8, text_color);
}
if (entry->type == MENU_ENTRY_TOGGLE) {
vec2i_t toggle_pos = items_pos;
toggle_pos.x += page->block_width - ui_text_width(entry->options[entry->data], UI_SIZE_8);
ui_draw_text(entry->options[entry->data], ui_scaled_pos(page->items_anchor, toggle_pos), UI_SIZE_8, text_color);
}
items_pos.y += 12;
}
}
// Handle back buttons
if (input_pressed(A_MENU_BACK) || input_pressed(A_MENU_QUIT)) {
if (menu->index != 0) {
menu_pop(menu);
sfx_play(SFX_MENU_SELECT);
}
return;
}
if (page->entries_len == 0) {
return;
}
// Handle toggle entries
menu_entry_t *entry = &page->entries[page->index];
if (entry->type == MENU_ENTRY_TOGGLE) {
if (input_pressed(A_MENU_LEFT)) {
sfx_play(SFX_MENU_SELECT);
entry->data--;
if (entry->data < 0) {
entry->data = entry->options_len-1;
}
if (entry->select_func) {
entry->select_func(menu, entry->data);
}
}
else if (input_pressed(A_MENU_RIGHT) || input_pressed(A_MENU_SELECT) || input_pressed(A_MENU_START)) {
sfx_play(SFX_MENU_SELECT);
entry->data = (entry->data + 1) % entry->options_len;
if (entry->select_func) {
entry->select_func(menu, entry->data);
}
}
}
// Handle buttons
else {
if (input_pressed(A_MENU_SELECT) || input_pressed(A_MENU_START)) {
if (entry->select_func) {
sfx_play(SFX_MENU_SELECT);
if (entry->type == MENU_ENTRY_TOGGLE) {
entry->data = (entry->data + 1) % entry->options_len;
}
entry->select_func(menu, entry->data);
}
}
}
}

View file

@ -1,65 +1,65 @@
#ifndef MENU_H
#define MENU_H
#include "../types.h"
#include "ui.h"
#define MENU_PAGES_MAX 8
#define MENU_ENTRIES_MAX 16
typedef enum {
MENU_ENTRY_BUTTON,
MENU_ENTRY_TOGGLE
} menu_entry_type_t;
typedef enum {
MENU_VERTICAL = (1<<0),
MENU_HORIZONTAL = (1<<1),
MENU_FIXED = (1<<2),
MENU_ALIGN_CENTER = (1<<3),
MENU_ALIGN_BLOCK = (1<<4)
} menu_page_layout_t;
typedef struct menu_t menu_t;
typedef struct menu_page_t menu_page_t;
typedef struct menu_entry_t menu_entry_t;
typedef struct menu_entry_options_t menu_entry_options_t;
struct menu_entry_t {
menu_entry_type_t type;
int data;
char *text;
void (*select_func)(menu_t *, int);
const char **options;
int options_len;
};
struct menu_page_t {
char *title, *subtitle;
menu_page_layout_t layout_flags;
void (*draw_func)(menu_t *, int);
menu_entry_t entries[MENU_ENTRIES_MAX];
int entries_len;
int index;
int block_width;
vec2i_t title_pos;
ui_pos_t title_anchor;
vec2i_t items_pos;
ui_pos_t items_anchor;
};
struct menu_t {
menu_page_t pages[MENU_PAGES_MAX];
int index;
};
void menu_reset(menu_t *menu);
menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int));
menu_page_t *menu_confirm(menu_t *menu, char *title, char *subtitle, char *yes, char *no, void(*confirm_func)(menu_t *, int));
void menu_pop(menu_t *menu);
void menu_page_add_button(menu_page_t *page, int data, char *text, void(*select_func)(menu_t *, int));
void menu_page_add_toggle(menu_page_t *page, int data, char *text, const char **options, int len, void(*select_func)(menu_t *, int));
void menu_update(menu_t *menu);
#endif
#ifndef MENU_H
#define MENU_H
#include "../types.h"
#include "ui.h"
#define MENU_PAGES_MAX 8
#define MENU_ENTRIES_MAX 16
typedef enum {
MENU_ENTRY_BUTTON,
MENU_ENTRY_TOGGLE
} menu_entry_type_t;
typedef enum {
MENU_VERTICAL = (1<<0),
MENU_HORIZONTAL = (1<<1),
MENU_FIXED = (1<<2),
MENU_ALIGN_CENTER = (1<<3),
MENU_ALIGN_BLOCK = (1<<4)
} menu_page_layout_t;
typedef struct menu_t menu_t;
typedef struct menu_page_t menu_page_t;
typedef struct menu_entry_t menu_entry_t;
typedef struct menu_entry_options_t menu_entry_options_t;
struct menu_entry_t {
menu_entry_type_t type;
int data;
char *text;
void (*select_func)(menu_t *, int);
const char **options;
int options_len;
};
struct menu_page_t {
char *title, *subtitle;
menu_page_layout_t layout_flags;
void (*draw_func)(menu_t *, int);
menu_entry_t entries[MENU_ENTRIES_MAX];
int entries_len;
int index;
int block_width;
vec2i_t title_pos;
ui_pos_t title_anchor;
vec2i_t items_pos;
ui_pos_t items_anchor;
};
struct menu_t {
menu_page_t pages[MENU_PAGES_MAX];
int index;
};
void menu_reset(menu_t *menu);
menu_page_t *menu_push(menu_t *menu, char *title, void(*draw_func)(menu_t *, int));
menu_page_t *menu_confirm(menu_t *menu, char *title, char *subtitle, char *yes, char *no, void(*confirm_func)(menu_t *, int));
void menu_pop(menu_t *menu);
void menu_page_add_button(menu_page_t *page, int data, char *text, void(*select_func)(menu_t *, int));
void menu_page_add_toggle(menu_page_t *page, int data, char *text, const char **options, int len, void(*select_func)(menu_t *, int));
void menu_update(menu_t *menu);
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,384 +1,384 @@
#ifndef OBJECT_H
#define OBJECT_H
#include "../types.h"
#include "../render.h"
#include "../utils.h"
#include "image.h"
// Primitive Structure Stub ( Structure varies with primitive type )
typedef struct Primitive {
int16_t type; // Type of Primitive
} Primitive;
typedef struct F3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t pad1;
rgba_t color;
} F3;
typedef struct FT3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
int16_t pad1;
rgba_t color;
} FT3;
typedef struct F4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
rgba_t color;
} F4;
typedef struct FT4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
int16_t pad1;
rgba_t color;
} FT4;
typedef struct G3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t pad1;
rgba_t color[3];
} G3;
typedef struct GT3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
int16_t pad1;
rgba_t color[3];
} GT3;
typedef struct G4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
rgba_t color[4];
} G4;
typedef struct GT4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
int16_t pad1;
rgba_t color[4];
} GT4;
/* LIGHT SOURCED POLYGONS
*/
typedef struct LSF3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t normal; // Indices of the normals
rgba_t color;
} LSF3;
typedef struct LSFT3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t normal; // Indices of the normals
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
rgba_t color;
} LSFT3;
typedef struct LSF4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t normal; // Indices of the normals
int16_t pad1;
rgba_t color;
} LSF4;
typedef struct LSFT4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t normal; // Indices of the normals
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
rgba_t color;
} LSFT4;
typedef struct LSG3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t normals[3]; // Indices of the normals
rgba_t color[3];
} LSG3;
typedef struct LSGT3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t normals[3]; // Indices of the normals
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
rgba_t color[3];
} LSGT3;
typedef struct LSG4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t normals[4]; // Indices of the normals
rgba_t color[4];
} LSG4;
typedef struct LSGT4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t normals[4]; // Indices of the normals
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
int16_t pad1;
rgba_t color[4];
} LSGT4;
/* OTHER PRIMITIVE TYPES
*/
typedef struct SPR {
int16_t type;
int16_t flag;
int16_t coord;
int16_t width;
int16_t height;
int16_t texture;
rgba_t color;
} SPR;
typedef struct Spline {
int16_t type; // Type of primitive
int16_t flag;
vec3_t control1;
vec3_t position;
vec3_t control2;
rgba_t color;
} Spline;
typedef struct PointLight {
int16_t type;
int16_t flag;
vec3_t position;
rgba_t color;
int16_t startFalloff;
int16_t endFalloff;
} PointLight;
typedef struct SpotLight {
int16_t type;
int16_t flag;
vec3_t position;
vec3_t direction;
rgba_t color;
int16_t startFalloff;
int16_t endFalloff;
int16_t coneAngle;
int16_t spreadAngle;
} SpotLight;
typedef struct InfiniteLight {
int16_t type;
int16_t flag;
vec3_t direction;
rgba_t color;
} InfiniteLight;
// PRIMITIVE FLAGS
#define PRM_SINGLE_SIDED 0x0001
#define PRM_SHIP_ENGINE 0x0002
#define PRM_TRANSLUCENT 0x0004
#define PRM_TYPE_F3 1
#define PRM_TYPE_FT3 2
#define PRM_TYPE_F4 3
#define PRM_TYPE_FT4 4
#define PRM_TYPE_G3 5
#define PRM_TYPE_GT3 6
#define PRM_TYPE_G4 7
#define PRM_TYPE_GT4 8
#define PRM_TYPE_LF2 9
#define PRM_TYPE_TSPR 10
#define PRM_TYPE_BSPR 11
#define PRM_TYPE_LSF3 12
#define PRM_TYPE_LSFT3 13
#define PRM_TYPE_LSF4 14
#define PRM_TYPE_LSFT4 15
#define PRM_TYPE_LSG3 16
#define PRM_TYPE_LSGT3 17
#define PRM_TYPE_LSG4 18
#define PRM_TYPE_LSGT4 19
#define PRM_TYPE_SPLINE 20
#define PRM_TYPE_INFINITE_LIGHT 21
#define PRM_TYPE_POINT_LIGHT 22
#define PRM_TYPE_SPOT_LIGHT 23
typedef struct Object {
char name[16];
mat4_t mat;
int16_t vertices_len; // Number of Vertices
vec3_t *vertices; // Pointer to 3D Points
int16_t normals_len; // Number of Normals
vec3_t *normals; // Pointer to 3D Normals
int16_t primitives_len; // Number of Primitives
Primitive *primitives; // Pointer to Z Sort Primitives
vec3_t origin;
int32_t extent; // Flags for object characteristics
int16_t flags; // Next object in list
float radius;
struct Object *next; // Next object in list
} Object;
typedef union Prm {
uint8_t *ptr;
int16_t *sptr;
int32_t *lptr;
Object *object;
Primitive *primitive;
F3 *f3;
FT3 *ft3;
F4 *f4;
FT4 *ft4;
G3 *g3;
GT3 *gt3;
G4 *g4;
GT4 *gt4;
SPR *spr;
Spline *spline;
PointLight *pointLight;
SpotLight *spotLight;
InfiniteLight *infiniteLight;
LSF3 *lsf3;
LSFT3 *lsft3;
LSF4 *lsf4;
LSFT4 *lsft4;
LSG3 *lsg3;
LSGT3 *lsgt3;
LSG4 *lsg4;
LSGT4 *lsgt4;
} Prm;
Object *objects_load(char *name, texture_list_t tl);
void object_draw(Object *object, mat4_t *mat);
#ifndef OBJECT_H
#define OBJECT_H
#include "../types.h"
#include "../render.h"
#include "../utils.h"
#include "image.h"
// Primitive Structure Stub ( Structure varies with primitive type )
typedef struct Primitive {
int16_t type; // Type of Primitive
} Primitive;
typedef struct F3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t pad1;
rgba_t color;
} F3;
typedef struct FT3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
int16_t pad1;
rgba_t color;
} FT3;
typedef struct F4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
rgba_t color;
} F4;
typedef struct FT4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
int16_t pad1;
rgba_t color;
} FT4;
typedef struct G3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t pad1;
rgba_t color[3];
} G3;
typedef struct GT3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
int16_t pad1;
rgba_t color[3];
} GT3;
typedef struct G4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
rgba_t color[4];
} G4;
typedef struct GT4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
int16_t pad1;
rgba_t color[4];
} GT4;
/* LIGHT SOURCED POLYGONS
*/
typedef struct LSF3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t normal; // Indices of the normals
rgba_t color;
} LSF3;
typedef struct LSFT3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t normal; // Indices of the normals
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
rgba_t color;
} LSFT3;
typedef struct LSF4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t normal; // Indices of the normals
int16_t pad1;
rgba_t color;
} LSF4;
typedef struct LSFT4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t normal; // Indices of the normals
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
rgba_t color;
} LSFT4;
typedef struct LSG3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t normals[3]; // Indices of the normals
rgba_t color[3];
} LSG3;
typedef struct LSGT3 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[3]; // Indices of the coords
int16_t normals[3]; // Indices of the normals
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
rgba_t color[3];
} LSGT3;
typedef struct LSG4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t normals[4]; // Indices of the normals
rgba_t color[4];
} LSG4;
typedef struct LSGT4 {
int16_t type; // Type of primitive
int16_t flag;
int16_t coords[4]; // Indices of the coords
int16_t normals[4]; // Indices of the normals
int16_t texture;
int16_t cba;
int16_t tsb;
uint8_t u0;
uint8_t v0;
uint8_t u1;
uint8_t v1;
uint8_t u2;
uint8_t v2;
uint8_t u3;
uint8_t v3;
int16_t pad1;
rgba_t color[4];
} LSGT4;
/* OTHER PRIMITIVE TYPES
*/
typedef struct SPR {
int16_t type;
int16_t flag;
int16_t coord;
int16_t width;
int16_t height;
int16_t texture;
rgba_t color;
} SPR;
typedef struct Spline {
int16_t type; // Type of primitive
int16_t flag;
vec3_t control1;
vec3_t position;
vec3_t control2;
rgba_t color;
} Spline;
typedef struct PointLight {
int16_t type;
int16_t flag;
vec3_t position;
rgba_t color;
int16_t startFalloff;
int16_t endFalloff;
} PointLight;
typedef struct SpotLight {
int16_t type;
int16_t flag;
vec3_t position;
vec3_t direction;
rgba_t color;
int16_t startFalloff;
int16_t endFalloff;
int16_t coneAngle;
int16_t spreadAngle;
} SpotLight;
typedef struct InfiniteLight {
int16_t type;
int16_t flag;
vec3_t direction;
rgba_t color;
} InfiniteLight;
// PRIMITIVE FLAGS
#define PRM_SINGLE_SIDED 0x0001
#define PRM_SHIP_ENGINE 0x0002
#define PRM_TRANSLUCENT 0x0004
#define PRM_TYPE_F3 1
#define PRM_TYPE_FT3 2
#define PRM_TYPE_F4 3
#define PRM_TYPE_FT4 4
#define PRM_TYPE_G3 5
#define PRM_TYPE_GT3 6
#define PRM_TYPE_G4 7
#define PRM_TYPE_GT4 8
#define PRM_TYPE_LF2 9
#define PRM_TYPE_TSPR 10
#define PRM_TYPE_BSPR 11
#define PRM_TYPE_LSF3 12
#define PRM_TYPE_LSFT3 13
#define PRM_TYPE_LSF4 14
#define PRM_TYPE_LSFT4 15
#define PRM_TYPE_LSG3 16
#define PRM_TYPE_LSGT3 17
#define PRM_TYPE_LSG4 18
#define PRM_TYPE_LSGT4 19
#define PRM_TYPE_SPLINE 20
#define PRM_TYPE_INFINITE_LIGHT 21
#define PRM_TYPE_POINT_LIGHT 22
#define PRM_TYPE_SPOT_LIGHT 23
typedef struct Object {
char name[16];
mat4_t mat;
int16_t vertices_len; // Number of Vertices
vec3_t *vertices; // Pointer to 3D Points
int16_t normals_len; // Number of Normals
vec3_t *normals; // Pointer to 3D Normals
int16_t primitives_len; // Number of Primitives
Primitive *primitives; // Pointer to Z Sort Primitives
vec3_t origin;
int32_t extent; // Flags for object characteristics
int16_t flags; // Next object in list
float radius;
struct Object *next; // Next object in list
} Object;
typedef union Prm {
uint8_t *ptr;
int16_t *sptr;
int32_t *lptr;
Object *object;
Primitive *primitive;
F3 *f3;
FT3 *ft3;
F4 *f4;
FT4 *ft4;
G3 *g3;
GT3 *gt3;
G4 *g4;
GT4 *gt4;
SPR *spr;
Spline *spline;
PointLight *pointLight;
SpotLight *spotLight;
InfiniteLight *infiniteLight;
LSF3 *lsf3;
LSFT3 *lsft3;
LSF4 *lsf4;
LSFT4 *lsft4;
LSG3 *lsg3;
LSGT3 *lsgt3;
LSG4 *lsg4;
LSGT4 *lsgt4;
} Prm;
Object *objects_load(char *name, texture_list_t tl);
void object_draw(Object *object, mat4_t *mat);
#endif

View file

@ -1,279 +1,279 @@
#include "../mem.h"
#include "../input.h"
#include "../platform.h"
#include "../system.h"
#include "../utils.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "droid.h"
#include "camera.h"
#include "object.h"
#include "scene.h"
#include "game.h"
#include "hud.h"
#include "sfx.h"
#include "race.h"
#include "particle.h"
#include "menu.h"
#include "ship_ai.h"
#include "ingame_menus.h"
#define ATTRACT_DURATION 60.0
static bool is_paused = false;
static bool menu_is_scroll_text = false;
static bool has_show_credits = false;
static float attract_start_time;
static menu_t *active_menu = NULL;
void race_init(void) {
ingame_menus_load();
menu_is_scroll_text = false;
const circut_settings_t *cs = &def.circuts[g.circut].settings[g.race_class];
track_load(cs->path);
scene_load(cs->path, cs->sky_y_offset);
if (g.circut == CIRCUT_SILVERSTREAM && g.race_class == RACE_CLASS_RAPIER) {
scene_init_aurora_borealis();
}
race_start();
// render_textures_dump("texture_atlas.png");
if (g.is_attract_mode) {
attract_start_time = system_time();
for (int i = 0; i < len(g.ships); i++) {
// FIXME: this is needed to initializes the engine sound. Should
// maybe be done in a separate step?
ship_ai_update_intro(&g.ships[i]);
g.ships[i].update_func = ship_ai_update_race;
flags_rm(g.ships[i].flags, SHIP_VIEW_INTERNAL);
flags_rm(g.ships[i].flags, SHIP_RACING);
}
g.pilot = rand_int(0, len(def.pilots));
g.camera.update_func = camera_update_attract_random;
if (!has_show_credits || rand_int(0, 10) == 0) {
active_menu = text_scroll_menu_init(def.credits, len(def.credits));
menu_is_scroll_text = true;
has_show_credits = true;
}
}
is_paused = false;
}
void race_update(void) {
if (is_paused) {
if (!active_menu) {
active_menu = pause_menu_init();
}
if (input_pressed(A_MENU_QUIT)) {
race_unpause();
}
}
else {
ships_update();
droid_update(&g.droid, &g.ships[g.pilot]);
camera_update(&g.camera, &g.ships[g.pilot], &g.droid);
weapons_update();
particles_update();
scene_update();
if (g.race_type != RACE_TYPE_TIME_TRIAL) {
track_cycle_pickups();
}
if (g.is_attract_mode) {
if (input_pressed(A_MENU_START) || input_pressed(A_MENU_SELECT)) {
game_set_scene(GAME_SCENE_MAIN_MENU);
}
float duration = system_time() - attract_start_time;
if ((!active_menu && duration > 30) || duration > 120) {
game_set_scene(GAME_SCENE_TITLE);
}
}
else if (active_menu == NULL && (input_pressed(A_MENU_START) || input_pressed(A_MENU_QUIT))) {
race_pause();
}
}
// Draw 3D
render_set_view(g.camera.position, g.camera.angle);
render_set_cull_backface(false);
scene_draw(&g.camera);
track_draw(&g.camera);
render_set_cull_backface(true);
ships_draw();
droid_draw(&g.droid);
weapons_draw();
particles_draw();
// Draw 2d
render_set_view_2d();
if (flags_is(g.ships[g.pilot].flags, SHIP_RACING)) {
hud_draw(&g.ships[g.pilot]);
}
if (active_menu) {
if (!menu_is_scroll_text) {
vec2i_t size = render_size();
render_push_2d(vec2i(0, 0), size, rgba(0, 0, 0, 128), RENDER_NO_TEXTURE);
}
menu_update(active_menu);
}
}
void race_start(void) {
active_menu = NULL;
sfx_reset();
scene_init();
camera_init(&g.camera, g.track.sections);
g.camera.update_func = camera_update_race_intro;
ships_init(g.track.sections);
droid_init(&g.droid, &g.ships[g.pilot]);
particles_init();
weapons_init();
for (int i = 0; i < len(g.race_ranks); i++) {
g.race_ranks[i].points = 0;
g.race_ranks[i].pilot = i;
}
for (int i = 0; i < len(g.lap_times); i++) {
for (int j = 0; j < len(g.lap_times[i]); j++) {
g.lap_times[i][j] = 0;
}
}
g.is_new_race_record = false;
g.is_new_lap_record = false;
g.best_lap = 0;
g.race_time = 0;
}
void race_restart(void) {
race_unpause();
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
g.lives--;
if (g.lives == 0) {
race_release_control();
active_menu = game_over_menu_init();
return;
}
}
race_start();
}
static bool sort_points_compare(pilot_points_t *pa, pilot_points_t *pb) {
return (pa->points < pb->points);
}
void race_end(void) {
race_release_control();
g.race_position = g.ships[g.pilot].position_rank;
g.race_time = 0;
g.best_lap = g.lap_times[g.pilot][0];
for (int i = 0; i < NUM_LAPS; i++) {
g.race_time += g.lap_times[g.pilot][i];
if (g.lap_times[g.pilot][i] < g.best_lap) {
g.best_lap = g.lap_times[g.pilot][i];
}
}
highscores_t *hs = &save.highscores[g.race_class][g.circut][g.highscore_tab];
if (g.best_lap < hs->lap_record) {
hs->lap_record = g.best_lap;
g.is_new_lap_record = true;
save.is_dirty = true;
}
for (int i = 0; i < NUM_HIGHSCORES; i++) {
if (g.race_time < hs->entries[i].time) {
g.is_new_race_record = true;
break;
}
}
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
for (int i = 0; i < len(def.race_points_for_rank); i++) {
g.race_ranks[i].points = def.race_points_for_rank[i];
// Find the pilot for this race rank in the championship table
for (int j = 0; j < len(g.championship_ranks); j++) {
if (g.race_ranks[i].pilot == g.championship_ranks[j].pilot) {
g.championship_ranks[j].points += def.race_points_for_rank[i];
break;
}
}
}
sort(g.championship_ranks, len(g.championship_ranks), sort_points_compare);
}
active_menu = race_stats_menu_init();
}
void race_next(void) {
int next_circut = g.circut + 1;
// Championship complete
if (
(save.has_bonus_circuts && next_circut >= NUM_CIRCUTS) ||
(!save.has_bonus_circuts && next_circut >= NUM_NON_BONUS_CIRCUTS)
) {
if (g.race_class == RACE_CLASS_RAPIER) {
if (save.has_bonus_circuts) {
active_menu = text_scroll_menu_init(def.congratulations.rapier_all_circuts, len(def.congratulations.rapier_all_circuts));
}
else {
save.has_bonus_circuts = true;
active_menu = text_scroll_menu_init(def.congratulations.rapier, len(def.congratulations.rapier));
}
}
else {
save.has_rapier_class = true;
if (save.has_bonus_circuts) {
active_menu = text_scroll_menu_init(def.congratulations.venom_all_circuts, len(def.congratulations.venom_all_circuts));
}
else {
active_menu = text_scroll_menu_init(def.congratulations.venom, len(def.congratulations.venom));
}
}
save.is_dirty = true;
menu_is_scroll_text = true;
}
// Next track
else {
g.circut = next_circut;
game_set_scene(GAME_SCENE_RACE);
}
}
void race_release_control(void) {
flags_rm(g.ships[g.pilot].flags, SHIP_RACING);
g.ships[g.pilot].remote_thrust_max = 3160;
g.ships[g.pilot].remote_thrust_mag = 32;
g.ships[g.pilot].speed = 3160;
g.camera.update_func = camera_update_attract_random;
}
void race_pause(void) {
sfx_pause();
is_paused = true;
}
void race_unpause(void) {
sfx_unpause();
is_paused = false;
active_menu = NULL;
}
#include "../mem.h"
#include "../input.h"
#include "../platform.h"
#include "../system.h"
#include "../utils.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "droid.h"
#include "camera.h"
#include "object.h"
#include "scene.h"
#include "game.h"
#include "hud.h"
#include "sfx.h"
#include "race.h"
#include "particle.h"
#include "menu.h"
#include "ship_ai.h"
#include "ingame_menus.h"
#define ATTRACT_DURATION 60.0
static bool is_paused = false;
static bool menu_is_scroll_text = false;
static bool has_show_credits = false;
static float attract_start_time;
static menu_t *active_menu = NULL;
void race_init(void) {
ingame_menus_load();
menu_is_scroll_text = false;
const circut_settings_t *cs = &def.circuts[g.circut].settings[g.race_class];
track_load(cs->path);
scene_load(cs->path, cs->sky_y_offset);
if (g.circut == CIRCUT_SILVERSTREAM && g.race_class == RACE_CLASS_RAPIER) {
scene_init_aurora_borealis();
}
race_start();
// render_textures_dump("texture_atlas.png");
if (g.is_attract_mode) {
attract_start_time = system_time();
for (int i = 0; i < len(g.ships); i++) {
// FIXME: this is needed to initializes the engine sound. Should
// maybe be done in a separate step?
ship_ai_update_intro(&g.ships[i]);
g.ships[i].update_func = ship_ai_update_race;
flags_rm(g.ships[i].flags, SHIP_VIEW_INTERNAL);
flags_rm(g.ships[i].flags, SHIP_RACING);
}
g.pilot = rand_int(0, len(def.pilots));
g.camera.update_func = camera_update_attract_random;
if (!has_show_credits || rand_int(0, 10) == 0) {
active_menu = text_scroll_menu_init(def.credits, len(def.credits));
menu_is_scroll_text = true;
has_show_credits = true;
}
}
is_paused = false;
}
void race_update(void) {
if (is_paused) {
if (!active_menu) {
active_menu = pause_menu_init();
}
if (input_pressed(A_MENU_QUIT)) {
race_unpause();
}
}
else {
ships_update();
droid_update(&g.droid, &g.ships[g.pilot]);
camera_update(&g.camera, &g.ships[g.pilot], &g.droid);
weapons_update();
particles_update();
scene_update();
if (g.race_type != RACE_TYPE_TIME_TRIAL) {
track_cycle_pickups();
}
if (g.is_attract_mode) {
if (input_pressed(A_MENU_START) || input_pressed(A_MENU_SELECT)) {
game_set_scene(GAME_SCENE_MAIN_MENU);
}
float duration = system_time() - attract_start_time;
if ((!active_menu && duration > 30) || duration > 120) {
game_set_scene(GAME_SCENE_TITLE);
}
}
else if (active_menu == NULL && (input_pressed(A_MENU_START) || input_pressed(A_MENU_QUIT))) {
race_pause();
}
}
// Draw 3D
render_set_view(g.camera.position, g.camera.angle);
render_set_cull_backface(false);
scene_draw(&g.camera);
track_draw(&g.camera);
render_set_cull_backface(true);
ships_draw();
droid_draw(&g.droid);
weapons_draw();
particles_draw();
// Draw 2d
render_set_view_2d();
if (flags_is(g.ships[g.pilot].flags, SHIP_RACING)) {
hud_draw(&g.ships[g.pilot]);
}
if (active_menu) {
if (!menu_is_scroll_text) {
vec2i_t size = render_size();
render_push_2d(vec2i(0, 0), size, rgba(0, 0, 0, 128), RENDER_NO_TEXTURE);
}
menu_update(active_menu);
}
}
void race_start(void) {
active_menu = NULL;
sfx_reset();
scene_init();
camera_init(&g.camera, g.track.sections);
g.camera.update_func = camera_update_race_intro;
ships_init(g.track.sections);
droid_init(&g.droid, &g.ships[g.pilot]);
particles_init();
weapons_init();
for (int i = 0; i < len(g.race_ranks); i++) {
g.race_ranks[i].points = 0;
g.race_ranks[i].pilot = i;
}
for (int i = 0; i < len(g.lap_times); i++) {
for (int j = 0; j < len(g.lap_times[i]); j++) {
g.lap_times[i][j] = 0;
}
}
g.is_new_race_record = false;
g.is_new_lap_record = false;
g.best_lap = 0;
g.race_time = 0;
}
void race_restart(void) {
race_unpause();
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
g.lives--;
if (g.lives == 0) {
race_release_control();
active_menu = game_over_menu_init();
return;
}
}
race_start();
}
static bool sort_points_compare(pilot_points_t *pa, pilot_points_t *pb) {
return (pa->points < pb->points);
}
void race_end(void) {
race_release_control();
g.race_position = g.ships[g.pilot].position_rank;
g.race_time = 0;
g.best_lap = g.lap_times[g.pilot][0];
for (int i = 0; i < NUM_LAPS; i++) {
g.race_time += g.lap_times[g.pilot][i];
if (g.lap_times[g.pilot][i] < g.best_lap) {
g.best_lap = g.lap_times[g.pilot][i];
}
}
highscores_t *hs = &save.highscores[g.race_class][g.circut][g.highscore_tab];
if (g.best_lap < hs->lap_record) {
hs->lap_record = g.best_lap;
g.is_new_lap_record = true;
save.is_dirty = true;
}
for (int i = 0; i < NUM_HIGHSCORES; i++) {
if (g.race_time < hs->entries[i].time) {
g.is_new_race_record = true;
break;
}
}
if (g.race_type == RACE_TYPE_CHAMPIONSHIP) {
for (int i = 0; i < len(def.race_points_for_rank); i++) {
g.race_ranks[i].points = def.race_points_for_rank[i];
// Find the pilot for this race rank in the championship table
for (int j = 0; j < len(g.championship_ranks); j++) {
if (g.race_ranks[i].pilot == g.championship_ranks[j].pilot) {
g.championship_ranks[j].points += def.race_points_for_rank[i];
break;
}
}
}
sort(g.championship_ranks, len(g.championship_ranks), sort_points_compare);
}
active_menu = race_stats_menu_init();
}
void race_next(void) {
int next_circut = g.circut + 1;
// Championship complete
if (
(save.has_bonus_circuts && next_circut >= NUM_CIRCUTS) ||
(!save.has_bonus_circuts && next_circut >= NUM_NON_BONUS_CIRCUTS)
) {
if (g.race_class == RACE_CLASS_RAPIER) {
if (save.has_bonus_circuts) {
active_menu = text_scroll_menu_init(def.congratulations.rapier_all_circuts, len(def.congratulations.rapier_all_circuts));
}
else {
save.has_bonus_circuts = true;
active_menu = text_scroll_menu_init(def.congratulations.rapier, len(def.congratulations.rapier));
}
}
else {
save.has_rapier_class = true;
if (save.has_bonus_circuts) {
active_menu = text_scroll_menu_init(def.congratulations.venom_all_circuts, len(def.congratulations.venom_all_circuts));
}
else {
active_menu = text_scroll_menu_init(def.congratulations.venom, len(def.congratulations.venom));
}
}
save.is_dirty = true;
menu_is_scroll_text = true;
}
// Next track
else {
g.circut = next_circut;
game_set_scene(GAME_SCENE_RACE);
}
}
void race_release_control(void) {
flags_rm(g.ships[g.pilot].flags, SHIP_RACING);
g.ships[g.pilot].remote_thrust_max = 3160;
g.ships[g.pilot].remote_thrust_mag = 32;
g.ships[g.pilot].speed = 3160;
g.camera.update_func = camera_update_attract_random;
}
void race_pause(void) {
sfx_pause();
is_paused = true;
}
void race_unpause(void) {
sfx_unpause();
is_paused = false;
active_menu = NULL;
}

View file

@ -1,14 +1,14 @@
#ifndef RACE_H
#define RACE_H
void race_init(void);
void race_update(void);
void race_start(void);
void race_restart(void);
void race_pause(void);
void race_unpause(void);
void race_end(void);
void race_next(void);
void race_release_control(void);
#endif
#ifndef RACE_H
#define RACE_H
void race_init(void);
void race_update(void);
void race_start(void);
void race_restart(void);
void race_pause(void);
void race_unpause(void);
void race_end(void);
void race_next(void);
void race_release_control(void);
#endif

View file

@ -1,266 +1,266 @@
#include "../mem.h"
#include "../utils.h"
#include "../system.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "scene.h"
#include "droid.h"
#include "camera.h"
#include "object.h"
#include "game.h"
#define SCENE_START_BOOMS_MAX 4
#define SCENE_OIL_PUMPS_MAX 2
#define SCENE_RED_LIGHTS_MAX 4
#define SCENE_STANDS_MAX 20
static Object *scene_objects;
static Object *sky_object;
static vec3_t sky_offset;
static Object *start_booms[SCENE_START_BOOMS_MAX];
static int start_booms_len;
static Object *oil_pumps[SCENE_OIL_PUMPS_MAX];
static int oil_pumps_len;
static Object *red_lights[SCENE_RED_LIGHTS_MAX];
static int red_lights_len;
typedef struct {
sfx_t *sfx;
vec3_t pos;
} scene_stand_t;
static scene_stand_t stands[SCENE_STANDS_MAX];
static int stands_len;
static struct {
bool enabled;
GT4 *primitives[80];
int16_t *coords[80];
int16_t grey_coords[80];
} aurora_borealis;
void scene_pulsate_red_light(Object *obj);
void scene_move_oil_pump(Object *obj);
void scene_update_aurora_borealis(void);
void scene_load(const char *base_path, float sky_y_offset) {
texture_list_t scene_textures = image_get_compressed_textures(get_path(base_path, "scene.cmp"));
scene_objects = objects_load(get_path(base_path, "scene.prm"), scene_textures);
texture_list_t sky_textures = image_get_compressed_textures(get_path(base_path, "sky.cmp"));
sky_object = objects_load(get_path(base_path, "sky.prm") , sky_textures);
sky_offset = vec3(0, sky_y_offset, 0);
// Collect all objects that need to be updated each frame
start_booms_len = 0;
oil_pumps_len = 0;
red_lights_len = 0;
stands_len = 0;
Object *obj = scene_objects;
while (obj) {
mat4_set_translation(&obj->mat, obj->origin);
if (str_starts_with(obj->name, "start")) {
error_if(start_booms_len >= SCENE_START_BOOMS_MAX, "SCENE_START_BOOMS_MAX reached");
start_booms[start_booms_len++] = obj;
}
else if (str_starts_with(obj->name, "redl")) {
error_if(red_lights_len >= SCENE_RED_LIGHTS_MAX, "SCENE_RED_LIGHTS_MAX reached");
red_lights[red_lights_len++] = obj;
}
else if (str_starts_with(obj->name, "donkey")) {
error_if(oil_pumps_len >= SCENE_OIL_PUMPS_MAX, "SCENE_OIL_PUMPS_MAX reached");
oil_pumps[oil_pumps_len++] = obj;
}
else if (
str_starts_with(obj->name, "lostad") ||
str_starts_with(obj->name, "stad_") ||
str_starts_with(obj->name, "newstad_")
) {
error_if(stands_len >= SCENE_STANDS_MAX, "SCENE_STANDS_MAX reached");
stands[stands_len++] = (scene_stand_t){.sfx = NULL, .pos = obj->origin};
}
obj = obj->next;
}
aurora_borealis.enabled = false;
}
void scene_init(void) {
scene_set_start_booms(0);
for (int i = 0; i < stands_len; i++) {
stands[i].sfx = sfx_reserve_loop(SFX_CROWD);
}
}
void scene_update(void) {
for (int i = 0; i < red_lights_len; i++) {
scene_pulsate_red_light(red_lights[i]);
}
for (int i = 0; i < oil_pumps_len; i++) {
scene_move_oil_pump(oil_pumps[i]);
}
for (int i = 0; i < stands_len; i++) {
sfx_set_position(stands[i].sfx, stands[i].pos, vec3(0, 0, 0), 0.4);
}
if (aurora_borealis.enabled) {
scene_update_aurora_borealis();
}
}
void scene_draw(camera_t *camera) {
// Sky
render_set_depth_write(false);
mat4_set_translation(&sky_object->mat, vec3_add(camera->position, sky_offset));
object_draw(sky_object, &sky_object->mat);
render_set_depth_write(true);
// Objects
// Calculate the camera forward vector, so we can cull everything that's
// behind. Ideally we'd want to do a full frustum culling here. FIXME.
vec3_t cam_pos = camera->position;
vec3_t cam_dir = camera_forward(camera);
Object *object = scene_objects;
while (object) {
vec3_t diff = vec3_sub(cam_pos, object->origin);
float cam_dot = vec3_dot(diff, cam_dir);
float dist_sq = vec3_dot(diff, diff);
if (
cam_dot < object->radius &&
dist_sq < (RENDER_FADEOUT_FAR * RENDER_FADEOUT_FAR)
) {
object_draw(object, &object->mat);
}
object = object->next;
}
}
void scene_set_start_booms(int light_index) {
int lights_len = 1;
rgba_t color = rgba(0, 0, 0, 0);
if (light_index == 0) { // reset all 3
lights_len = 3;
color = rgba(0x20, 0x20, 0x20, 0xff);
}
else if (light_index == 1) {
color = rgba(0xff, 0x00, 0x00, 0xff);
}
else if (light_index == 2) {
color = rgba(0xff, 0x80, 0x00, 0xff);
}
else if (light_index == 3) {
color = rgba(0x00, 0xff, 0x00, 0xff);
}
for (int i = 0; i < start_booms_len; i++) {
Prm libPoly = {.primitive = start_booms[i]->primitives};
for (int j = 1; j < light_index; j++) {
libPoly.gt4 += 1;
}
for (int j = 0; j < lights_len; j++) {
for (int v = 0; v < 4; v++) {
libPoly.gt4->color[v].r = color.r;
libPoly.gt4->color[v].g = color.g;
libPoly.gt4->color[v].b = color.b;
}
libPoly.gt4 += 1;
}
}
}
void scene_pulsate_red_light(Object *obj) {
uint8_t r = clamp(sin(system_cycle_time() * M_PI * 2) * 128 + 128, 0, 255);
Prm libPoly = {.primitive = obj->primitives};
for (int v = 0; v < 4; v++) {
libPoly.gt4->color[v].r = r;
libPoly.gt4->color[v].g = 0x00;
libPoly.gt4->color[v].b = 0x00;
}
}
void scene_move_oil_pump(Object *pump) {
mat4_set_yaw_pitch_roll(&pump->mat, vec3(sin(system_cycle_time() * 0.125 * M_PI * 2), 0, 0));
}
void scene_init_aurora_borealis(void) {
aurora_borealis.enabled = true;
clear(aurora_borealis.grey_coords);
int count = 0;
int16_t *coords;
float y;
Prm poly = {.primitive = sky_object->primitives};
for (int i = 0; i < sky_object->primitives_len; i++) {
switch (poly.primitive->type) {
case PRM_TYPE_GT3:
poly.gt3 += 1;
break;
case PRM_TYPE_GT4:
coords = poly.gt4->coords;
y = sky_object->vertices[coords[0]].y;
if (y < -6000) { // -8000
aurora_borealis.primitives[count] = poly.gt4;
if (y > -6800) {
aurora_borealis.coords[count] = poly.gt4->coords;
aurora_borealis.grey_coords[count] = -1;
}
else if (y < -11000) {
aurora_borealis.coords[count] = poly.gt4->coords;
aurora_borealis.grey_coords[count] = -2;
}
else {
aurora_borealis.coords[count] = poly.gt4->coords;
}
count++;
}
poly.gt4 += 1;
break;
}
}
}
void scene_update_aurora_borealis(void) {
float phase = system_time() / 30.0;
for (int i = 0; i < 80; i++) {
int16_t *coords = aurora_borealis.coords[i];
if (aurora_borealis.grey_coords[i] != -2) {
aurora_borealis.primitives[i]->color[0].r = (sin(coords[0] * phase) * 64.0) + 190;
aurora_borealis.primitives[i]->color[0].g = (sin(coords[0] * (phase + 0.054)) * 64.0) + 190;
aurora_borealis.primitives[i]->color[0].b = (sin(coords[0] * (phase + 0.039)) * 64.0) + 190;
}
if (aurora_borealis.grey_coords[i] != -2) {
aurora_borealis.primitives[i]->color[1].r = (sin(coords[1] * phase) * 64.0) + 190;
aurora_borealis.primitives[i]->color[1].g = (sin(coords[1] * (phase + 0.054)) * 64.0) + 190;
aurora_borealis.primitives[i]->color[1].b = (sin(coords[1] * (phase + 0.039)) * 64.0) + 190;
}
if (aurora_borealis.grey_coords[i] != -1) {
aurora_borealis.primitives[i]->color[2].r = (sin(coords[2] * phase) * 64.0) + 190;
aurora_borealis.primitives[i]->color[2].g = (sin(coords[2] * (phase + 0.054)) * 64.0) + 190;
aurora_borealis.primitives[i]->color[2].b = (sin(coords[2] * (phase + 0.039)) * 64.0) + 190;
}
if (aurora_borealis.grey_coords[i] != -1) {
aurora_borealis.primitives[i]->color[3].r = (sin(coords[3] * phase) * 64.0) + 190;
aurora_borealis.primitives[i]->color[3].g = (sin(coords[3] * (phase + 0.054)) * 64.0) + 190;
aurora_borealis.primitives[i]->color[3].b = (sin(coords[3] * (phase + 0.039)) * 64.0) + 190;
}
}
}
#include "../mem.h"
#include "../utils.h"
#include "../system.h"
#include "object.h"
#include "track.h"
#include "ship.h"
#include "weapon.h"
#include "scene.h"
#include "droid.h"
#include "camera.h"
#include "object.h"
#include "game.h"
#define SCENE_START_BOOMS_MAX 4
#define SCENE_OIL_PUMPS_MAX 2
#define SCENE_RED_LIGHTS_MAX 4
#define SCENE_STANDS_MAX 20
static Object *scene_objects;
static Object *sky_object;
static vec3_t sky_offset;
static Object *start_booms[SCENE_START_BOOMS_MAX];
static int start_booms_len;
static Object *oil_pumps[SCENE_OIL_PUMPS_MAX];
static int oil_pumps_len;
static Object *red_lights[SCENE_RED_LIGHTS_MAX];
static int red_lights_len;
typedef struct {
sfx_t *sfx;
vec3_t pos;
} scene_stand_t;
static scene_stand_t stands[SCENE_STANDS_MAX];
static int stands_len;
static struct {
bool enabled;
GT4 *primitives[80];
int16_t *coords[80];
int16_t grey_coords[80];
} aurora_borealis;
void scene_pulsate_red_light(Object *obj);
void scene_move_oil_pump(Object *obj);
void scene_update_aurora_borealis(void);
void scene_load(const char *base_path, float sky_y_offset) {
texture_list_t scene_textures = image_get_compressed_textures(get_path(base_path, "scene.cmp"));
scene_objects = objects_load(get_path(base_path, "scene.prm"), scene_textures);
texture_list_t sky_textures = image_get_compressed_textures(get_path(base_path, "sky.cmp"));
sky_object = objects_load(get_path(base_path, "sky.prm") , sky_textures);
sky_offset = vec3(0, sky_y_offset, 0);
// Collect all objects that need to be updated each frame
start_booms_len = 0;
oil_pumps_len = 0;
red_lights_len = 0;
stands_len = 0;
Object *obj = scene_objects;
while (obj) {
mat4_set_translation(&obj->mat, obj->origin);
if (str_starts_with(obj->name, "start")) {
error_if(start_booms_len >= SCENE_START_BOOMS_MAX, "SCENE_START_BOOMS_MAX reached");
start_booms[start_booms_len++] = obj;
}
else if (str_starts_with(obj->name, "redl")) {
error_if(red_lights_len >= SCENE_RED_LIGHTS_MAX, "SCENE_RED_LIGHTS_MAX reached");
red_lights[red_lights_len++] = obj;
}
else if (str_starts_with(obj->name, "donkey")) {
error_if(oil_pumps_len >= SCENE_OIL_PUMPS_MAX, "SCENE_OIL_PUMPS_MAX reached");
oil_pumps[oil_pumps_len++] = obj;
}
else if (
str_starts_with(obj->name, "lostad") ||
str_starts_with(obj->name, "stad_") ||
str_starts_with(obj->name, "newstad_")
) {
error_if(stands_len >= SCENE_STANDS_MAX, "SCENE_STANDS_MAX reached");
stands[stands_len++] = (scene_stand_t){.sfx = NULL, .pos = obj->origin};
}
obj = obj->next;
}
aurora_borealis.enabled = false;
}
void scene_init(void) {
scene_set_start_booms(0);
for (int i = 0; i < stands_len; i++) {
stands[i].sfx = sfx_reserve_loop(SFX_CROWD);
}
}
void scene_update(void) {
for (int i = 0; i < red_lights_len; i++) {
scene_pulsate_red_light(red_lights[i]);
}
for (int i = 0; i < oil_pumps_len; i++) {
scene_move_oil_pump(oil_pumps[i]);
}
for (int i = 0; i < stands_len; i++) {
sfx_set_position(stands[i].sfx, stands[i].pos, vec3(0, 0, 0), 0.4);
}
if (aurora_borealis.enabled) {
scene_update_aurora_borealis();
}
}
void scene_draw(camera_t *camera) {
// Sky
render_set_depth_write(false);
mat4_set_translation(&sky_object->mat, vec3_add(camera->position, sky_offset));
object_draw(sky_object, &sky_object->mat);
render_set_depth_write(true);
// Objects
// Calculate the camera forward vector, so we can cull everything that's
// behind. Ideally we'd want to do a full frustum culling here. FIXME.
vec3_t cam_pos = camera->position;
vec3_t cam_dir = camera_forward(camera);
Object *object = scene_objects;
while (object) {
vec3_t diff = vec3_sub(cam_pos, object->origin);
float cam_dot = vec3_dot(diff, cam_dir);
float dist_sq = vec3_dot(diff, diff);
if (
cam_dot < object->radius &&
dist_sq < (RENDER_FADEOUT_FAR * RENDER_FADEOUT_FAR)
) {
object_draw(object, &object->mat);
}
object = object->next;
}
}
void scene_set_start_booms(int light_index) {
int lights_len = 1;
rgba_t color = rgba(0, 0, 0, 0);
if (light_index == 0) { // reset all 3
lights_len = 3;
color = rgba(0x20, 0x20, 0x20, 0xff);
}
else if (light_index == 1) {
color = rgba(0xff, 0x00, 0x00, 0xff);
}
else if (light_index == 2) {
color = rgba(0xff, 0x80, 0x00, 0xff);
}
else if (light_index == 3) {
color = rgba(0x00, 0xff, 0x00, 0xff);
}
for (int i = 0; i < start_booms_len; i++) {
Prm libPoly = {.primitive = start_booms[i]->primitives};
for (int j = 1; j < light_index; j++) {
libPoly.gt4 += 1;
}
for (int j = 0; j < lights_len; j++) {
for (int v = 0; v < 4; v++) {
libPoly.gt4->color[v].r = color.r;
libPoly.gt4->color[v].g = color.g;
libPoly.gt4->color[v].b = color.b;
}
libPoly.gt4 += 1;
}
}
}
void scene_pulsate_red_light(Object *obj) {
uint8_t r = clamp(sin(system_cycle_time() * M_PI * 2) * 128 + 128, 0, 255);
Prm libPoly = {.primitive = obj->primitives};
for (int v = 0; v < 4; v++) {
libPoly.gt4->color[v].r = r;
libPoly.gt4->color[v].g = 0x00;
libPoly.gt4->color[v].b = 0x00;
}
}
void scene_move_oil_pump(Object *pump) {
mat4_set_yaw_pitch_roll(&pump->mat, vec3(sin(system_cycle_time() * 0.125 * M_PI * 2), 0, 0));
}
void scene_init_aurora_borealis(void) {
aurora_borealis.enabled = true;
clear(aurora_borealis.grey_coords);
int count = 0;
int16_t *coords;
float y;
Prm poly = {.primitive = sky_object->primitives};
for (int i = 0; i < sky_object->primitives_len; i++) {
switch (poly.primitive->type) {
case PRM_TYPE_GT3:
poly.gt3 += 1;
break;
case PRM_TYPE_GT4:
coords = poly.gt4->coords;
y = sky_object->vertices[coords[0]].y;
if (y < -6000) { // -8000
aurora_borealis.primitives[count] = poly.gt4;
if (y > -6800) {
aurora_borealis.coords[count] = poly.gt4->coords;
aurora_borealis.grey_coords[count] = -1;
}
else if (y < -11000) {
aurora_borealis.coords[count] = poly.gt4->coords;
aurora_borealis.grey_coords[count] = -2;
}
else {
aurora_borealis.coords[count] = poly.gt4->coords;
}
count++;
}
poly.gt4 += 1;
break;
}
}
}
void scene_update_aurora_borealis(void) {
float phase = system_time() / 30.0;
for (int i = 0; i < 80; i++) {
int16_t *coords = aurora_borealis.coords[i];
if (aurora_borealis.grey_coords[i] != -2) {
aurora_borealis.primitives[i]->color[0].r = (sin(coords[0] * phase) * 64.0) + 190;
aurora_borealis.primitives[i]->color[0].g = (sin(coords[0] * (phase + 0.054)) * 64.0) + 190;
aurora_borealis.primitives[i]->color[0].b = (sin(coords[0] * (phase + 0.039)) * 64.0) + 190;
}
if (aurora_borealis.grey_coords[i] != -2) {
aurora_borealis.primitives[i]->color[1].r = (sin(coords[1] * phase) * 64.0) + 190;
aurora_borealis.primitives[i]->color[1].g = (sin(coords[1] * (phase + 0.054)) * 64.0) + 190;
aurora_borealis.primitives[i]->color[1].b = (sin(coords[1] * (phase + 0.039)) * 64.0) + 190;
}
if (aurora_borealis.grey_coords[i] != -1) {
aurora_borealis.primitives[i]->color[2].r = (sin(coords[2] * phase) * 64.0) + 190;
aurora_borealis.primitives[i]->color[2].g = (sin(coords[2] * (phase + 0.054)) * 64.0) + 190;
aurora_borealis.primitives[i]->color[2].b = (sin(coords[2] * (phase + 0.039)) * 64.0) + 190;
}
if (aurora_borealis.grey_coords[i] != -1) {
aurora_borealis.primitives[i]->color[3].r = (sin(coords[3] * phase) * 64.0) + 190;
aurora_borealis.primitives[i]->color[3].g = (sin(coords[3] * (phase + 0.054)) * 64.0) + 190;
aurora_borealis.primitives[i]->color[3].b = (sin(coords[3] * (phase + 0.039)) * 64.0) + 190;
}
}
}

View file

@ -1,14 +1,14 @@
#ifndef SCENE_H
#define SCENE_H
#include "image.h"
#include "camera.h"
void scene_load(const char *path, float sky_y_offset);
void scene_draw(camera_t *camera);
void scene_init(void);
void scene_set_start_booms(int num_lights);
void scene_init_aurora_borealis(void);
void scene_update(void);
#endif
#ifndef SCENE_H
#define SCENE_H
#include "image.h"
#include "camera.h"
void scene_load(const char *path, float sky_y_offset);
void scene_draw(camera_t *camera);
void scene_init(void);
void scene_set_start_booms(int num_lights);
void scene_init_aurora_borealis(void);
void scene_update(void);
#endif

View file

@ -1,374 +1,374 @@
#include "../mem.h"
#include "../utils.h"
#include "../render.h"
#include "../system.h"
#include "../platform.h"
#include "object.h"
#include "track.h"
#include "camera.h"
#include "object.h"
#include "game.h"
void track_load(const char *base_path) {
// Load and assemble high res track tiles
g.track.textures.start = render_textures_len();
g.track.textures.len = 0;
ttf_t *ttf = track_load_tile_format(get_path(base_path, "library.ttf"));
cmp_t *cmp = image_load_compressed(get_path(base_path, "library.cmp"));
image_t *temp_tile = image_alloc(128, 128);
for (int i = 0; i < ttf->len; i++) {
for (int tx = 0; tx < 4; tx++) {
for (int ty = 0; ty < 4; ty++) {
uint32_t sub_tile_index = ttf->tiles[i].near[ty * 4 + tx];
image_t *sub_tile = image_load_from_bytes(cmp->entries[sub_tile_index], false);
image_copy(sub_tile, temp_tile, 0, 0, 32, 32, tx * 32, ty * 32);
mem_temp_free(sub_tile);
}
}
render_texture_create(temp_tile->width, temp_tile->height, temp_tile->pixels);
g.track.textures.len++;
}
mem_temp_free(temp_tile);
mem_temp_free(cmp);
mem_temp_free(ttf);
vec3_t *vertices = track_load_vertices(get_path(base_path, "track.trv"));
track_load_faces(get_path(base_path, "track.trf"), vertices);
mem_temp_free(vertices);
track_load_sections(get_path(base_path, "track.trs"));
g.track.pickups_len = 0;
section_t *s = g.track.sections;
section_t *j = NULL;
// Nummerate all sections; take care to give both stretches at a junction
// the same numbers.
int num = 0;
do {
s->num = num++;
if (s->junction) { // start junction
j = s->junction;
do {
j->num = num++;
j = j->next;
} while (!j->junction); // end junction
num = s->num;
}
s = s->next;
} while (s != g.track.sections);
g.track.total_section_nums = num;
g.track.pickups = mem_mark();
for (int i = 0; i < g.track.section_count; i++) {
track_face_t *face = track_section_get_base_face(&g.track.sections[i]);
for (int f = 0; f < 2; f++) {
if (flags_any(face->flags, FACE_PICKUP_RIGHT | FACE_PICKUP_LEFT)) {
mem_bump(sizeof(track_pickup_t));
g.track.pickups[g.track.pickups_len].face = face;
g.track.pickups[g.track.pickups_len].cooldown_timer = 0;
g.track.pickups_len++;
}
if (flags_is(face->flags, FACE_BOOST)) {
track_face_set_color(face, rgba(0, 0, 255, 255));
}
face++;
}
}
}
ttf_t *track_load_tile_format(char *ttf_name) {
uint32_t ttf_size;
uint8_t *ttf_bytes = platform_load_asset(ttf_name, &ttf_size);
uint32_t p = 0;
uint32_t num_tiles = ttf_size / 42;
ttf_t *ttf = mem_temp_alloc(sizeof(ttf_t) + sizeof(ttf_tile_t) * num_tiles);
ttf->len = num_tiles;
for (int t = 0; t < num_tiles; t++) {
for (int i = 0; i < 16; i++) {
ttf->tiles[t].near[i] = get_i16(ttf_bytes, &p);
}
for (int i = 0; i < 4; i++) {
ttf->tiles[t].med[i] = get_i16(ttf_bytes, &p);
}
ttf->tiles[t].far = get_i16(ttf_bytes, &p);
}
mem_temp_free(ttf_bytes);
return ttf;
}
bool track_collect_pickups(track_face_t *face) {
if (flags_is(face->flags, FACE_PICKUP_ACTIVE)) {
flags_rm(face->flags, FACE_PICKUP_ACTIVE);
flags_add(face->flags, FACE_PICKUP_COLLECTED);
track_face_set_color(face, rgba(255, 255, 255, 255));
return true;
}
else {
return false;
}
}
vec3_t *track_load_vertices(char *file_name) {
uint32_t size;
uint8_t *bytes = platform_load_asset(file_name, &size);
g.track.vertex_count = size / 16; // VECTOR_SIZE
vec3_t *vertices = mem_temp_alloc(sizeof(vec3_t) * g.track.vertex_count);
uint32_t p = 0;
for (int i = 0; i < g.track.vertex_count; i++) {
vertices[i].x = get_i32(bytes, &p);
vertices[i].y = get_i32(bytes, &p);
vertices[i].z = get_i32(bytes, &p);
p += 4; // padding
}
mem_temp_free(bytes);
return vertices;
}
static const vec2_t track_uv[2][4] = {
{{128, 0}, { 0, 0}, { 0, 128}, {128, 128}},
{{ 0, 0}, {128, 0}, {128, 128}, { 0, 128}}
};
void track_load_faces(char *file_name, vec3_t *vertices) {
uint32_t size;
uint8_t *bytes = platform_load_asset(file_name, &size);
g.track.face_count = size / 20; // TRACK_FACE_DATA_SIZE
g.track.faces = mem_bump(sizeof(track_face_t) * g.track.face_count);
uint32_t p = 0;
track_face_t *tf = g.track.faces;
for (int i = 0; i < g.track.face_count; i++) {
vec3_t v0 = vertices[get_i16(bytes, &p)];
vec3_t v1 = vertices[get_i16(bytes, &p)];
vec3_t v2 = vertices[get_i16(bytes, &p)];
vec3_t v3 = vertices[get_i16(bytes, &p)];
tf->normal.x = (float)get_i16(bytes, &p) / 4096.0;
tf->normal.y = (float)get_i16(bytes, &p) / 4096.0;
tf->normal.z = (float)get_i16(bytes, &p) / 4096.0;
tf->texture = get_i8(bytes, &p);
tf->flags = get_i8(bytes, &p);
rgba_t color = rgba_from_u32(get_u32(bytes, &p));
const vec2_t *uv = track_uv[flags_is(tf->flags, FACE_FLIP_TEXTURE) ? 1 : 0];
tf->tris[0] = (tris_t){
.vertices = {
{.pos = v0, .uv = uv[0], .color = color},
{.pos = v1, .uv = uv[1], .color = color},
{.pos = v2, .uv = uv[2], .color = color},
}
};
tf->tris[1] = (tris_t){
.vertices = {
{.pos = v3, .uv = uv[3], .color = color},
{.pos = v0, .uv = uv[0], .color = color},
{.pos = v2, .uv = uv[2], .color = color},
}
};
tf++;
}
mem_temp_free(bytes);
}
void track_load_sections(char *file_name) {
uint32_t size;
uint8_t *bytes = platform_load_asset(file_name, &size);
g.track.section_count = size / 156; // SECTION_DATA_SIZE
g.track.sections = mem_bump(sizeof(section_t) * g.track.section_count);
uint32_t p = 0;
section_t *ts = g.track.sections;
for (int i = 0; i < g.track.section_count; i++) {
int32_t junction_index = get_i32(bytes, &p);
if (junction_index != -1) {
ts->junction = g.track.sections + junction_index;
}
else {
ts->junction = NULL;
}
ts->prev = g.track.sections + get_i32(bytes, &p);
ts->next = g.track.sections + get_i32(bytes, &p);
ts->center.x = get_i32(bytes, &p);
ts->center.y = get_i32(bytes, &p);
ts->center.z = get_i32(bytes, &p);
int16_t version = get_i16(bytes, &p);
error_if(version != TRACK_VERSION, "Convert track with track10: section: %d Track: %d\n", version, TRACK_VERSION);
p += 2; // padding
p += 4 + 4; // objects pointer, objectCount
p += 5 * 3 * 4; // view section pointers
p += 5 * 3 * 2; // view section counts
p += 4 * 2; // high list
p += 4 * 2; // med list
ts->face_start = get_i16(bytes, &p);
ts->face_count = get_i16(bytes, &p);
p += 2 * 2; // global/local radius
ts->flags = get_i16(bytes, &p);
ts->num = get_i16(bytes, &p);
p += 2; // padding
ts++;
}
mem_temp_free(bytes);
}
void track_draw_section(section_t *section) {
track_face_t *face = g.track.faces + section->face_start;
int16_t face_count = section->face_count;
for (uint32_t j = 0; j < face_count; j++) {
uint16_t tex_index = texture_from_list(g.track.textures, face->texture);
render_push_tris(face->tris[0], tex_index);
render_push_tris(face->tris[1], tex_index);
face++;
}
}
void track_draw(camera_t *camera) {
render_set_model_mat(&mat4_identity());
// Calculate the camera forward vector, so we can cull everything that's
// behind. Ideally we'd want to do a full frustum culling here. FIXME.
vec3_t cam_pos = camera->position;
vec3_t cam_dir = camera_forward(camera);
int drawn = 0;
int skipped = 0;
for(int32_t i = 0; i < g.track.section_count; i++) {
section_t *s = &g.track.sections[i];
vec3_t diff = vec3_sub(cam_pos, s->center);
float cam_dot = vec3_dot(diff, cam_dir);
float dist_sq = vec3_dot(diff, diff);
if (
cam_dot < 2048 && // FIXME: should use the bounding radius of the section
dist_sq < (RENDER_FADEOUT_FAR * RENDER_FADEOUT_FAR)
) {
track_draw_section(s);
}
}
}
void track_cycle_pickups(void) {
float pickup_cycle_time = 1.5 * system_cycle_time();
for (int i = 0; i < g.track.pickups_len; i++) {
if (flags_is(g.track.pickups[i].face->flags, FACE_PICKUP_COLLECTED)) {
flags_rm(g.track.pickups[i].face->flags, FACE_PICKUP_COLLECTED);
g.track.pickups[i].cooldown_timer = TRACK_PICKUP_COOLDOWN_TIME;
}
else if (g.track.pickups[i].cooldown_timer <= 0) {
flags_add(g.track.pickups[i].face->flags, FACE_PICKUP_ACTIVE);
track_face_set_color(g.track.pickups[i].face, rgba(
sin( pickup_cycle_time + i) * 127 + 128,
cos( pickup_cycle_time + i) * 127 + 128,
sin(-pickup_cycle_time - i) * 127 + 128,
255
));
}
else{
g.track.pickups[i].cooldown_timer -= system_tick();
}
}
}
void track_face_set_color(track_face_t *face, rgba_t color) {
face->tris[0].vertices[0].color = color;
face->tris[0].vertices[1].color = color;
face->tris[0].vertices[2].color = color;
face->tris[1].vertices[0].color = color;
face->tris[1].vertices[1].color = color;
face->tris[1].vertices[2].color = color;
}
track_face_t *track_section_get_base_face(section_t *section) {
track_face_t *face = g.track.faces +section->face_start;
while(flags_not(face->flags, FACE_TRACK_BASE)) {
face++;
}
return face;
}
section_t *track_nearest_section(vec3_t pos, section_t *section, float *distance) {
// Start search several sections before current section
for (int i = 0; i < TRACK_SEARCH_LOOK_BACK; i++) {
section = section->prev;
}
// Find vector from ship center to track section under
// consideration
float shortest_distance = 1000000000.0;
section_t *nearest_section = section;
section_t *junction = NULL;
for (int i = 0; i < TRACK_SEARCH_LOOK_AHEAD; i++) {
if (section->junction) {
junction = section->junction;
}
float d = vec3_len(vec3_sub(pos, section->center));
if (d < shortest_distance) {
shortest_distance = d;
nearest_section = section;
}
section = section->next;
}
if (junction) {
section = junction;
for (int i = 0; i < TRACK_SEARCH_LOOK_AHEAD; i++) {
float d = vec3_len(vec3_sub(pos, section->center));
if (d < shortest_distance) {
shortest_distance = d;
nearest_section = section;
}
if (flags_is(junction->flags, SECTION_JUNCTION_START)) {
section = section->next;
}
else {
section = section->prev;
}
}
}
if (distance != NULL) {
*distance = shortest_distance;
}
return nearest_section;
}
#include "../mem.h"
#include "../utils.h"
#include "../render.h"
#include "../system.h"
#include "../platform.h"
#include "object.h"
#include "track.h"
#include "camera.h"
#include "object.h"
#include "game.h"
void track_load(const char *base_path) {
// Load and assemble high res track tiles
g.track.textures.start = render_textures_len();
g.track.textures.len = 0;
ttf_t *ttf = track_load_tile_format(get_path(base_path, "library.ttf"));
cmp_t *cmp = image_load_compressed(get_path(base_path, "library.cmp"));
image_t *temp_tile = image_alloc(128, 128);
for (int i = 0; i < ttf->len; i++) {
for (int tx = 0; tx < 4; tx++) {
for (int ty = 0; ty < 4; ty++) {
uint32_t sub_tile_index = ttf->tiles[i].near[ty * 4 + tx];
image_t *sub_tile = image_load_from_bytes(cmp->entries[sub_tile_index], false);
image_copy(sub_tile, temp_tile, 0, 0, 32, 32, tx * 32, ty * 32);
mem_temp_free(sub_tile);
}
}
render_texture_create(temp_tile->width, temp_tile->height, temp_tile->pixels);
g.track.textures.len++;
}
mem_temp_free(temp_tile);
mem_temp_free(cmp);
mem_temp_free(ttf);
vec3_t *vertices = track_load_vertices(get_path(base_path, "track.trv"));
track_load_faces(get_path(base_path, "track.trf"), vertices);
mem_temp_free(vertices);
track_load_sections(get_path(base_path, "track.trs"));
g.track.pickups_len = 0;
section_t *s = g.track.sections;
section_t *j = NULL;
// Nummerate all sections; take care to give both stretches at a junction
// the same numbers.
int num = 0;
do {
s->num = num++;
if (s->junction) { // start junction
j = s->junction;
do {
j->num = num++;
j = j->next;
} while (!j->junction); // end junction
num = s->num;
}
s = s->next;
} while (s != g.track.sections);
g.track.total_section_nums = num;
g.track.pickups = mem_mark();
for (int i = 0; i < g.track.section_count; i++) {
track_face_t *face = track_section_get_base_face(&g.track.sections[i]);
for (int f = 0; f < 2; f++) {
if (flags_any(face->flags, FACE_PICKUP_RIGHT | FACE_PICKUP_LEFT)) {
mem_bump(sizeof(track_pickup_t));
g.track.pickups[g.track.pickups_len].face = face;
g.track.pickups[g.track.pickups_len].cooldown_timer = 0;
g.track.pickups_len++;
}
if (flags_is(face->flags, FACE_BOOST)) {
track_face_set_color(face, rgba(0, 0, 255, 255));
}
face++;
}
}
}
ttf_t *track_load_tile_format(char *ttf_name) {
uint32_t ttf_size;
uint8_t *ttf_bytes = platform_load_asset(ttf_name, &ttf_size);
uint32_t p = 0;
uint32_t num_tiles = ttf_size / 42;
ttf_t *ttf = mem_temp_alloc(sizeof(ttf_t) + sizeof(ttf_tile_t) * num_tiles);
ttf->len = num_tiles;
for (int t = 0; t < num_tiles; t++) {
for (int i = 0; i < 16; i++) {
ttf->tiles[t].near[i] = get_i16(ttf_bytes, &p);
}
for (int i = 0; i < 4; i++) {
ttf->tiles[t].med[i] = get_i16(ttf_bytes, &p);
}
ttf->tiles[t].far = get_i16(ttf_bytes, &p);
}
mem_temp_free(ttf_bytes);
return ttf;
}
bool track_collect_pickups(track_face_t *face) {
if (flags_is(face->flags, FACE_PICKUP_ACTIVE)) {
flags_rm(face->flags, FACE_PICKUP_ACTIVE);
flags_add(face->flags, FACE_PICKUP_COLLECTED);
track_face_set_color(face, rgba(255, 255, 255, 255));
return true;
}
else {
return false;
}
}
vec3_t *track_load_vertices(char *file_name) {
uint32_t size;
uint8_t *bytes = platform_load_asset(file_name, &size);
g.track.vertex_count = size / 16; // VECTOR_SIZE
vec3_t *vertices = mem_temp_alloc(sizeof(vec3_t) * g.track.vertex_count);
uint32_t p = 0;
for (int i = 0; i < g.track.vertex_count; i++) {
vertices[i].x = get_i32(bytes, &p);
vertices[i].y = get_i32(bytes, &p);
vertices[i].z = get_i32(bytes, &p);
p += 4; // padding
}
mem_temp_free(bytes);
return vertices;
}
static const vec2_t track_uv[2][4] = {
{{128, 0}, { 0, 0}, { 0, 128}, {128, 128}},
{{ 0, 0}, {128, 0}, {128, 128}, { 0, 128}}
};
void track_load_faces(char *file_name, vec3_t *vertices) {
uint32_t size;
uint8_t *bytes = platform_load_asset(file_name, &size);
g.track.face_count = size / 20; // TRACK_FACE_DATA_SIZE
g.track.faces = mem_bump(sizeof(track_face_t) * g.track.face_count);
uint32_t p = 0;
track_face_t *tf = g.track.faces;
for (int i = 0; i < g.track.face_count; i++) {
vec3_t v0 = vertices[get_i16(bytes, &p)];
vec3_t v1 = vertices[get_i16(bytes, &p)];
vec3_t v2 = vertices[get_i16(bytes, &p)];
vec3_t v3 = vertices[get_i16(bytes, &p)];
tf->normal.x = (float)get_i16(bytes, &p) / 4096.0;
tf->normal.y = (float)get_i16(bytes, &p) / 4096.0;
tf->normal.z = (float)get_i16(bytes, &p) / 4096.0;
tf->texture = get_i8(bytes, &p);
tf->flags = get_i8(bytes, &p);
rgba_t color = rgba_from_u32(get_u32(bytes, &p));
const vec2_t *uv = track_uv[flags_is(tf->flags, FACE_FLIP_TEXTURE) ? 1 : 0];
tf->tris[0] = (tris_t){
.vertices = {
{.pos = v0, .uv = uv[0], .color = color},
{.pos = v1, .uv = uv[1], .color = color},
{.pos = v2, .uv = uv[2], .color = color},
}
};
tf->tris[1] = (tris_t){
.vertices = {
{.pos = v3, .uv = uv[3], .color = color},
{.pos = v0, .uv = uv[0], .color = color},
{.pos = v2, .uv = uv[2], .color = color},
}
};
tf++;
}
mem_temp_free(bytes);
}
void track_load_sections(char *file_name) {
uint32_t size;
uint8_t *bytes = platform_load_asset(file_name, &size);
g.track.section_count = size / 156; // SECTION_DATA_SIZE
g.track.sections = mem_bump(sizeof(section_t) * g.track.section_count);
uint32_t p = 0;
section_t *ts = g.track.sections;
for (int i = 0; i < g.track.section_count; i++) {
int32_t junction_index = get_i32(bytes, &p);
if (junction_index != -1) {
ts->junction = g.track.sections + junction_index;
}
else {
ts->junction = NULL;
}
ts->prev = g.track.sections + get_i32(bytes, &p);
ts->next = g.track.sections + get_i32(bytes, &p);
ts->center.x = get_i32(bytes, &p);
ts->center.y = get_i32(bytes, &p);
ts->center.z = get_i32(bytes, &p);
int16_t version = get_i16(bytes, &p);
error_if(version != TRACK_VERSION, "Convert track with track10: section: %d Track: %d\n", version, TRACK_VERSION);
p += 2; // padding
p += 4 + 4; // objects pointer, objectCount
p += 5 * 3 * 4; // view section pointers
p += 5 * 3 * 2; // view section counts
p += 4 * 2; // high list
p += 4 * 2; // med list
ts->face_start = get_i16(bytes, &p);
ts->face_count = get_i16(bytes, &p);
p += 2 * 2; // global/local radius
ts->flags = get_i16(bytes, &p);
ts->num = get_i16(bytes, &p);
p += 2; // padding
ts++;
}
mem_temp_free(bytes);
}
void track_draw_section(section_t *section) {
track_face_t *face = g.track.faces + section->face_start;
int16_t face_count = section->face_count;
for (uint32_t j = 0; j < face_count; j++) {
uint16_t tex_index = texture_from_list(g.track.textures, face->texture);
render_push_tris(face->tris[0], tex_index);
render_push_tris(face->tris[1], tex_index);
face++;
}
}
void track_draw(camera_t *camera) {
render_set_model_mat(&mat4_identity());
// Calculate the camera forward vector, so we can cull everything that's
// behind. Ideally we'd want to do a full frustum culling here. FIXME.
vec3_t cam_pos = camera->position;
vec3_t cam_dir = camera_forward(camera);
int drawn = 0;
int skipped = 0;
for(int32_t i = 0; i < g.track.section_count; i++) {
section_t *s = &g.track.sections[i];
vec3_t diff = vec3_sub(cam_pos, s->center);
float cam_dot = vec3_dot(diff, cam_dir);
float dist_sq = vec3_dot(diff, diff);
if (
cam_dot < 2048 && // FIXME: should use the bounding radius of the section
dist_sq < (RENDER_FADEOUT_FAR * RENDER_FADEOUT_FAR)
) {
track_draw_section(s);
}
}
}
void track_cycle_pickups(void) {
float pickup_cycle_time = 1.5 * system_cycle_time();
for (int i = 0; i < g.track.pickups_len; i++) {
if (flags_is(g.track.pickups[i].face->flags, FACE_PICKUP_COLLECTED)) {
flags_rm(g.track.pickups[i].face->flags, FACE_PICKUP_COLLECTED);
g.track.pickups[i].cooldown_timer = TRACK_PICKUP_COOLDOWN_TIME;
}
else if (g.track.pickups[i].cooldown_timer <= 0) {
flags_add(g.track.pickups[i].face->flags, FACE_PICKUP_ACTIVE);
track_face_set_color(g.track.pickups[i].face, rgba(
sin( pickup_cycle_time + i) * 127 + 128,
cos( pickup_cycle_time + i) * 127 + 128,
sin(-pickup_cycle_time - i) * 127 + 128,
255
));
}
else{
g.track.pickups[i].cooldown_timer -= system_tick();
}
}
}
void track_face_set_color(track_face_t *face, rgba_t color) {
face->tris[0].vertices[0].color = color;
face->tris[0].vertices[1].color = color;
face->tris[0].vertices[2].color = color;
face->tris[1].vertices[0].color = color;
face->tris[1].vertices[1].color = color;
face->tris[1].vertices[2].color = color;
}
track_face_t *track_section_get_base_face(section_t *section) {
track_face_t *face = g.track.faces +section->face_start;
while(flags_not(face->flags, FACE_TRACK_BASE)) {
face++;
}
return face;
}
section_t *track_nearest_section(vec3_t pos, section_t *section, float *distance) {
// Start search several sections before current section
for (int i = 0; i < TRACK_SEARCH_LOOK_BACK; i++) {
section = section->prev;
}
// Find vector from ship center to track section under
// consideration
float shortest_distance = 1000000000.0;
section_t *nearest_section = section;
section_t *junction = NULL;
for (int i = 0; i < TRACK_SEARCH_LOOK_AHEAD; i++) {
if (section->junction) {
junction = section->junction;
}
float d = vec3_len(vec3_sub(pos, section->center));
if (d < shortest_distance) {
shortest_distance = d;
nearest_section = section;
}
section = section->next;
}
if (junction) {
section = junction;
for (int i = 0; i < TRACK_SEARCH_LOOK_AHEAD; i++) {
float d = vec3_len(vec3_sub(pos, section->center));
if (d < shortest_distance) {
shortest_distance = d;
nearest_section = section;
}
if (flags_is(junction->flags, SECTION_JUNCTION_START)) {
section = section->next;
}
else {
section = section->prev;
}
}
}
if (distance != NULL) {
*distance = shortest_distance;
}
return nearest_section;
}

View file

@ -1,96 +1,96 @@
#ifndef TRACK_H
#define TRACK_H
#include "../types.h"
#include "object.h"
#include "image.h"
#define TRACK_VERSION 8
#define TRACK_PICKUP_COOLDOWN_TIME 1
#define TRACK_SEARCH_LOOK_BACK 3
#define TRACK_SEARCH_LOOK_AHEAD 6
typedef struct track_face_t {
tris_t tris[2];
vec3_t normal;
uint8_t flags;
uint8_t texture;
} track_face_t;
#define FACE_TRACK_BASE (1<<0)
#define FACE_PICKUP_LEFT (1<<1)
#define FACE_FLIP_TEXTURE (1<<2)
#define FACE_PICKUP_RIGHT (1<<3)
#define FACE_START_GRID (1<<4)
#define FACE_BOOST (1<<5)
#define FACE_PICKUP_COLLECTED (1<<6)
#define FACE_PICKUP_ACTIVE (1<<7)
typedef struct {
uint16_t near[16];
uint16_t med[4];
uint16_t far;
} ttf_tile_t;
typedef struct {
uint32_t len;
ttf_tile_t tiles[];
} ttf_t;
typedef struct section_t {
struct section_t *junction;
struct section_t *prev;
struct section_t *next;
vec3_t center;
int16_t face_start;
int16_t face_count;
int16_t flags;
int16_t num;
} section_t;
#define SECTION_JUMP 1
#define SECTION_JUNCTION_END 8
#define SECTION_JUNCTION_START 16
#define SECTION_JUNCTION 32
typedef struct {
track_face_t *face;
float cooldown_timer;
} track_pickup_t;
typedef struct track_t {
int32_t vertex_count;
int32_t face_count;
int32_t section_count;
int32_t pickups_len;
int32_t total_section_nums;
texture_list_t textures;
track_face_t *faces;
section_t *sections;
track_pickup_t *pickups;
} track_t;
void track_load(const char *base_path);
ttf_t *track_load_tile_format(char *ttf_name);
vec3_t *track_load_vertices(char *file);
void track_load_faces(char *file, vec3_t *vertices);
void track_load_sections(char *file);
bool track_collect_pickups(track_face_t *face);
void track_face_set_color(track_face_t *face, rgba_t color);
track_face_t *track_section_get_base_face(section_t *section);
section_t *track_nearest_section(vec3_t pos, section_t *section, float *distance);
struct camera_t;
void track_draw(struct camera_t *camera);
void track_cycle_pickups(void);
#endif
#ifndef TRACK_H
#define TRACK_H
#include "../types.h"
#include "object.h"
#include "image.h"
#define TRACK_VERSION 8
#define TRACK_PICKUP_COOLDOWN_TIME 1
#define TRACK_SEARCH_LOOK_BACK 3
#define TRACK_SEARCH_LOOK_AHEAD 6
typedef struct track_face_t {
tris_t tris[2];
vec3_t normal;
uint8_t flags;
uint8_t texture;
} track_face_t;
#define FACE_TRACK_BASE (1<<0)
#define FACE_PICKUP_LEFT (1<<1)
#define FACE_FLIP_TEXTURE (1<<2)
#define FACE_PICKUP_RIGHT (1<<3)
#define FACE_START_GRID (1<<4)
#define FACE_BOOST (1<<5)
#define FACE_PICKUP_COLLECTED (1<<6)
#define FACE_PICKUP_ACTIVE (1<<7)
typedef struct {
uint16_t near[16];
uint16_t med[4];
uint16_t far;
} ttf_tile_t;
typedef struct {
uint32_t len;
ttf_tile_t tiles[];
} ttf_t;
typedef struct section_t {
struct section_t *junction;
struct section_t *prev;
struct section_t *next;
vec3_t center;
int16_t face_start;
int16_t face_count;
int16_t flags;
int16_t num;
} section_t;
#define SECTION_JUMP 1
#define SECTION_JUNCTION_END 8
#define SECTION_JUNCTION_START 16
#define SECTION_JUNCTION 32
typedef struct {
track_face_t *face;
float cooldown_timer;
} track_pickup_t;
typedef struct track_t {
int32_t vertex_count;
int32_t face_count;
int32_t section_count;
int32_t pickups_len;
int32_t total_section_nums;
texture_list_t textures;
track_face_t *faces;
section_t *sections;
track_pickup_t *pickups;
} track_t;
void track_load(const char *base_path);
ttf_t *track_load_tile_format(char *ttf_name);
vec3_t *track_load_vertices(char *file);
void track_load_faces(char *file, vec3_t *vertices);
void track_load_sections(char *file);
bool track_collect_pickups(track_face_t *face);
void track_face_set_color(track_face_t *face, rgba_t color);
track_face_t *track_section_get_base_face(section_t *section);
section_t *track_nearest_section(vec3_t pos, section_t *section, float *distance);
struct camera_t;
void track_draw(struct camera_t *camera);
void track_cycle_pickups(void);
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,51 +1,51 @@
#ifndef WEAPON_H
#define WEAPON_H
#define WEAPONS_MAX 64
#define WEAPON_MINE_DURATION (450 * (1.0/30.0))
#define WEAPON_ROCKET_DURATION (200 * (1.0/30.0))
#define WEAPON_EBOLT_DURATION (140 * (1.0/30.0))
#define WEAPON_REV_CON_DURATION (60 * (1.0/30.0))
#define WEAPON_MISSILE_DURATION (200 * (1.0/30.0))
#define WEAPON_SHIELD_DURATION (200 * (1.0/30.0))
#define WEAPON_FLARE_DURATION (200 * (1.0/30.0))
#define WEAPON_SPECIAL_DURATION (400 * (1.0/30.0))
#define WEAPON_MINE_RELEASE_RATE (3 * (1.0/30.0))
#define WEAPON_DELAY (40 * (1.0/30.0))
#define WEAPON_TYPE_NONE 0
#define WEAPON_TYPE_MINE 1
#define WEAPON_TYPE_MISSILE 2
#define WEAPON_TYPE_ROCKET 3
#define WEAPON_TYPE_SPECIAL 4
#define WEAPON_TYPE_EBOLT 5
#define WEAPON_TYPE_FLARE 6
#define WEAPON_TYPE_REV_CON 7
#define WEAPON_TYPE_SHIELD 8
#define WEAPON_TYPE_TURBO 9
#define WEAPON_TYPE_MAX 10
#define WEAPON_MINE_COUNT 5
#define WEAPON_HIT_NONE 0
#define WEAPON_HIT_SHIP 1
#define WEAPON_HIT_TRACK 2
#define WEAPON_PARTICLE_SPAWN_RATE 0.011
#define WEAPON_AI_DELAY 1.1
#define WEAPON_CLASS_ANY 1
#define WEAPON_CLASS_PROJECTILE 2
void weapons_load(void);
void weapons_init(void);
void weapons_fire(ship_t *ship, int weapon_type);
void weapons_fire_delayed(ship_t *ship, int weapon_type);
void weapons_update(void);
void weapons_draw(void);
int weapon_get_random_type(int type_class);
#endif
#ifndef WEAPON_H
#define WEAPON_H
#define WEAPONS_MAX 64
#define WEAPON_MINE_DURATION (450 * (1.0/30.0))
#define WEAPON_ROCKET_DURATION (200 * (1.0/30.0))
#define WEAPON_EBOLT_DURATION (140 * (1.0/30.0))
#define WEAPON_REV_CON_DURATION (60 * (1.0/30.0))
#define WEAPON_MISSILE_DURATION (200 * (1.0/30.0))
#define WEAPON_SHIELD_DURATION (200 * (1.0/30.0))
#define WEAPON_FLARE_DURATION (200 * (1.0/30.0))
#define WEAPON_SPECIAL_DURATION (400 * (1.0/30.0))
#define WEAPON_MINE_RELEASE_RATE (3 * (1.0/30.0))
#define WEAPON_DELAY (40 * (1.0/30.0))
#define WEAPON_TYPE_NONE 0
#define WEAPON_TYPE_MINE 1
#define WEAPON_TYPE_MISSILE 2
#define WEAPON_TYPE_ROCKET 3
#define WEAPON_TYPE_SPECIAL 4
#define WEAPON_TYPE_EBOLT 5
#define WEAPON_TYPE_FLARE 6
#define WEAPON_TYPE_REV_CON 7
#define WEAPON_TYPE_SHIELD 8
#define WEAPON_TYPE_TURBO 9
#define WEAPON_TYPE_MAX 10
#define WEAPON_MINE_COUNT 5
#define WEAPON_HIT_NONE 0
#define WEAPON_HIT_SHIP 1
#define WEAPON_HIT_TRACK 2
#define WEAPON_PARTICLE_SPAWN_RATE 0.011
#define WEAPON_AI_DELAY 1.1
#define WEAPON_CLASS_ANY 1
#define WEAPON_CLASS_PROJECTILE 2
void weapons_load(void);
void weapons_init(void);
void weapons_fire(ship_t *ship, int weapon_type);
void weapons_fire_delayed(ship_t *ship, int weapon_type);
void weapons_update(void);
void weapons_draw(void);
int weapon_get_random_type(int type_class);
#endif