diff --git a/src/wipeout/camera.c b/src/wipeout/camera.c index 8b4304b..455c88c 100755 --- a/src/wipeout/camera.c +++ b/src/wipeout/camera.c @@ -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); +} diff --git a/src/wipeout/camera.h b/src/wipeout/camera.h index e846700..40d8dc9 100755 --- a/src/wipeout/camera.h +++ b/src/wipeout/camera.h @@ -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 diff --git a/src/wipeout/droid.c b/src/wipeout/droid.c index 694088e..340c378 100755 --- a/src/wipeout/droid.c +++ b/src/wipeout/droid.c @@ -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; + } + } +} diff --git a/src/wipeout/droid.h b/src/wipeout/droid.h index b5e21b9..0fa1905 100755 --- a/src/wipeout/droid.h +++ b/src/wipeout/droid.h @@ -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 diff --git a/src/wipeout/game.c b/src/wipeout/game.c index 6dc8bf6..a3278d2 100755 --- a/src/wipeout/game.c +++ b/src/wipeout/game.c @@ -1,647 +1,647 @@ -#include - -#include "../mem.h" -#include "../utils.h" -#include "../system.h" -#include "../platform.h" -#include "../input.h" - -#include "game.h" -#include "ship.h" -#include "weapon.h" -#include "droid.h" -#include "object.h" -#include "hud.h" -#include "game.h" -#include "sfx.h" -#include "ui.h" -#include "particle.h" -#include "race.h" -#include "main_menu.h" -#include "title.h" -#include "intro.h" - -#define TURN_ACCEL(V) NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(YAW_VELOCITY(V)))) -#define TURN_VEL(V) NTSC_VELOCITY(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(YAW_VELOCITY(V)))) - -const game_def_t def = { - .race_classes = { - [RACE_CLASS_VENOM] = {.name = "VENOM CLASS"}, - [RACE_CLASS_RAPIER] = {.name = "RAPIER CLASS"}, - }, - - .race_types = { - [RACE_TYPE_CHAMPIONSHIP] = {.name = "CHAMPIONSHIP RACE"}, - [RACE_TYPE_SINGLE] = {.name = "SINGLE RACE"}, - [RACE_TYPE_TIME_TRIAL] = {.name = "TIME TRIAL"}, - }, - - .pilots = { - [PILOT_JOHN_DEKKA] = {.name = "JOHN DEKKA", .portrait = "wipeout/textures/dekka.cmp", .team = 0, .logo_model = 0}, - [PILOT_DANIEL_CHANG] = {.name = "DANIEL CHANG", .portrait = "wipeout/textures/chang.cmp", .team = 0, .logo_model = 4}, - [PILOT_ARIAL_TETSUO] = {.name = "ARIAL TETSUO", .portrait = "wipeout/textures/arial.cmp", .team = 1, .logo_model = 6}, - [PILOT_ANASTASIA_CHEROVOSKI] = {.name = "ANASTASIA CHEROVOSKI", .portrait = "wipeout/textures/anast.cmp", .team = 1, .logo_model = 7}, - [PILOT_KEL_SOLAAR] = {.name = "KEL SOLAAR", .portrait = "wipeout/textures/solar.cmp", .team = 2, .logo_model = 2}, - [PILOT_ARIAN_TETSUO] = {.name = "ARIAN TETSUO", .portrait = "wipeout/textures/arian.cmp", .team = 2, .logo_model = 5}, - [PILOT_SOFIA_DE_LA_RENTE] = {.name = "SOFIA DE LA RENTE", .portrait = "wipeout/textures/sophi.cmp", .team = 3, .logo_model = 1}, - [PILOT_PAUL_JACKSON] = {.name = "PAUL JACKSON", .portrait = "wipeout/textures/paul.cmp", .team = 3, .logo_model = 3}, - }, - - .ship_model_to_pilot = {6, 4, 7, 1, 5, 2, 3, 0}, - .race_points_for_rank = {9, 7, 5, 3, 2, 1, 0, 0}, - - // SHIP ATTRIBUTES - // TEAM 1 TEAM 2 TEAM 3 TEAM 4 - // Acceleration: *** ***** ** **** - // Top Speed: **** ** **** *** - // Armour: ***** *** **** ** - // Turn Rate: ** **** *** ***** - - .teams = { - [TEAM_AG_SYSTEMS] = { - .name = "AG SYSTEMS", - .logo_model = 2, - .pilots = {0, 1}, - .attributes = { - [RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 790, .resistance = 140, .turn_rate = TURN_ACCEL(160), .turn_rate_max = TURN_VEL(2560), .skid = 12}, - [RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1200, .resistance = 140, .turn_rate = TURN_ACCEL(160), .turn_rate_max = TURN_VEL(2560), .skid = 10}, - }, - }, - [TEAM_AURICOM] = { - .name = "AURICOM", - .logo_model = 3, - .pilots = {2, 3}, - .attributes = { - [RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 850, .resistance = 134, .turn_rate = TURN_ACCEL(140), .turn_rate_max = TURN_VEL(1920), .skid = 20}, - [RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1400, .resistance = 140, .turn_rate = TURN_ACCEL(120), .turn_rate_max = TURN_VEL(1920), .skid = 14}, - }, - }, - [TEAM_QIREX] = { - .name = "QIREX", - .logo_model = 1, - .pilots = {4, 5}, - .attributes = { - [RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 850, .resistance = 140, .turn_rate = TURN_ACCEL(120), .turn_rate_max = TURN_VEL(1920), .skid = 24}, - [RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1400, .resistance = 130, .turn_rate = TURN_ACCEL(140), .turn_rate_max = TURN_VEL(1920), .skid = 16}, - }, - }, - [TEAM_FEISAR] = { - .name = "FEISAR", - .logo_model = 0, - .pilots = {6, 7}, - .attributes = { - [RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 790, .resistance = 134, .turn_rate = TURN_ACCEL(180), .turn_rate_max = TURN_VEL(2560), .skid = 12}, - [RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1200, .resistance = 130, .turn_rate = TURN_ACCEL(180), .turn_rate_max = TURN_VEL(2560), .skid = 8}, - }, - }, - }, - - .ai_settings = { - [RACE_CLASS_VENOM] = { - {.thrust_max = 2550, .thrust_magnitude = 44, .fight_back = 1}, - {.thrust_max = 2600, .thrust_magnitude = 45, .fight_back = 1}, - {.thrust_max = 2630, .thrust_magnitude = 45, .fight_back = 1}, - {.thrust_max = 2660, .thrust_magnitude = 46, .fight_back = 1}, - {.thrust_max = 2700, .thrust_magnitude = 47, .fight_back = 1}, - {.thrust_max = 2720, .thrust_magnitude = 48, .fight_back = 1}, - {.thrust_max = 2750, .thrust_magnitude = 49, .fight_back = 1}, - }, - [RACE_CLASS_RAPIER] = { - {.thrust_max = 3750, .thrust_magnitude = 50, .fight_back = 1}, - {.thrust_max = 3780, .thrust_magnitude = 53, .fight_back = 1}, - {.thrust_max = 3800, .thrust_magnitude = 55, .fight_back = 1}, - {.thrust_max = 3850, .thrust_magnitude = 57, .fight_back = 1}, - {.thrust_max = 3900, .thrust_magnitude = 60, .fight_back = 1}, - {.thrust_max = 3950, .thrust_magnitude = 62, .fight_back = 1}, - {.thrust_max = 4000, .thrust_magnitude = 65, .fight_back = 1}, - }, - }, - - .circuts = { - [CIRCUT_ALTIMA_VII] = { - .name = "ALTIMA VII", - .is_bonus_circut = false, - .settings = { - [RACE_CLASS_VENOM] = {.path = "wipeout/track02/", .start_line_pos = 27, .behind_speed = 300, .spread_base = 80, .spread_factor = 20, .sky_y_offset = -2520}, - [RACE_CLASS_RAPIER] = {.path = "wipeout/track03/", .start_line_pos = 27, .behind_speed = 500, .spread_base = 80, .spread_factor = 11, .sky_y_offset = -1930}, - } - }, - [CIRCUT_KARBONIS_V] = { - .name = "KARBONIS V", - .is_bonus_circut = false, - .settings = { - [RACE_CLASS_VENOM] = {.path = "wipeout/track04/", .start_line_pos = 16, .behind_speed = 200, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -5000}, - [RACE_CLASS_RAPIER] = {.path = "wipeout/track05/", .start_line_pos = 16, .behind_speed = 500, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -5000}, - } - }, - [CIRCUT_TERRAMAX] = { - .name = "TERRAMAX", - .is_bonus_circut = false, - .settings = { - [RACE_CLASS_VENOM] = {.path = "wipeout/track01/", .start_line_pos = 27, .behind_speed = 350, .spread_base = 60, .spread_factor = 11, .sky_y_offset = -820}, - [RACE_CLASS_RAPIER] = {.path = "wipeout/track06/", .start_line_pos = 27, .behind_speed = 500, .spread_base = 10, .spread_factor = 8, .sky_y_offset = 0}, - } - }, - [CIRCUT_KORODERA] = { - .name = "KORODERA", - .is_bonus_circut = false, - .settings = { - [RACE_CLASS_VENOM] = {.path = "wipeout/track12/", .start_line_pos = 16, .behind_speed = 450, .spread_base = 40, .spread_factor = 11, .sky_y_offset = -2120}, - [RACE_CLASS_RAPIER] = {.path = "wipeout/track07/", .start_line_pos = 16, .behind_speed = 500, .spread_base = 30, .spread_factor = 11, .sky_y_offset = -2260}, - } - }, - [CIRCUT_ARRIDOS_IV] = { - .name = "ARRIDOS IV", - .is_bonus_circut = false, - .settings = { - [RACE_CLASS_VENOM] = {.path = "wipeout/track08/", .start_line_pos = 16, .behind_speed = 350, .spread_base = 80, .spread_factor = 15, .sky_y_offset = -40}, - [RACE_CLASS_RAPIER] = {.path = "wipeout/track11/", .start_line_pos = 16, .behind_speed = 450, .spread_base = 30, .spread_factor = 11, .sky_y_offset = -240}, - } - }, - [CIRCUT_SILVERSTREAM] = { - .name = "SILVERSTREAM", - .is_bonus_circut = false, - .settings = { - [RACE_CLASS_VENOM] = {.path = "wipeout/track09/", .start_line_pos = 16, .behind_speed = 150, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -2700}, - [RACE_CLASS_RAPIER] = {.path = "wipeout/track13/", .start_line_pos = 16, .behind_speed = 150, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -2700}, - } - }, - [CIRCUT_FIRESTAR] = { - .name = "FIRESTAR", - .is_bonus_circut = true, - .settings = { - [RACE_CLASS_VENOM] = {.path = "wipeout/track10/", .start_line_pos = 27, .behind_speed = 200, .spread_base = 40, .spread_factor = 11, .sky_y_offset = 0}, - [RACE_CLASS_RAPIER] = {.path = "wipeout/track14/", .start_line_pos = 27, .behind_speed = 500, .spread_base = 40, .spread_factor = 11, .sky_y_offset = 0}, - } - }, - }, - .music = { - {.path = "wipeout/music/track01.qoa", .name = "CAIRODROME"}, - {.path = "wipeout/music/track02.qoa", .name = "CARDINAL DANCER"}, - {.path = "wipeout/music/track03.qoa", .name = "COLD COMFORT"}, - {.path = "wipeout/music/track04.qoa", .name = "DOH T"}, - {.path = "wipeout/music/track05.qoa", .name = "MESSIJ"}, - {.path = "wipeout/music/track06.qoa", .name = "OPERATIQUE"}, - {.path = "wipeout/music/track07.qoa", .name = "TENTATIVE"}, - {.path = "wipeout/music/track08.qoa", .name = "TRANCEVAAL"}, - {.path = "wipeout/music/track09.qoa", .name = "AFRO RIDE"}, - {.path = "wipeout/music/track10.qoa", .name = "CHEMICAL BEATS"}, - {.path = "wipeout/music/track11.qoa", .name = "WIPEOUT"}, - }, - .credits = { - "#MANAGING DIRECTORS", - "IAN HETHERINGTON", - "JONATHAN ELLIS", - "#DIRECTOR OF DEVELOPMENT", - "JOHN WHITE", - "#PRODUCERS", - "DOMINIC MALLINSON", - "ANDY YELLAND", - "#PRODUCT MANAGER", - "SUE CAMPBELL", - "#GAME DESIGNER", - "NICK BURCOMBE", - "", - "", - "#PLAYSTATION VERSION", - "#PROGRAMMERS", - "DAVE ROSE", - "ROB SMITH", - "JASON DENTON", - "STEWART SOCKETT", - "#ORIGINAL ARTISTS", - "NICKY CARUS WESTCOTT", - "LAURA GRIEVE", - "LOUISE SMITH", - "DARREN DOUGLAS", - "POL SIGERSON", - "#INTRO SEQUENCE", - "LEE CARUS WESTCOTT", - "#CONCEPTUAL ARTIST", - "JIM BOWERS", - "#ADDITIONAL GRAPHIC DESIGN", - "THE DESIGNERS REPUBLIC", - "#MUSIC", - "ORBITAL", - "CHEMICAL BROTHERS", - "LEFTFIELD", - "COLD STORAGE", - "#SOUND EFFECTS", - "TIM WRIGHT", - "#MANUAL WRITTEN BY", - "DAMON FAIRCLOUGH", - "NICK BURCOMBE", - "#PACKAGING DESIGN", - "THE DESIGNERS REPUBLIC", - "KEITH HOPWOOD", - "", - "", - "#PC VERSION", - "#PROGRAMMERS", - "ANDY YELLAND", - "ANDY SATTERTHWAITE", - "DAVE SMITH", - "MARK KELLY", - "JED ADAMS", - "STEVE WARD", - "CHRIS EDEN", - "SALIM SIWANI", - "#SOUND PROGRAMMING", - "ANDY CROWLEY", - "#MOVIE PROGRAMMING", - "MIKE ANTHONY", - "#CONVERSION ARTISTS", - "JOHN DWYER", - "GARY BURLEY", - "", - "", - "#ATI 3D RAGE VERSION", - "#PRODUCER", - "BILL ALLEN", - "#DEVELOPED BY", - "#BROADSWORD INTERACTIVE LTD", - "STEPHEN ROSE", - "JOHN JONES STEELE", - "", - "", - "#2023 REWRITE", - "PHOBOSLAB", - "DOMINIC SZABLEWSKI", - "", - "", - "#DEVELOPMENT SECRETARY", - "JENNIFER REES", - "", - "", - "#QUALITY ASSURANCE", - "STUART ALLEN", - "CHRIS GRAHAM", - "THOMAS REES", - "BRIAN WALSH", - "CARL BERRY", - "MARK INMAN", - "PAUL TWEEDLE", - "ANTHONY CROSS", - "EDWARD HAY", - "ROB WOLFE", - "", - "", - "#SPECIAL THANKS TO", - "THE HACKERS TEAM MGM", - "SOFTIMAGE", - "SGI", - "GLEN OCONNELL", - "JOANNE GALVIN", - "ALL AT PSYGNOSIS", - }, - .congratulations = { - .venom = { - "#WELL DONE", - "", - "VENOM CLASS", - "", - "COMPETENCE ACHIEVED", - "", - "YOU HAVE NOW QUALIFIED", - "", - "FOR THE ULTRA FAST", - "", - "RAPIER CLASS", - "", - "WE RECOMMEND YOU", - "", - "SAVE YOUR CURRENT GAME", - }, - .venom_all_circuts = { - "#AMAZING", - "", - "YOU HAVE COMPLETED THE FULL", - "", - "VENOM CLASS CHAMPIONSHIP", - "", - "", - "WELL DONE", - "", - "YOU ARE A GREAT PILOT", - "", - "", - "", - "NOW TAKE ON THE FULL", - "", - "RAPIER CLASS CHAMPIONSHIP", - "", - "", - "#KEEP GOING", - }, - .rapier = { - "#CONGRATULATIONS", - "", - "RAPIER CLASS", - "", - "COMPETENCE ACHIEVED", - "", - "YOU NOW HAVE ACCESS TO THE", - "", - "FULL VENOM AND RAPIER", - "", - "CHAMPIONSHIPS WITH THE ", - "", - "NEWLY CONSTRUCTED CIRCUIT", - "", - "FIRESTAR", - "", - "", - "", - "WE RECOMMEND YOU", - "", - "SAVE", - "", - "YOUR CURRENT GAME", - "", - "", - "#GOOD LUCK", - }, - .rapier_all_circuts = { - "#AWESOME", - "", - "YOU HAVE BEATEN", - "#WIPEOUT", - "", - "YOU ARE A TRULY", - "", - "AMAZING PILOT", - "", - "", - "", - "#CONGRATULATIONS", - "", - "", - "", - "", - "#A BIG THANKS", - "", - "FROM ALL OF US ON THE TEAM", - "", - "LOOK OUT FOR", - "#WIPEOUT II", - "", - "COMING SOON", - }, - } -}; - -save_t save = { - .magic = SAVE_DATA_MAGIC, - .is_dirty = true, - - .sfx_volume = 0.6, - .music_volume = 0.5, - .ui_scale = 0, - .show_fps = false, - .fullscreen = false, - .screen_res = 0, - .post_effect = 0, - - .has_rapier_class = true, // for testing; should be false in prod - .has_bonus_circuts = true, // for testing; should be false in prod - - .buttons = { - [A_UP] = {INPUT_KEY_UP, INPUT_GAMEPAD_DPAD_UP}, - [A_DOWN] = {INPUT_KEY_DOWN, INPUT_GAMEPAD_DPAD_DOWN}, - [A_LEFT] = {INPUT_KEY_LEFT, INPUT_GAMEPAD_DPAD_LEFT}, - [A_RIGHT] = {INPUT_KEY_RIGHT, INPUT_GAMEPAD_DPAD_RIGHT}, - [A_BRAKE_LEFT] = {INPUT_KEY_C, INPUT_GAMEPAD_L_SHOULDER}, - [A_BRAKE_RIGHT] = {INPUT_KEY_V, INPUT_GAMEPAD_R_SHOULDER}, - [A_THRUST] = {INPUT_KEY_X, INPUT_GAMEPAD_A}, - [A_FIRE] = {INPUT_KEY_Z, INPUT_GAMEPAD_X}, - [A_CHANGE_VIEW] = {INPUT_KEY_A, INPUT_GAMEPAD_Y}, - }, - - .highscores_name = {0,0,0,0}, - .highscores = { - [RACE_CLASS_VENOM] = { - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 85.83, .entries = {{"WIP", 254.50},{"EOU", 271.17},{"TPC", 289.50},{"NOT", 294.50},{"PSX", 314.50}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 85.83, .entries = {{"MVE", 254.50},{"ALM", 271.17},{"POL", 289.50},{"NIK", 294.50},{"DAR", 314.50}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 55.33, .entries = {{"AJY", 159.33},{"AJS", 172.67},{"DLS", 191.00},{"MAK", 207.67},{"JED", 219.33}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 55.33, .entries = {{"DAR", 159.33},{"STU", 172.67},{"MOC", 191.00},{"DOM", 207.67},{"NIK", 219.33}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 57.5, .entries = {{ "JD", 171.00},{"AJC", 189.33},{"MSA", 202.67},{ "SD", 219.33},{"TIM", 232.67}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 57.5, .entries = {{"PHO", 171.00},{"ENI", 189.33},{ "XR", 202.67},{"ISI", 219.33},{ "NG", 232.67}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 85.17, .entries = {{"POL", 251.33},{"DAR", 263.00},{"JAS", 283.00},{"ROB", 294.67},{"DJR", 314.82}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 85.17, .entries = {{"DOM", 251.33},{"DJR", 263.00},{"MPI", 283.00},{"GOC", 294.67},{"SUE", 314.82}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 80.17, .entries = {{"NIK", 236.17},{"SAL", 253.17},{"DOM", 262.33},{ "LG", 282.67},{"LNK", 298.17}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 80.17, .entries = {{"NIK", 236.17},{"ROB", 253.17},{ "AM", 262.33},{"JAS", 282.67},{"DAR", 298.17}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 61.67, .entries = {{"HAN", 182.33},{"PER", 196.33},{"FEC", 214.83},{"TPI", 228.83},{"ZZA", 244.33}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 61.67, .entries = {{ "FC", 182.33},{"SUE", 196.33},{"ROB", 214.83},{"JEN", 228.83},{ "NT", 244.33}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 63.83, .entries = {{"CAN", 195.40},{"WEH", 209.23},{"AVE", 227.90},{"ABO", 239.90},{"NUS", 240.73}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 63.83, .entries = {{"DJR", 195.40},{"NIK", 209.23},{"JAS", 227.90},{"NCW", 239.90},{"LOU", 240.73}}}, - }, - }, - [RACE_CLASS_RAPIER] = { - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 69.50, .entries = {{"AJY", 200.67},{"DLS", 213.50},{"AJS", 228.67},{"MAK", 247.67},{"JED", 263.00}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 69.50, .entries = {{"NCW", 200.67},{"LEE", 213.50},{"STU", 228.67},{"JAS", 247.67},{"ROB", 263.00}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 47.33, .entries = {{"BOR", 134.58},{"ING", 147.00},{"HIS", 162.25},{"COR", 183.08},{ "ES", 198.25}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 47.33, .entries = {{"NIK", 134.58},{"POL", 147.00},{"DAR", 162.25},{"STU", 183.08},{"ROB", 198.25}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 47.83, .entries = {{"AJS", 142.08},{"DLS", 159.42},{"MAK", 178.08},{"JED", 190.25},{"AJY", 206.58}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 47.83, .entries = {{"POL", 142.08},{"JIM", 159.42},{"TIM", 178.08},{"MOC", 190.25},{ "PC", 206.58}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 76.75, .entries = {{"DLS", 224.17},{"DJR", 237.00},{"LEE", 257.50},{"MOC", 272.83},{"MPI", 285.17}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 76.75, .entries = {{"TIM", 224.17},{"JIM", 237.00},{"NIK", 257.50},{"JAS", 272.83},{ "LG", 285.17}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 65.75, .entries = {{"MAK", 191.00},{"STU", 203.67},{"JAS", 221.83},{"ROB", 239.00},{"DOM", 254.50}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 65.75, .entries = {{ "LG", 191.00},{"LOU", 203.67},{"JIM", 221.83},{"HAN", 239.00},{ "NT", 254.50}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 59.23, .entries = {{"JED", 156.67},{"NCW", 170.33},{"LOU", 188.83},{"DAR", 201.00},{"POL", 221.50}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 59.23, .entries = {{"STU", 156.67},{"DAV", 170.33},{"DOM", 188.83},{"MOR", 201.00},{"GAN", 221.50}}}, - }, - { - [HIGHSCORE_TAB_RACE] = {.lap_record = 55.00, .entries = {{ "PC", 162.42},{"POL", 179.58},{"DAR", 194.75},{"DAR", 208.92},{"MSC", 224.58}}}, - [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 55.00, .entries = {{"THA", 162.42},{"NKS", 179.58},{"FOR", 194.75},{"PLA", 208.92},{"YIN", 224.58}}}, - } - } - } -}; - -game_t g = {0}; - - - -struct { - void (*init)(void); - void (*update)(void); -} game_scenes[] = { - [GAME_SCENE_INTRO] = {intro_init, intro_update}, - [GAME_SCENE_TITLE] = {title_init, title_update}, - [GAME_SCENE_MAIN_MENU] = {main_menu_init, main_menu_update}, - [GAME_SCENE_RACE] = {race_init, race_update}, -}; - -static game_scene_t scene_current = GAME_SCENE_NONE; -static game_scene_t scene_next = GAME_SCENE_NONE; -static int global_textures_len = 0; -static void *global_mem_mark = 0; - -void game_init(void) { - uint32_t size; - save_t *save_file = (save_t *)platform_load_userdata("save.dat", &size); - if (save_file) { - if (size == sizeof(save_t) && save_file->magic == SAVE_DATA_MAGIC) { - printf("load save data success\n"); - memcpy(&save, save_file, sizeof(save_t)); - } - else { - printf("unexpected size/magic for save data"); - } - mem_temp_free(save_file); - } - - platform_set_fullscreen(save.fullscreen); - render_set_resolution(save.screen_res); - render_set_post_effect(save.post_effect); - - srand((int)(platform_now() * 100)); - - ui_load(); - sfx_load(); - hud_load(); - ships_load(); - droid_load(); - particles_load(); - weapons_load(); - - global_textures_len = render_textures_len(); - global_mem_mark = mem_mark(); - - sfx_music_mode(SFX_MUSIC_PAUSED); - sfx_music_play(rand_int(0, len(def.music))); - - - // System binds; always fixed - // Keyboard - input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_UP, A_MENU_UP); - input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_DOWN, A_MENU_DOWN); - input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_LEFT, A_MENU_LEFT); - input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_RIGHT, A_MENU_RIGHT); - - input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_BACKSPACE, A_MENU_BACK); - input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_C, A_MENU_BACK); - input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_V, A_MENU_BACK); - - input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_X, A_MENU_SELECT); - input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_RETURN, A_MENU_START); - input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_ESCAPE, A_MENU_QUIT); - - // Gamepad - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_UP, A_MENU_UP); - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_DOWN, A_MENU_DOWN); - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_LEFT, A_MENU_LEFT); - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_RIGHT, A_MENU_RIGHT); - - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_UP, A_MENU_UP); - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_DOWN, A_MENU_DOWN); - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_LEFT, A_MENU_LEFT); - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_RIGHT, A_MENU_RIGHT); - - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_X, A_MENU_BACK); - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_B, A_MENU_BACK); - - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_A, A_MENU_SELECT); - input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_START, A_MENU_START); - - - // User defined, loaded from the save struct - for (int action = 0; action < len(save.buttons); action++) { - if (save.buttons[action][0] != INPUT_INVALID) { - input_bind(INPUT_LAYER_USER, save.buttons[action][0], action); - } - if (save.buttons[action][1] != INPUT_INVALID) { - input_bind(INPUT_LAYER_USER, save.buttons[action][1], action); - } - } - - - game_set_scene(GAME_SCENE_INTRO); -} - -void game_set_scene(game_scene_t scene) { - sfx_reset(); - scene_next = scene; -} - -void game_reset_championship(void) { - for (int i = 0; i < len(g.championship_ranks); i++) { - g.championship_ranks[i].points = 0; - g.championship_ranks[i].pilot = i; - } - g.lives = NUM_LIVES; -} - -void game_update(void) { - double frame_start_time = platform_now(); - - int sh = render_size().y; - int scale = max(1, sh >= 720 ? sh / 360 : sh / 240); - if (save.ui_scale && save.ui_scale < scale) { - scale = save.ui_scale; - } - ui_set_scale(scale); - - - if (scene_next != GAME_SCENE_NONE) { - scene_current = scene_next; - scene_next = GAME_SCENE_NONE; - render_textures_reset(global_textures_len); - mem_reset(global_mem_mark); - system_reset_cycle_time(); - - if (scene_current != GAME_SCENE_NONE) { - game_scenes[scene_current].init(); - } - } - - if (scene_current != GAME_SCENE_NONE) { - game_scenes[scene_current].update(); - } - - // Fullscreen might have been toggled through alt+enter - bool fullscreen = platform_get_fullscreen(); - if (fullscreen != save.fullscreen) { - save.fullscreen = fullscreen; - save.is_dirty = true; - } - - if (save.is_dirty) { - // FIXME: use a text based format? - // FIXME: this should probably run async somewhere - save.is_dirty = false; - platform_store_userdata("save.dat", &save, sizeof(save_t)); - printf("wrote save.dat\n"); - } - - double now = platform_now(); - g.frame_time = now - frame_start_time; - if (g.frame_time > 0) { - g.frame_rate = ((double)g.frame_rate * 0.95) + (1.0/g.frame_time) * 0.05; - } -} - +#include + +#include "../mem.h" +#include "../utils.h" +#include "../system.h" +#include "../platform.h" +#include "../input.h" + +#include "game.h" +#include "ship.h" +#include "weapon.h" +#include "droid.h" +#include "object.h" +#include "hud.h" +#include "game.h" +#include "sfx.h" +#include "ui.h" +#include "particle.h" +#include "race.h" +#include "main_menu.h" +#include "title.h" +#include "intro.h" + +#define TURN_ACCEL(V) NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(YAW_VELOCITY(V)))) +#define TURN_VEL(V) NTSC_VELOCITY(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(YAW_VELOCITY(V)))) + +const game_def_t def = { + .race_classes = { + [RACE_CLASS_VENOM] = {.name = "VENOM CLASS"}, + [RACE_CLASS_RAPIER] = {.name = "RAPIER CLASS"}, + }, + + .race_types = { + [RACE_TYPE_CHAMPIONSHIP] = {.name = "CHAMPIONSHIP RACE"}, + [RACE_TYPE_SINGLE] = {.name = "SINGLE RACE"}, + [RACE_TYPE_TIME_TRIAL] = {.name = "TIME TRIAL"}, + }, + + .pilots = { + [PILOT_JOHN_DEKKA] = {.name = "JOHN DEKKA", .portrait = "wipeout/textures/dekka.cmp", .team = 0, .logo_model = 0}, + [PILOT_DANIEL_CHANG] = {.name = "DANIEL CHANG", .portrait = "wipeout/textures/chang.cmp", .team = 0, .logo_model = 4}, + [PILOT_ARIAL_TETSUO] = {.name = "ARIAL TETSUO", .portrait = "wipeout/textures/arial.cmp", .team = 1, .logo_model = 6}, + [PILOT_ANASTASIA_CHEROVOSKI] = {.name = "ANASTASIA CHEROVOSKI", .portrait = "wipeout/textures/anast.cmp", .team = 1, .logo_model = 7}, + [PILOT_KEL_SOLAAR] = {.name = "KEL SOLAAR", .portrait = "wipeout/textures/solar.cmp", .team = 2, .logo_model = 2}, + [PILOT_ARIAN_TETSUO] = {.name = "ARIAN TETSUO", .portrait = "wipeout/textures/arian.cmp", .team = 2, .logo_model = 5}, + [PILOT_SOFIA_DE_LA_RENTE] = {.name = "SOFIA DE LA RENTE", .portrait = "wipeout/textures/sophi.cmp", .team = 3, .logo_model = 1}, + [PILOT_PAUL_JACKSON] = {.name = "PAUL JACKSON", .portrait = "wipeout/textures/paul.cmp", .team = 3, .logo_model = 3}, + }, + + .ship_model_to_pilot = {6, 4, 7, 1, 5, 2, 3, 0}, + .race_points_for_rank = {9, 7, 5, 3, 2, 1, 0, 0}, + + // SHIP ATTRIBUTES + // TEAM 1 TEAM 2 TEAM 3 TEAM 4 + // Acceleration: *** ***** ** **** + // Top Speed: **** ** **** *** + // Armour: ***** *** **** ** + // Turn Rate: ** **** *** ***** + + .teams = { + [TEAM_AG_SYSTEMS] = { + .name = "AG SYSTEMS", + .logo_model = 2, + .pilots = {0, 1}, + .attributes = { + [RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 790, .resistance = 140, .turn_rate = TURN_ACCEL(160), .turn_rate_max = TURN_VEL(2560), .skid = 12}, + [RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1200, .resistance = 140, .turn_rate = TURN_ACCEL(160), .turn_rate_max = TURN_VEL(2560), .skid = 10}, + }, + }, + [TEAM_AURICOM] = { + .name = "AURICOM", + .logo_model = 3, + .pilots = {2, 3}, + .attributes = { + [RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 850, .resistance = 134, .turn_rate = TURN_ACCEL(140), .turn_rate_max = TURN_VEL(1920), .skid = 20}, + [RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1400, .resistance = 140, .turn_rate = TURN_ACCEL(120), .turn_rate_max = TURN_VEL(1920), .skid = 14}, + }, + }, + [TEAM_QIREX] = { + .name = "QIREX", + .logo_model = 1, + .pilots = {4, 5}, + .attributes = { + [RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 850, .resistance = 140, .turn_rate = TURN_ACCEL(120), .turn_rate_max = TURN_VEL(1920), .skid = 24}, + [RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1400, .resistance = 130, .turn_rate = TURN_ACCEL(140), .turn_rate_max = TURN_VEL(1920), .skid = 16}, + }, + }, + [TEAM_FEISAR] = { + .name = "FEISAR", + .logo_model = 0, + .pilots = {6, 7}, + .attributes = { + [RACE_CLASS_VENOM] = {.mass = 150, .thrust_max = 790, .resistance = 134, .turn_rate = TURN_ACCEL(180), .turn_rate_max = TURN_VEL(2560), .skid = 12}, + [RACE_CLASS_RAPIER] = {.mass = 150, .thrust_max = 1200, .resistance = 130, .turn_rate = TURN_ACCEL(180), .turn_rate_max = TURN_VEL(2560), .skid = 8}, + }, + }, + }, + + .ai_settings = { + [RACE_CLASS_VENOM] = { + {.thrust_max = 2550, .thrust_magnitude = 44, .fight_back = 1}, + {.thrust_max = 2600, .thrust_magnitude = 45, .fight_back = 1}, + {.thrust_max = 2630, .thrust_magnitude = 45, .fight_back = 1}, + {.thrust_max = 2660, .thrust_magnitude = 46, .fight_back = 1}, + {.thrust_max = 2700, .thrust_magnitude = 47, .fight_back = 1}, + {.thrust_max = 2720, .thrust_magnitude = 48, .fight_back = 1}, + {.thrust_max = 2750, .thrust_magnitude = 49, .fight_back = 1}, + }, + [RACE_CLASS_RAPIER] = { + {.thrust_max = 3750, .thrust_magnitude = 50, .fight_back = 1}, + {.thrust_max = 3780, .thrust_magnitude = 53, .fight_back = 1}, + {.thrust_max = 3800, .thrust_magnitude = 55, .fight_back = 1}, + {.thrust_max = 3850, .thrust_magnitude = 57, .fight_back = 1}, + {.thrust_max = 3900, .thrust_magnitude = 60, .fight_back = 1}, + {.thrust_max = 3950, .thrust_magnitude = 62, .fight_back = 1}, + {.thrust_max = 4000, .thrust_magnitude = 65, .fight_back = 1}, + }, + }, + + .circuts = { + [CIRCUT_ALTIMA_VII] = { + .name = "ALTIMA VII", + .is_bonus_circut = false, + .settings = { + [RACE_CLASS_VENOM] = {.path = "wipeout/track02/", .start_line_pos = 27, .behind_speed = 300, .spread_base = 80, .spread_factor = 20, .sky_y_offset = -2520}, + [RACE_CLASS_RAPIER] = {.path = "wipeout/track03/", .start_line_pos = 27, .behind_speed = 500, .spread_base = 80, .spread_factor = 11, .sky_y_offset = -1930}, + } + }, + [CIRCUT_KARBONIS_V] = { + .name = "KARBONIS V", + .is_bonus_circut = false, + .settings = { + [RACE_CLASS_VENOM] = {.path = "wipeout/track04/", .start_line_pos = 16, .behind_speed = 200, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -5000}, + [RACE_CLASS_RAPIER] = {.path = "wipeout/track05/", .start_line_pos = 16, .behind_speed = 500, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -5000}, + } + }, + [CIRCUT_TERRAMAX] = { + .name = "TERRAMAX", + .is_bonus_circut = false, + .settings = { + [RACE_CLASS_VENOM] = {.path = "wipeout/track01/", .start_line_pos = 27, .behind_speed = 350, .spread_base = 60, .spread_factor = 11, .sky_y_offset = -820}, + [RACE_CLASS_RAPIER] = {.path = "wipeout/track06/", .start_line_pos = 27, .behind_speed = 500, .spread_base = 10, .spread_factor = 8, .sky_y_offset = 0}, + } + }, + [CIRCUT_KORODERA] = { + .name = "KORODERA", + .is_bonus_circut = false, + .settings = { + [RACE_CLASS_VENOM] = {.path = "wipeout/track12/", .start_line_pos = 16, .behind_speed = 450, .spread_base = 40, .spread_factor = 11, .sky_y_offset = -2120}, + [RACE_CLASS_RAPIER] = {.path = "wipeout/track07/", .start_line_pos = 16, .behind_speed = 500, .spread_base = 30, .spread_factor = 11, .sky_y_offset = -2260}, + } + }, + [CIRCUT_ARRIDOS_IV] = { + .name = "ARRIDOS IV", + .is_bonus_circut = false, + .settings = { + [RACE_CLASS_VENOM] = {.path = "wipeout/track08/", .start_line_pos = 16, .behind_speed = 350, .spread_base = 80, .spread_factor = 15, .sky_y_offset = -40}, + [RACE_CLASS_RAPIER] = {.path = "wipeout/track11/", .start_line_pos = 16, .behind_speed = 450, .spread_base = 30, .spread_factor = 11, .sky_y_offset = -240}, + } + }, + [CIRCUT_SILVERSTREAM] = { + .name = "SILVERSTREAM", + .is_bonus_circut = false, + .settings = { + [RACE_CLASS_VENOM] = {.path = "wipeout/track09/", .start_line_pos = 16, .behind_speed = 150, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -2700}, + [RACE_CLASS_RAPIER] = {.path = "wipeout/track13/", .start_line_pos = 16, .behind_speed = 150, .spread_base = 10, .spread_factor = 8, .sky_y_offset = -2700}, + } + }, + [CIRCUT_FIRESTAR] = { + .name = "FIRESTAR", + .is_bonus_circut = true, + .settings = { + [RACE_CLASS_VENOM] = {.path = "wipeout/track10/", .start_line_pos = 27, .behind_speed = 200, .spread_base = 40, .spread_factor = 11, .sky_y_offset = 0}, + [RACE_CLASS_RAPIER] = {.path = "wipeout/track14/", .start_line_pos = 27, .behind_speed = 500, .spread_base = 40, .spread_factor = 11, .sky_y_offset = 0}, + } + }, + }, + .music = { + {.path = "wipeout/music/track01.qoa", .name = "CAIRODROME"}, + {.path = "wipeout/music/track02.qoa", .name = "CARDINAL DANCER"}, + {.path = "wipeout/music/track03.qoa", .name = "COLD COMFORT"}, + {.path = "wipeout/music/track04.qoa", .name = "DOH T"}, + {.path = "wipeout/music/track05.qoa", .name = "MESSIJ"}, + {.path = "wipeout/music/track06.qoa", .name = "OPERATIQUE"}, + {.path = "wipeout/music/track07.qoa", .name = "TENTATIVE"}, + {.path = "wipeout/music/track08.qoa", .name = "TRANCEVAAL"}, + {.path = "wipeout/music/track09.qoa", .name = "AFRO RIDE"}, + {.path = "wipeout/music/track10.qoa", .name = "CHEMICAL BEATS"}, + {.path = "wipeout/music/track11.qoa", .name = "WIPEOUT"}, + }, + .credits = { + "#MANAGING DIRECTORS", + "IAN HETHERINGTON", + "JONATHAN ELLIS", + "#DIRECTOR OF DEVELOPMENT", + "JOHN WHITE", + "#PRODUCERS", + "DOMINIC MALLINSON", + "ANDY YELLAND", + "#PRODUCT MANAGER", + "SUE CAMPBELL", + "#GAME DESIGNER", + "NICK BURCOMBE", + "", + "", + "#PLAYSTATION VERSION", + "#PROGRAMMERS", + "DAVE ROSE", + "ROB SMITH", + "JASON DENTON", + "STEWART SOCKETT", + "#ORIGINAL ARTISTS", + "NICKY CARUS WESTCOTT", + "LAURA GRIEVE", + "LOUISE SMITH", + "DARREN DOUGLAS", + "POL SIGERSON", + "#INTRO SEQUENCE", + "LEE CARUS WESTCOTT", + "#CONCEPTUAL ARTIST", + "JIM BOWERS", + "#ADDITIONAL GRAPHIC DESIGN", + "THE DESIGNERS REPUBLIC", + "#MUSIC", + "ORBITAL", + "CHEMICAL BROTHERS", + "LEFTFIELD", + "COLD STORAGE", + "#SOUND EFFECTS", + "TIM WRIGHT", + "#MANUAL WRITTEN BY", + "DAMON FAIRCLOUGH", + "NICK BURCOMBE", + "#PACKAGING DESIGN", + "THE DESIGNERS REPUBLIC", + "KEITH HOPWOOD", + "", + "", + "#PC VERSION", + "#PROGRAMMERS", + "ANDY YELLAND", + "ANDY SATTERTHWAITE", + "DAVE SMITH", + "MARK KELLY", + "JED ADAMS", + "STEVE WARD", + "CHRIS EDEN", + "SALIM SIWANI", + "#SOUND PROGRAMMING", + "ANDY CROWLEY", + "#MOVIE PROGRAMMING", + "MIKE ANTHONY", + "#CONVERSION ARTISTS", + "JOHN DWYER", + "GARY BURLEY", + "", + "", + "#ATI 3D RAGE VERSION", + "#PRODUCER", + "BILL ALLEN", + "#DEVELOPED BY", + "#BROADSWORD INTERACTIVE LTD", + "STEPHEN ROSE", + "JOHN JONES STEELE", + "", + "", + "#2023 REWRITE", + "PHOBOSLAB", + "DOMINIC SZABLEWSKI", + "", + "", + "#DEVELOPMENT SECRETARY", + "JENNIFER REES", + "", + "", + "#QUALITY ASSURANCE", + "STUART ALLEN", + "CHRIS GRAHAM", + "THOMAS REES", + "BRIAN WALSH", + "CARL BERRY", + "MARK INMAN", + "PAUL TWEEDLE", + "ANTHONY CROSS", + "EDWARD HAY", + "ROB WOLFE", + "", + "", + "#SPECIAL THANKS TO", + "THE HACKERS TEAM MGM", + "SOFTIMAGE", + "SGI", + "GLEN OCONNELL", + "JOANNE GALVIN", + "ALL AT PSYGNOSIS", + }, + .congratulations = { + .venom = { + "#WELL DONE", + "", + "VENOM CLASS", + "", + "COMPETENCE ACHIEVED", + "", + "YOU HAVE NOW QUALIFIED", + "", + "FOR THE ULTRA FAST", + "", + "RAPIER CLASS", + "", + "WE RECOMMEND YOU", + "", + "SAVE YOUR CURRENT GAME", + }, + .venom_all_circuts = { + "#AMAZING", + "", + "YOU HAVE COMPLETED THE FULL", + "", + "VENOM CLASS CHAMPIONSHIP", + "", + "", + "WELL DONE", + "", + "YOU ARE A GREAT PILOT", + "", + "", + "", + "NOW TAKE ON THE FULL", + "", + "RAPIER CLASS CHAMPIONSHIP", + "", + "", + "#KEEP GOING", + }, + .rapier = { + "#CONGRATULATIONS", + "", + "RAPIER CLASS", + "", + "COMPETENCE ACHIEVED", + "", + "YOU NOW HAVE ACCESS TO THE", + "", + "FULL VENOM AND RAPIER", + "", + "CHAMPIONSHIPS WITH THE ", + "", + "NEWLY CONSTRUCTED CIRCUIT", + "", + "FIRESTAR", + "", + "", + "", + "WE RECOMMEND YOU", + "", + "SAVE", + "", + "YOUR CURRENT GAME", + "", + "", + "#GOOD LUCK", + }, + .rapier_all_circuts = { + "#AWESOME", + "", + "YOU HAVE BEATEN", + "#WIPEOUT", + "", + "YOU ARE A TRULY", + "", + "AMAZING PILOT", + "", + "", + "", + "#CONGRATULATIONS", + "", + "", + "", + "", + "#A BIG THANKS", + "", + "FROM ALL OF US ON THE TEAM", + "", + "LOOK OUT FOR", + "#WIPEOUT II", + "", + "COMING SOON", + }, + } +}; + +save_t save = { + .magic = SAVE_DATA_MAGIC, + .is_dirty = true, + + .sfx_volume = 0.6, + .music_volume = 0.5, + .ui_scale = 0, + .show_fps = false, + .fullscreen = false, + .screen_res = 0, + .post_effect = 0, + + .has_rapier_class = true, // for testing; should be false in prod + .has_bonus_circuts = true, // for testing; should be false in prod + + .buttons = { + [A_UP] = {INPUT_KEY_UP, INPUT_GAMEPAD_DPAD_UP}, + [A_DOWN] = {INPUT_KEY_DOWN, INPUT_GAMEPAD_DPAD_DOWN}, + [A_LEFT] = {INPUT_KEY_LEFT, INPUT_GAMEPAD_DPAD_LEFT}, + [A_RIGHT] = {INPUT_KEY_RIGHT, INPUT_GAMEPAD_DPAD_RIGHT}, + [A_BRAKE_LEFT] = {INPUT_KEY_C, INPUT_GAMEPAD_L_SHOULDER}, + [A_BRAKE_RIGHT] = {INPUT_KEY_V, INPUT_GAMEPAD_R_SHOULDER}, + [A_THRUST] = {INPUT_KEY_X, INPUT_GAMEPAD_A}, + [A_FIRE] = {INPUT_KEY_Z, INPUT_GAMEPAD_X}, + [A_CHANGE_VIEW] = {INPUT_KEY_A, INPUT_GAMEPAD_Y}, + }, + + .highscores_name = {0,0,0,0}, + .highscores = { + [RACE_CLASS_VENOM] = { + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 85.83, .entries = {{"WIP", 254.50},{"EOU", 271.17},{"TPC", 289.50},{"NOT", 294.50},{"PSX", 314.50}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 85.83, .entries = {{"MVE", 254.50},{"ALM", 271.17},{"POL", 289.50},{"NIK", 294.50},{"DAR", 314.50}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 55.33, .entries = {{"AJY", 159.33},{"AJS", 172.67},{"DLS", 191.00},{"MAK", 207.67},{"JED", 219.33}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 55.33, .entries = {{"DAR", 159.33},{"STU", 172.67},{"MOC", 191.00},{"DOM", 207.67},{"NIK", 219.33}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 57.5, .entries = {{ "JD", 171.00},{"AJC", 189.33},{"MSA", 202.67},{ "SD", 219.33},{"TIM", 232.67}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 57.5, .entries = {{"PHO", 171.00},{"ENI", 189.33},{ "XR", 202.67},{"ISI", 219.33},{ "NG", 232.67}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 85.17, .entries = {{"POL", 251.33},{"DAR", 263.00},{"JAS", 283.00},{"ROB", 294.67},{"DJR", 314.82}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 85.17, .entries = {{"DOM", 251.33},{"DJR", 263.00},{"MPI", 283.00},{"GOC", 294.67},{"SUE", 314.82}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 80.17, .entries = {{"NIK", 236.17},{"SAL", 253.17},{"DOM", 262.33},{ "LG", 282.67},{"LNK", 298.17}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 80.17, .entries = {{"NIK", 236.17},{"ROB", 253.17},{ "AM", 262.33},{"JAS", 282.67},{"DAR", 298.17}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 61.67, .entries = {{"HAN", 182.33},{"PER", 196.33},{"FEC", 214.83},{"TPI", 228.83},{"ZZA", 244.33}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 61.67, .entries = {{ "FC", 182.33},{"SUE", 196.33},{"ROB", 214.83},{"JEN", 228.83},{ "NT", 244.33}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 63.83, .entries = {{"CAN", 195.40},{"WEH", 209.23},{"AVE", 227.90},{"ABO", 239.90},{"NUS", 240.73}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 63.83, .entries = {{"DJR", 195.40},{"NIK", 209.23},{"JAS", 227.90},{"NCW", 239.90},{"LOU", 240.73}}}, + }, + }, + [RACE_CLASS_RAPIER] = { + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 69.50, .entries = {{"AJY", 200.67},{"DLS", 213.50},{"AJS", 228.67},{"MAK", 247.67},{"JED", 263.00}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 69.50, .entries = {{"NCW", 200.67},{"LEE", 213.50},{"STU", 228.67},{"JAS", 247.67},{"ROB", 263.00}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 47.33, .entries = {{"BOR", 134.58},{"ING", 147.00},{"HIS", 162.25},{"COR", 183.08},{ "ES", 198.25}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 47.33, .entries = {{"NIK", 134.58},{"POL", 147.00},{"DAR", 162.25},{"STU", 183.08},{"ROB", 198.25}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 47.83, .entries = {{"AJS", 142.08},{"DLS", 159.42},{"MAK", 178.08},{"JED", 190.25},{"AJY", 206.58}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 47.83, .entries = {{"POL", 142.08},{"JIM", 159.42},{"TIM", 178.08},{"MOC", 190.25},{ "PC", 206.58}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 76.75, .entries = {{"DLS", 224.17},{"DJR", 237.00},{"LEE", 257.50},{"MOC", 272.83},{"MPI", 285.17}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 76.75, .entries = {{"TIM", 224.17},{"JIM", 237.00},{"NIK", 257.50},{"JAS", 272.83},{ "LG", 285.17}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 65.75, .entries = {{"MAK", 191.00},{"STU", 203.67},{"JAS", 221.83},{"ROB", 239.00},{"DOM", 254.50}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 65.75, .entries = {{ "LG", 191.00},{"LOU", 203.67},{"JIM", 221.83},{"HAN", 239.00},{ "NT", 254.50}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 59.23, .entries = {{"JED", 156.67},{"NCW", 170.33},{"LOU", 188.83},{"DAR", 201.00},{"POL", 221.50}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 59.23, .entries = {{"STU", 156.67},{"DAV", 170.33},{"DOM", 188.83},{"MOR", 201.00},{"GAN", 221.50}}}, + }, + { + [HIGHSCORE_TAB_RACE] = {.lap_record = 55.00, .entries = {{ "PC", 162.42},{"POL", 179.58},{"DAR", 194.75},{"DAR", 208.92},{"MSC", 224.58}}}, + [HIGHSCORE_TAB_TIME_TRIAL] = {.lap_record = 55.00, .entries = {{"THA", 162.42},{"NKS", 179.58},{"FOR", 194.75},{"PLA", 208.92},{"YIN", 224.58}}}, + } + } + } +}; + +game_t g = {0}; + + + +struct { + void (*init)(void); + void (*update)(void); +} game_scenes[] = { + [GAME_SCENE_INTRO] = {intro_init, intro_update}, + [GAME_SCENE_TITLE] = {title_init, title_update}, + [GAME_SCENE_MAIN_MENU] = {main_menu_init, main_menu_update}, + [GAME_SCENE_RACE] = {race_init, race_update}, +}; + +static game_scene_t scene_current = GAME_SCENE_NONE; +static game_scene_t scene_next = GAME_SCENE_NONE; +static int global_textures_len = 0; +static void *global_mem_mark = 0; + +void game_init(void) { + uint32_t size; + save_t *save_file = (save_t *)platform_load_userdata("save.dat", &size); + if (save_file) { + if (size == sizeof(save_t) && save_file->magic == SAVE_DATA_MAGIC) { + printf("load save data success\n"); + memcpy(&save, save_file, sizeof(save_t)); + } + else { + printf("unexpected size/magic for save data"); + } + mem_temp_free(save_file); + } + + platform_set_fullscreen(save.fullscreen); + render_set_resolution(save.screen_res); + render_set_post_effect(save.post_effect); + + srand((int)(platform_now() * 100)); + + ui_load(); + sfx_load(); + hud_load(); + ships_load(); + droid_load(); + particles_load(); + weapons_load(); + + global_textures_len = render_textures_len(); + global_mem_mark = mem_mark(); + + sfx_music_mode(SFX_MUSIC_PAUSED); + sfx_music_play(rand_int(0, len(def.music))); + + + // System binds; always fixed + // Keyboard + input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_UP, A_MENU_UP); + input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_DOWN, A_MENU_DOWN); + input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_LEFT, A_MENU_LEFT); + input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_RIGHT, A_MENU_RIGHT); + + input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_BACKSPACE, A_MENU_BACK); + input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_C, A_MENU_BACK); + input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_V, A_MENU_BACK); + + input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_X, A_MENU_SELECT); + input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_RETURN, A_MENU_START); + input_bind(INPUT_LAYER_SYSTEM, INPUT_KEY_ESCAPE, A_MENU_QUIT); + + // Gamepad + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_UP, A_MENU_UP); + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_DOWN, A_MENU_DOWN); + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_LEFT, A_MENU_LEFT); + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_DPAD_RIGHT, A_MENU_RIGHT); + + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_UP, A_MENU_UP); + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_DOWN, A_MENU_DOWN); + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_LEFT, A_MENU_LEFT); + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_L_STICK_RIGHT, A_MENU_RIGHT); + + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_X, A_MENU_BACK); + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_B, A_MENU_BACK); + + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_A, A_MENU_SELECT); + input_bind(INPUT_LAYER_SYSTEM, INPUT_GAMEPAD_START, A_MENU_START); + + + // User defined, loaded from the save struct + for (int action = 0; action < len(save.buttons); action++) { + if (save.buttons[action][0] != INPUT_INVALID) { + input_bind(INPUT_LAYER_USER, save.buttons[action][0], action); + } + if (save.buttons[action][1] != INPUT_INVALID) { + input_bind(INPUT_LAYER_USER, save.buttons[action][1], action); + } + } + + + game_set_scene(GAME_SCENE_INTRO); +} + +void game_set_scene(game_scene_t scene) { + sfx_reset(); + scene_next = scene; +} + +void game_reset_championship(void) { + for (int i = 0; i < len(g.championship_ranks); i++) { + g.championship_ranks[i].points = 0; + g.championship_ranks[i].pilot = i; + } + g.lives = NUM_LIVES; +} + +void game_update(void) { + double frame_start_time = platform_now(); + + int sh = render_size().y; + int scale = max(1, sh >= 720 ? sh / 360 : sh / 240); + if (save.ui_scale && save.ui_scale < scale) { + scale = save.ui_scale; + } + ui_set_scale(scale); + + + if (scene_next != GAME_SCENE_NONE) { + scene_current = scene_next; + scene_next = GAME_SCENE_NONE; + render_textures_reset(global_textures_len); + mem_reset(global_mem_mark); + system_reset_cycle_time(); + + if (scene_current != GAME_SCENE_NONE) { + game_scenes[scene_current].init(); + } + } + + if (scene_current != GAME_SCENE_NONE) { + game_scenes[scene_current].update(); + } + + // Fullscreen might have been toggled through alt+enter + bool fullscreen = platform_get_fullscreen(); + if (fullscreen != save.fullscreen) { + save.fullscreen = fullscreen; + save.is_dirty = true; + } + + if (save.is_dirty) { + // FIXME: use a text based format? + // FIXME: this should probably run async somewhere + save.is_dirty = false; + platform_store_userdata("save.dat", &save, sizeof(save_t)); + printf("wrote save.dat\n"); + } + + double now = platform_now(); + g.frame_time = now - frame_start_time; + if (g.frame_time > 0) { + g.frame_rate = ((double)g.frame_rate * 0.95) + (1.0/g.frame_time) * 0.05; + } +} + diff --git a/src/wipeout/game.h b/src/wipeout/game.h index 4164385..1359e18 100755 --- a/src/wipeout/game.h +++ b/src/wipeout/game.h @@ -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 diff --git a/src/wipeout/hud.c b/src/wipeout/hud.c index e4480e1..fe947e9 100755 --- a/src/wipeout/hud.c +++ b/src/wipeout/hud.c @@ -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); + } +} diff --git a/src/wipeout/hud.h b/src/wipeout/hud.h index 39cfa9f..f842b1d 100755 --- a/src/wipeout/hud.h +++ b/src/wipeout/hud.h @@ -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 diff --git a/src/wipeout/image.c b/src/wipeout/image.c index 9f5fc7f..99712d6 100755 --- a/src/wipeout/image.c +++ b/src/wipeout/image.c @@ -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 - - -#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 + + +#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; + } +} + diff --git a/src/wipeout/image.h b/src/wipeout/image.h index 7f758c5..d545212 100755 --- a/src/wipeout/image.h +++ b/src/wipeout/image.h @@ -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 \ No newline at end of file diff --git a/src/wipeout/ingame_menus.c b/src/wipeout/ingame_menus.c index 1cb7f7a..1eb93ed 100644 --- a/src/wipeout/ingame_menus.c +++ b/src/wipeout/ingame_menus.c @@ -1,486 +1,486 @@ -#include - -#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 + +#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; +} diff --git a/src/wipeout/ingame_menus.h b/src/wipeout/ingame_menus.h index 2386b79..78443b9 100644 --- a/src/wipeout/ingame_menus.h +++ b/src/wipeout/ingame_menus.h @@ -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 diff --git a/src/wipeout/main_menu.c b/src/wipeout/main_menu.c index 80d81dd..edb1b1e 100755 --- a/src/wipeout/main_menu.c +++ b/src/wipeout/main_menu.c @@ -1,541 +1,541 @@ -#include "../utils.h" -#include "../system.h" -#include "../mem.h" -#include "../platform.h" -#include "../input.h" - -#include "menu.h" -#include "main_menu.h" -#include "game.h" -#include "image.h" -#include "ui.h" - -static void page_main_init(menu_t *menu); -static void page_options_init(menu_t *menu); -static void page_race_class_init(menu_t *menu); -static void page_race_type_init(menu_t *menu); -static void page_team_init(menu_t *menu); -static void page_pilot_init(menu_t *menu); -static void page_circut_init(menu_t *menu); -static void page_options_controls_init(menu_t *menu); -static void page_options_video_init(menu_t *menu); -static void page_options_audio_init(menu_t *menu); - -static uint16_t background; -static texture_list_t track_images; -static menu_t *main_menu; - -static struct { - Object *race_classes[2]; - Object *teams[4]; - Object *pilots[8]; - struct { Object *stopwatch, *save, *load, *headphones, *cd; } options; - struct { Object *championship, *msdos, *single_race, *options; } misc; - Object *rescue; - Object *controller; -} models; - -static void draw_model(Object *model, vec2_t offset, vec3_t pos, float rotation) { - render_set_view(vec3(0,0,0), vec3(0, -M_PI, -M_PI)); - render_set_screen_position(offset); - mat4_t mat = mat4_identity(); - mat4_set_translation(&mat, pos); - mat4_set_yaw_pitch_roll(&mat, vec3(0, rotation, M_PI)); - object_draw(model, &mat); - render_set_screen_position(vec2(0, 0)); -} - -// ----------------------------------------------------------------------------- -// Main Menu - -static void button_start_game(menu_t *menu, int data) { - page_race_class_init(menu); -} - -static void button_options(menu_t *menu, int data) { - page_options_init(menu); -} - -static void button_quit_confirm(menu_t *menu, int data) { - if (data) { - system_exit(); - } - 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 page_main_draw(menu_t *menu, int data) { - switch (data) { - case 0: draw_model(g.ships[0].model, vec2(0, -0.1), vec3(0, 0, -700), system_cycle_time()); break; - case 1: draw_model(models.misc.options, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; - case 2: draw_model(models.misc.msdos, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; - } -} - -static void page_main_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "OPTIONS", page_main_draw); - flags_add(page->layout_flags, MENU_FIXED); - page->title_pos = vec2i(0, 30); - page->title_anchor = UI_POS_TOP | UI_POS_CENTER; - page->items_pos = vec2i(0, -110); - page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - - menu_page_add_button(page, 0, "START GAME", button_start_game); - menu_page_add_button(page, 1, "OPTIONS", button_options); - - #ifndef __EMSCRIPTEN__ - menu_page_add_button(page, 2, "QUIT", button_quit); - #endif -} - - - -// ----------------------------------------------------------------------------- -// Options - -static void button_controls(menu_t *menu, int data) { - page_options_controls_init(menu); -} - -static void button_video(menu_t *menu, int data) { - page_options_video_init(menu); -} - -static void button_audio(menu_t *menu, int data) { - page_options_audio_init(menu); -} - -static void page_options_draw(menu_t *menu, int data) { - switch (data) { - case 0: draw_model(models.controller, vec2(0, -0.1), vec3(0, 0, -6000), system_cycle_time()); break; - case 1: draw_model(models.rescue, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; // TODO: needs better model - case 2: draw_model(models.options.headphones, vec2(0, -0.2), vec3(0, 0, -300), system_cycle_time()); break; - } -} - -static void page_options_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "OPTIONS", page_options_draw); - flags_add(page->layout_flags, MENU_FIXED); - page->title_pos = vec2i(0, 30); - page->title_anchor = UI_POS_TOP | UI_POS_CENTER; - page->items_pos = vec2i(0, -110); - page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - menu_page_add_button(page, 0, "CONTROLS", button_controls); - menu_page_add_button(page, 1, "VIDEO", button_video); - menu_page_add_button(page, 2, "AUDIO", button_audio); -} - - -// ----------------------------------------------------------------------------- -// Options Controls - -static const char *button_names[NUM_GAME_ACTIONS][2] = {}; -static int control_current_action; -static float await_input_deadline; - -void button_capture(void *user, button_t button, int32_t ascii_char) { - if (button == INPUT_INVALID) { - return; - } - - menu_t *menu = (menu_t *)user; - if (button == INPUT_KEY_ESCAPE) { - input_capture(NULL, NULL); - menu_pop(menu); - return; - } - - int index = button < INPUT_KEY_MAX ? 0 : 1; // joypad or keyboard - - // unbind this button if it's bound anywhere - for (int i = 0; i < len(save.buttons); i++) { - if (save.buttons[i][index] == button) { - save.buttons[i][index] = INPUT_INVALID; - } - } - input_capture(NULL, NULL); - input_bind(INPUT_LAYER_USER, button, control_current_action); - save.buttons[control_current_action][index] = button; - save.is_dirty = true; - menu_pop(menu); -} - -static void page_options_control_set_draw(menu_t *menu, int data) { - float remaining = await_input_deadline - platform_now(); - - menu_page_t *page = &menu->pages[menu->index]; - char remaining_text[2] = { '0' + (uint8_t)clamp(remaining + 1, 0, 3), '\0'}; - vec2i_t pos = vec2i(page->items_pos.x, page->items_pos.y + 24); - ui_draw_text_centered(remaining_text, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_16, UI_COLOR_DEFAULT); - - if (remaining <= 0) { - input_capture(NULL, NULL); - menu_pop(menu); - return; - } -} - -static void page_options_controls_set_init(menu_t *menu, int data) { - control_current_action = data; - await_input_deadline = platform_now() + 3; - - menu_page_t *page = menu_push(menu, "AWAITING INPUT", page_options_control_set_draw); - input_capture(button_capture, menu); -} - - -static void page_options_control_draw(menu_t *menu, int data) { - menu_page_t *page = &menu->pages[menu->index]; - - int left = page->items_pos.x + page->block_width - 100; - int right = page->items_pos.x + page->block_width; - int line_y = page->items_pos.y - 20; - - vec2i_t left_head_pos = vec2i(left - ui_text_width("KEYBOARD", UI_SIZE_8), line_y); - ui_draw_text("KEYBOARD", ui_scaled_pos(page->items_anchor, left_head_pos), UI_SIZE_8, UI_COLOR_DEFAULT); - - vec2i_t right_head_pos = vec2i(right - ui_text_width("JOYSTICK", UI_SIZE_8), line_y); - ui_draw_text("JOYSTICK", ui_scaled_pos(page->items_anchor, right_head_pos), UI_SIZE_8, UI_COLOR_DEFAULT); - line_y += 20; - - for (int action = 0; action < NUM_GAME_ACTIONS; action++) { - rgba_t text_color = UI_COLOR_DEFAULT; - if (action == data) { - text_color = UI_COLOR_ACCENT; - } - - if (save.buttons[action][0] != INPUT_INVALID) { - const char *name = input_button_to_name(save.buttons[action][0]); - if (!name) { - name = "UNKNWN"; - } - vec2i_t pos = vec2i(left - ui_text_width(name, UI_SIZE_8), line_y); - ui_draw_text(name, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_8, text_color); - } - if (save.buttons[action][1] != INPUT_INVALID) { - const char *name = input_button_to_name(save.buttons[action][1]); - if (!name) { - name = "UNKNWN"; - } - vec2i_t pos = vec2i(right - ui_text_width(name, UI_SIZE_8), line_y); - ui_draw_text(name, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_8, text_color); - } - line_y += 12; - } -} - -static void page_options_controls_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "CONTROLS", page_options_control_draw); - flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); - page->title_pos = vec2i(-160, -100); - page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; - page->items_pos = vec2i(-160, -50); - page->block_width = 320; - page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; - - // const char *thrust_name = button_name(A_THRUST); - // printf("thrust: %s\n", thrust_name); - menu_page_add_button(page, A_UP, "UP", page_options_controls_set_init); - menu_page_add_button(page, A_DOWN, "DOWN", page_options_controls_set_init); - menu_page_add_button(page, A_LEFT, "LEFT", page_options_controls_set_init); - menu_page_add_button(page, A_RIGHT, "RIGHT", page_options_controls_set_init); - menu_page_add_button(page, A_BRAKE_LEFT, "BRAKE L", page_options_controls_set_init); - menu_page_add_button(page, A_BRAKE_RIGHT, "BRAKE R", page_options_controls_set_init); - menu_page_add_button(page, A_THRUST, "THRUST", page_options_controls_set_init); - menu_page_add_button(page, A_FIRE, "FIRE", page_options_controls_set_init); - menu_page_add_button(page, A_CHANGE_VIEW, "VIEW", page_options_controls_set_init); -} - -// ----------------------------------------------------------------------------- -// Options Video - -static void toggle_fullscreen(menu_t *menu, int data) { - save.fullscreen = data; - save.is_dirty = true; - platform_set_fullscreen(save.fullscreen); -} - -static void toggle_show_fps(menu_t *menu, int data) { - save.show_fps = data; - save.is_dirty = true; -} - -static void toggle_ui_scale(menu_t *menu, int data) { - save.ui_scale = data; - save.is_dirty = true; -} - -static void toggle_res(menu_t *menu, int data) { - render_set_resolution(data); - save.screen_res = data; - save.is_dirty = true; -} - -static void toggle_post(menu_t *menu, int data) { - render_set_post_effect(data); - save.post_effect = data; - save.is_dirty = true; -} - -static const char *opts_off_on[] = {"OFF", "ON"}; -static const char *opts_ui_sizes[] = {"AUTO", "1X", "2X", "3X", "4X"}; -static const char *opts_res[] = {"NATIVE", "240P", "480P"}; -static const char *opts_post[] = {"NONE", "CRT EFFECT"}; - -static void page_options_video_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "VIDEO OPTIONS", NULL); - flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); - page->title_pos = vec2i(-160, -100); - page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; - page->items_pos = vec2i(-160, -60); - page->block_width = 320; - page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; - - #ifndef __EMSCRIPTEN__ - menu_page_add_toggle(page, save.fullscreen, "FULLSCREEN", opts_off_on, len(opts_off_on), toggle_fullscreen); - #endif - menu_page_add_toggle(page, save.ui_scale, "UI SCALE", opts_ui_sizes, len(opts_ui_sizes), toggle_ui_scale); - menu_page_add_toggle(page, save.show_fps, "SHOW FPS", opts_off_on, len(opts_off_on), toggle_show_fps); - menu_page_add_toggle(page, save.screen_res, "SCREEN RESOLUTION", opts_res, len(opts_res), toggle_res); - menu_page_add_toggle(page, save.post_effect, "POST PROCESSING", opts_post, len(opts_post), toggle_post); -} - -// ----------------------------------------------------------------------------- -// Options Audio - -static void toggle_music_volume(menu_t *menu, int data) { - save.music_volume = (float)data * 0.1; - save.is_dirty = true; -} - -static void toggle_sfx_volume(menu_t *menu, int data) { - save.sfx_volume = (float)data * 0.1; - save.is_dirty = true; -} - -static const char *opts_volume[] = {"0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"}; - -static void page_options_audio_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "AUDIO OPTIONS", NULL); - - flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); - page->title_pos = vec2i(-160, -100); - page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; - page->items_pos = vec2i(-160, -80); - page->block_width = 320; - page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; - - menu_page_add_toggle(page, save.music_volume * 10, "MUSIC VOLUME", opts_volume, len(opts_volume), toggle_music_volume); - menu_page_add_toggle(page, save.sfx_volume * 10, "SOUND EFFECTS VOLUME", opts_volume, len(opts_volume), toggle_sfx_volume); -} - - - - - - - - -// ----------------------------------------------------------------------------- -// Racing class - -static void button_race_class_select(menu_t *menu, int data) { - if (!save.has_rapier_class && data == RACE_CLASS_RAPIER) { - return; - } - g.race_class = data; - page_race_type_init(menu); -} - -static void page_race_class_draw(menu_t *menu, int data) { - menu_page_t *page = &menu->pages[menu->index]; - flags_add(page->layout_flags, MENU_FIXED); - page->title_pos = vec2i(0, 30); - page->title_anchor = UI_POS_TOP | UI_POS_CENTER; - page->items_pos = vec2i(0, -110); - page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - draw_model(models.race_classes[data], vec2(0, -0.2), vec3(0, 0, -350), system_cycle_time()); - - if (!save.has_rapier_class && data == RACE_CLASS_RAPIER) { - render_set_view_2d(); - vec2i_t pos = vec2i(page->items_pos.x, page->items_pos.y + 32); - ui_draw_text_centered("NOT AVAILABLE", ui_scaled_pos(page->items_anchor, pos), UI_SIZE_12, UI_COLOR_ACCENT); - } -} - -static void page_race_class_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "SELECT RACING CLASS", page_race_class_draw); - for (int i = 0; i < len(def.race_classes); i++) { - menu_page_add_button(page, i, def.race_classes[i].name, button_race_class_select); - } -} - - - -// ----------------------------------------------------------------------------- -// Race Type - -static void button_race_type_select(menu_t *menu, int data) { - g.race_type = data; - g.highscore_tab = g.race_type == RACE_TYPE_TIME_TRIAL ? HIGHSCORE_TAB_TIME_TRIAL : HIGHSCORE_TAB_RACE; - page_team_init(menu); -} - -static void page_race_type_draw(menu_t *menu, int data) { - switch (data) { - case 0: draw_model(models.misc.championship, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; - case 1: draw_model(models.misc.single_race, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; - case 2: draw_model(models.options.stopwatch, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; - } -} - -static void page_race_type_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "SELECT RACE TYPE", page_race_type_draw); - flags_add(page->layout_flags, MENU_FIXED); - page->title_pos = vec2i(0, 30); - page->title_anchor = UI_POS_TOP | UI_POS_CENTER; - page->items_pos = vec2i(0, -110); - page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - for (int i = 0; i < len(def.race_types); i++) { - menu_page_add_button(page, i, def.race_types[i].name, button_race_type_select); - } -} - - - -// ----------------------------------------------------------------------------- -// Team - -static void button_team_select(menu_t *menu, int data) { - g.team = data; - page_pilot_init(menu); -} - -static void page_team_draw(menu_t *menu, int data) { - int team_model_index = (data + 3) % 4; // models in the prm are shifted by -1 - draw_model(models.teams[team_model_index], vec2(0, -0.2), vec3(0, 0, -10000), system_cycle_time()); - draw_model(g.ships[def.teams[data].pilots[0]].model, vec2(0, -0.3), vec3(-700, -800, -1300), system_cycle_time()*1.1); - draw_model(g.ships[def.teams[data].pilots[1]].model, vec2(0, -0.3), vec3( 700, -800, -1300), system_cycle_time()*1.2); -} - -static void page_team_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "SELECT YOUR TEAM", page_team_draw); - flags_add(page->layout_flags, MENU_FIXED); - page->title_pos = vec2i(0, 30); - page->title_anchor = UI_POS_TOP | UI_POS_CENTER; - page->items_pos = vec2i(0, -110); - page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - for (int i = 0; i < len(def.teams); i++) { - menu_page_add_button(page, i, def.teams[i].name, button_team_select); - } -} - - - -// ----------------------------------------------------------------------------- -// Pilot - -static void button_pilot_select(menu_t *menu, int data) { - g.pilot = data; - if (g.race_type != RACE_TYPE_CHAMPIONSHIP) { - page_circut_init(menu); - } - else { - g.circut = 0; - game_reset_championship(); - game_set_scene(GAME_SCENE_RACE); - } -} - -static void page_pilot_draw(menu_t *menu, int data) { - draw_model(models.pilots[data], vec2(0, -0.2), vec3(0, 0, -10000), system_cycle_time()); -} - -static void page_pilot_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "CHOOSE YOUR PILOT", page_pilot_draw); - flags_add(page->layout_flags, MENU_FIXED); - page->title_pos = vec2i(0, 30); - page->title_anchor = UI_POS_TOP | UI_POS_CENTER; - page->items_pos = vec2i(0, -110); - page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - for (int i = 0; i < len(def.teams[g.team].pilots); i++) { - menu_page_add_button(page, def.teams[g.team].pilots[i], def.pilots[def.teams[g.team].pilots[i]].name, button_pilot_select); - } -} - - -// ----------------------------------------------------------------------------- -// Circut - -static void button_circut_select(menu_t *menu, int data) { - g.circut = data; - game_set_scene(GAME_SCENE_RACE); -} - -static void page_circut_draw(menu_t *menu, int data) { - vec2i_t pos = vec2i(0, -25); - vec2i_t size = vec2i(128, 74); - vec2i_t scaled_size = ui_scaled(size); - vec2i_t scaled_pos = ui_scaled_pos(UI_POS_MIDDLE | UI_POS_CENTER, vec2i(pos.x - size.x/2, pos.y - size.y/2)); - render_push_2d(scaled_pos, scaled_size, rgba(128, 128, 128, 255), texture_from_list(track_images, data)); -} - -static void page_circut_init(menu_t *menu) { - menu_page_t *page = menu_push(menu, "SELECT RACING CIRCUT", page_circut_draw); - flags_add(page->layout_flags, MENU_FIXED); - page->title_pos = vec2i(0, 30); - page->title_anchor = UI_POS_TOP | UI_POS_CENTER; - page->items_pos = vec2i(0, -100); - page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; - for (int i = 0; i < len(def.circuts); i++) { - if (!def.circuts[i].is_bonus_circut || save.has_bonus_circuts) { - menu_page_add_button(page, i, def.circuts[i].name, button_circut_select); - } - } -} - -#define objects_unpack(DEST, SRC) \ - objects_unpack_imp((Object **)&DEST, sizeof(DEST)/sizeof(Object*), SRC) - -static void objects_unpack_imp(Object **dest_array, int len, Object *src) { - int i; - for (i = 0; src && i < len; i++) { - dest_array[i] = src; - src = src->next; - } - error_if(i != len, "expected %d models got %d", len, i) -} - - -void main_menu_init(void) { - g.is_attract_mode = false; - - main_menu = mem_bump(sizeof(menu_t)); - - background = image_get_texture("wipeout/textures/wipeout1.tim"); - track_images = image_get_compressed_textures("wipeout/textures/track.cmp"); - - objects_unpack(models.race_classes, objects_load("wipeout/common/leeg.prm", image_get_compressed_textures("wipeout/common/leeg.cmp"))); - objects_unpack(models.teams, objects_load("wipeout/common/teams.prm", texture_list_empty())); - objects_unpack(models.pilots, objects_load("wipeout/common/pilot.prm", image_get_compressed_textures("wipeout/common/pilot.cmp"))); - objects_unpack(models.options, objects_load("wipeout/common/alopt.prm", image_get_compressed_textures("wipeout/common/alopt.cmp"))); - objects_unpack(models.rescue, objects_load("wipeout/common/rescu.prm", image_get_compressed_textures("wipeout/common/rescu.cmp"))); - objects_unpack(models.controller, objects_load("wipeout/common/pad1.prm", image_get_compressed_textures("wipeout/common/pad1.cmp"))); - objects_unpack(models.misc, objects_load("wipeout/common/msdos.prm", image_get_compressed_textures("wipeout/common/msdos.cmp"))); - - menu_reset(main_menu); - page_main_init(main_menu); -} - -void main_menu_update(void) { - render_set_view_2d(); - render_push_2d(vec2i(0, 0), render_size(), rgba(128, 128, 128, 255), background); - - menu_update(main_menu); -} - +#include "../utils.h" +#include "../system.h" +#include "../mem.h" +#include "../platform.h" +#include "../input.h" + +#include "menu.h" +#include "main_menu.h" +#include "game.h" +#include "image.h" +#include "ui.h" + +static void page_main_init(menu_t *menu); +static void page_options_init(menu_t *menu); +static void page_race_class_init(menu_t *menu); +static void page_race_type_init(menu_t *menu); +static void page_team_init(menu_t *menu); +static void page_pilot_init(menu_t *menu); +static void page_circut_init(menu_t *menu); +static void page_options_controls_init(menu_t *menu); +static void page_options_video_init(menu_t *menu); +static void page_options_audio_init(menu_t *menu); + +static uint16_t background; +static texture_list_t track_images; +static menu_t *main_menu; + +static struct { + Object *race_classes[2]; + Object *teams[4]; + Object *pilots[8]; + struct { Object *stopwatch, *save, *load, *headphones, *cd; } options; + struct { Object *championship, *msdos, *single_race, *options; } misc; + Object *rescue; + Object *controller; +} models; + +static void draw_model(Object *model, vec2_t offset, vec3_t pos, float rotation) { + render_set_view(vec3(0,0,0), vec3(0, -M_PI, -M_PI)); + render_set_screen_position(offset); + mat4_t mat = mat4_identity(); + mat4_set_translation(&mat, pos); + mat4_set_yaw_pitch_roll(&mat, vec3(0, rotation, M_PI)); + object_draw(model, &mat); + render_set_screen_position(vec2(0, 0)); +} + +// ----------------------------------------------------------------------------- +// Main Menu + +static void button_start_game(menu_t *menu, int data) { + page_race_class_init(menu); +} + +static void button_options(menu_t *menu, int data) { + page_options_init(menu); +} + +static void button_quit_confirm(menu_t *menu, int data) { + if (data) { + system_exit(); + } + 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 page_main_draw(menu_t *menu, int data) { + switch (data) { + case 0: draw_model(g.ships[0].model, vec2(0, -0.1), vec3(0, 0, -700), system_cycle_time()); break; + case 1: draw_model(models.misc.options, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; + case 2: draw_model(models.misc.msdos, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; + } +} + +static void page_main_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "OPTIONS", page_main_draw); + flags_add(page->layout_flags, MENU_FIXED); + page->title_pos = vec2i(0, 30); + page->title_anchor = UI_POS_TOP | UI_POS_CENTER; + page->items_pos = vec2i(0, -110); + page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; + + menu_page_add_button(page, 0, "START GAME", button_start_game); + menu_page_add_button(page, 1, "OPTIONS", button_options); + + #ifndef __EMSCRIPTEN__ + menu_page_add_button(page, 2, "QUIT", button_quit); + #endif +} + + + +// ----------------------------------------------------------------------------- +// Options + +static void button_controls(menu_t *menu, int data) { + page_options_controls_init(menu); +} + +static void button_video(menu_t *menu, int data) { + page_options_video_init(menu); +} + +static void button_audio(menu_t *menu, int data) { + page_options_audio_init(menu); +} + +static void page_options_draw(menu_t *menu, int data) { + switch (data) { + case 0: draw_model(models.controller, vec2(0, -0.1), vec3(0, 0, -6000), system_cycle_time()); break; + case 1: draw_model(models.rescue, vec2(0, -0.2), vec3(0, 0, -700), system_cycle_time()); break; // TODO: needs better model + case 2: draw_model(models.options.headphones, vec2(0, -0.2), vec3(0, 0, -300), system_cycle_time()); break; + } +} + +static void page_options_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "OPTIONS", page_options_draw); + flags_add(page->layout_flags, MENU_FIXED); + page->title_pos = vec2i(0, 30); + page->title_anchor = UI_POS_TOP | UI_POS_CENTER; + page->items_pos = vec2i(0, -110); + page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; + menu_page_add_button(page, 0, "CONTROLS", button_controls); + menu_page_add_button(page, 1, "VIDEO", button_video); + menu_page_add_button(page, 2, "AUDIO", button_audio); +} + + +// ----------------------------------------------------------------------------- +// Options Controls + +static const char *button_names[NUM_GAME_ACTIONS][2] = {}; +static int control_current_action; +static float await_input_deadline; + +void button_capture(void *user, button_t button, int32_t ascii_char) { + if (button == INPUT_INVALID) { + return; + } + + menu_t *menu = (menu_t *)user; + if (button == INPUT_KEY_ESCAPE) { + input_capture(NULL, NULL); + menu_pop(menu); + return; + } + + int index = button < INPUT_KEY_MAX ? 0 : 1; // joypad or keyboard + + // unbind this button if it's bound anywhere + for (int i = 0; i < len(save.buttons); i++) { + if (save.buttons[i][index] == button) { + save.buttons[i][index] = INPUT_INVALID; + } + } + input_capture(NULL, NULL); + input_bind(INPUT_LAYER_USER, button, control_current_action); + save.buttons[control_current_action][index] = button; + save.is_dirty = true; + menu_pop(menu); +} + +static void page_options_control_set_draw(menu_t *menu, int data) { + float remaining = await_input_deadline - platform_now(); + + menu_page_t *page = &menu->pages[menu->index]; + char remaining_text[2] = { '0' + (uint8_t)clamp(remaining + 1, 0, 3), '\0'}; + vec2i_t pos = vec2i(page->items_pos.x, page->items_pos.y + 24); + ui_draw_text_centered(remaining_text, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_16, UI_COLOR_DEFAULT); + + if (remaining <= 0) { + input_capture(NULL, NULL); + menu_pop(menu); + return; + } +} + +static void page_options_controls_set_init(menu_t *menu, int data) { + control_current_action = data; + await_input_deadline = platform_now() + 3; + + menu_page_t *page = menu_push(menu, "AWAITING INPUT", page_options_control_set_draw); + input_capture(button_capture, menu); +} + + +static void page_options_control_draw(menu_t *menu, int data) { + menu_page_t *page = &menu->pages[menu->index]; + + int left = page->items_pos.x + page->block_width - 100; + int right = page->items_pos.x + page->block_width; + int line_y = page->items_pos.y - 20; + + vec2i_t left_head_pos = vec2i(left - ui_text_width("KEYBOARD", UI_SIZE_8), line_y); + ui_draw_text("KEYBOARD", ui_scaled_pos(page->items_anchor, left_head_pos), UI_SIZE_8, UI_COLOR_DEFAULT); + + vec2i_t right_head_pos = vec2i(right - ui_text_width("JOYSTICK", UI_SIZE_8), line_y); + ui_draw_text("JOYSTICK", ui_scaled_pos(page->items_anchor, right_head_pos), UI_SIZE_8, UI_COLOR_DEFAULT); + line_y += 20; + + for (int action = 0; action < NUM_GAME_ACTIONS; action++) { + rgba_t text_color = UI_COLOR_DEFAULT; + if (action == data) { + text_color = UI_COLOR_ACCENT; + } + + if (save.buttons[action][0] != INPUT_INVALID) { + const char *name = input_button_to_name(save.buttons[action][0]); + if (!name) { + name = "UNKNWN"; + } + vec2i_t pos = vec2i(left - ui_text_width(name, UI_SIZE_8), line_y); + ui_draw_text(name, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_8, text_color); + } + if (save.buttons[action][1] != INPUT_INVALID) { + const char *name = input_button_to_name(save.buttons[action][1]); + if (!name) { + name = "UNKNWN"; + } + vec2i_t pos = vec2i(right - ui_text_width(name, UI_SIZE_8), line_y); + ui_draw_text(name, ui_scaled_pos(page->items_anchor, pos), UI_SIZE_8, text_color); + } + line_y += 12; + } +} + +static void page_options_controls_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "CONTROLS", page_options_control_draw); + flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); + page->title_pos = vec2i(-160, -100); + page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; + page->items_pos = vec2i(-160, -50); + page->block_width = 320; + page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; + + // const char *thrust_name = button_name(A_THRUST); + // printf("thrust: %s\n", thrust_name); + menu_page_add_button(page, A_UP, "UP", page_options_controls_set_init); + menu_page_add_button(page, A_DOWN, "DOWN", page_options_controls_set_init); + menu_page_add_button(page, A_LEFT, "LEFT", page_options_controls_set_init); + menu_page_add_button(page, A_RIGHT, "RIGHT", page_options_controls_set_init); + menu_page_add_button(page, A_BRAKE_LEFT, "BRAKE L", page_options_controls_set_init); + menu_page_add_button(page, A_BRAKE_RIGHT, "BRAKE R", page_options_controls_set_init); + menu_page_add_button(page, A_THRUST, "THRUST", page_options_controls_set_init); + menu_page_add_button(page, A_FIRE, "FIRE", page_options_controls_set_init); + menu_page_add_button(page, A_CHANGE_VIEW, "VIEW", page_options_controls_set_init); +} + +// ----------------------------------------------------------------------------- +// Options Video + +static void toggle_fullscreen(menu_t *menu, int data) { + save.fullscreen = data; + save.is_dirty = true; + platform_set_fullscreen(save.fullscreen); +} + +static void toggle_show_fps(menu_t *menu, int data) { + save.show_fps = data; + save.is_dirty = true; +} + +static void toggle_ui_scale(menu_t *menu, int data) { + save.ui_scale = data; + save.is_dirty = true; +} + +static void toggle_res(menu_t *menu, int data) { + render_set_resolution(data); + save.screen_res = data; + save.is_dirty = true; +} + +static void toggle_post(menu_t *menu, int data) { + render_set_post_effect(data); + save.post_effect = data; + save.is_dirty = true; +} + +static const char *opts_off_on[] = {"OFF", "ON"}; +static const char *opts_ui_sizes[] = {"AUTO", "1X", "2X", "3X", "4X"}; +static const char *opts_res[] = {"NATIVE", "240P", "480P"}; +static const char *opts_post[] = {"NONE", "CRT EFFECT"}; + +static void page_options_video_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "VIDEO OPTIONS", NULL); + flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); + page->title_pos = vec2i(-160, -100); + page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; + page->items_pos = vec2i(-160, -60); + page->block_width = 320; + page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; + + #ifndef __EMSCRIPTEN__ + menu_page_add_toggle(page, save.fullscreen, "FULLSCREEN", opts_off_on, len(opts_off_on), toggle_fullscreen); + #endif + menu_page_add_toggle(page, save.ui_scale, "UI SCALE", opts_ui_sizes, len(opts_ui_sizes), toggle_ui_scale); + menu_page_add_toggle(page, save.show_fps, "SHOW FPS", opts_off_on, len(opts_off_on), toggle_show_fps); + menu_page_add_toggle(page, save.screen_res, "SCREEN RESOLUTION", opts_res, len(opts_res), toggle_res); + menu_page_add_toggle(page, save.post_effect, "POST PROCESSING", opts_post, len(opts_post), toggle_post); +} + +// ----------------------------------------------------------------------------- +// Options Audio + +static void toggle_music_volume(menu_t *menu, int data) { + save.music_volume = (float)data * 0.1; + save.is_dirty = true; +} + +static void toggle_sfx_volume(menu_t *menu, int data) { + save.sfx_volume = (float)data * 0.1; + save.is_dirty = true; +} + +static const char *opts_volume[] = {"0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"}; + +static void page_options_audio_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "AUDIO OPTIONS", NULL); + + flags_set(page->layout_flags, MENU_VERTICAL | MENU_FIXED); + page->title_pos = vec2i(-160, -100); + page->title_anchor = UI_POS_MIDDLE | UI_POS_CENTER; + page->items_pos = vec2i(-160, -80); + page->block_width = 320; + page->items_anchor = UI_POS_MIDDLE | UI_POS_CENTER; + + menu_page_add_toggle(page, save.music_volume * 10, "MUSIC VOLUME", opts_volume, len(opts_volume), toggle_music_volume); + menu_page_add_toggle(page, save.sfx_volume * 10, "SOUND EFFECTS VOLUME", opts_volume, len(opts_volume), toggle_sfx_volume); +} + + + + + + + + +// ----------------------------------------------------------------------------- +// Racing class + +static void button_race_class_select(menu_t *menu, int data) { + if (!save.has_rapier_class && data == RACE_CLASS_RAPIER) { + return; + } + g.race_class = data; + page_race_type_init(menu); +} + +static void page_race_class_draw(menu_t *menu, int data) { + menu_page_t *page = &menu->pages[menu->index]; + flags_add(page->layout_flags, MENU_FIXED); + page->title_pos = vec2i(0, 30); + page->title_anchor = UI_POS_TOP | UI_POS_CENTER; + page->items_pos = vec2i(0, -110); + page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; + draw_model(models.race_classes[data], vec2(0, -0.2), vec3(0, 0, -350), system_cycle_time()); + + if (!save.has_rapier_class && data == RACE_CLASS_RAPIER) { + render_set_view_2d(); + vec2i_t pos = vec2i(page->items_pos.x, page->items_pos.y + 32); + ui_draw_text_centered("NOT AVAILABLE", ui_scaled_pos(page->items_anchor, pos), UI_SIZE_12, UI_COLOR_ACCENT); + } +} + +static void page_race_class_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "SELECT RACING CLASS", page_race_class_draw); + for (int i = 0; i < len(def.race_classes); i++) { + menu_page_add_button(page, i, def.race_classes[i].name, button_race_class_select); + } +} + + + +// ----------------------------------------------------------------------------- +// Race Type + +static void button_race_type_select(menu_t *menu, int data) { + g.race_type = data; + g.highscore_tab = g.race_type == RACE_TYPE_TIME_TRIAL ? HIGHSCORE_TAB_TIME_TRIAL : HIGHSCORE_TAB_RACE; + page_team_init(menu); +} + +static void page_race_type_draw(menu_t *menu, int data) { + switch (data) { + case 0: draw_model(models.misc.championship, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; + case 1: draw_model(models.misc.single_race, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; + case 2: draw_model(models.options.stopwatch, vec2(0, -0.2), vec3(0, 0, -400), system_cycle_time()); break; + } +} + +static void page_race_type_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "SELECT RACE TYPE", page_race_type_draw); + flags_add(page->layout_flags, MENU_FIXED); + page->title_pos = vec2i(0, 30); + page->title_anchor = UI_POS_TOP | UI_POS_CENTER; + page->items_pos = vec2i(0, -110); + page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; + for (int i = 0; i < len(def.race_types); i++) { + menu_page_add_button(page, i, def.race_types[i].name, button_race_type_select); + } +} + + + +// ----------------------------------------------------------------------------- +// Team + +static void button_team_select(menu_t *menu, int data) { + g.team = data; + page_pilot_init(menu); +} + +static void page_team_draw(menu_t *menu, int data) { + int team_model_index = (data + 3) % 4; // models in the prm are shifted by -1 + draw_model(models.teams[team_model_index], vec2(0, -0.2), vec3(0, 0, -10000), system_cycle_time()); + draw_model(g.ships[def.teams[data].pilots[0]].model, vec2(0, -0.3), vec3(-700, -800, -1300), system_cycle_time()*1.1); + draw_model(g.ships[def.teams[data].pilots[1]].model, vec2(0, -0.3), vec3( 700, -800, -1300), system_cycle_time()*1.2); +} + +static void page_team_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "SELECT YOUR TEAM", page_team_draw); + flags_add(page->layout_flags, MENU_FIXED); + page->title_pos = vec2i(0, 30); + page->title_anchor = UI_POS_TOP | UI_POS_CENTER; + page->items_pos = vec2i(0, -110); + page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; + for (int i = 0; i < len(def.teams); i++) { + menu_page_add_button(page, i, def.teams[i].name, button_team_select); + } +} + + + +// ----------------------------------------------------------------------------- +// Pilot + +static void button_pilot_select(menu_t *menu, int data) { + g.pilot = data; + if (g.race_type != RACE_TYPE_CHAMPIONSHIP) { + page_circut_init(menu); + } + else { + g.circut = 0; + game_reset_championship(); + game_set_scene(GAME_SCENE_RACE); + } +} + +static void page_pilot_draw(menu_t *menu, int data) { + draw_model(models.pilots[data], vec2(0, -0.2), vec3(0, 0, -10000), system_cycle_time()); +} + +static void page_pilot_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "CHOOSE YOUR PILOT", page_pilot_draw); + flags_add(page->layout_flags, MENU_FIXED); + page->title_pos = vec2i(0, 30); + page->title_anchor = UI_POS_TOP | UI_POS_CENTER; + page->items_pos = vec2i(0, -110); + page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; + for (int i = 0; i < len(def.teams[g.team].pilots); i++) { + menu_page_add_button(page, def.teams[g.team].pilots[i], def.pilots[def.teams[g.team].pilots[i]].name, button_pilot_select); + } +} + + +// ----------------------------------------------------------------------------- +// Circut + +static void button_circut_select(menu_t *menu, int data) { + g.circut = data; + game_set_scene(GAME_SCENE_RACE); +} + +static void page_circut_draw(menu_t *menu, int data) { + vec2i_t pos = vec2i(0, -25); + vec2i_t size = vec2i(128, 74); + vec2i_t scaled_size = ui_scaled(size); + vec2i_t scaled_pos = ui_scaled_pos(UI_POS_MIDDLE | UI_POS_CENTER, vec2i(pos.x - size.x/2, pos.y - size.y/2)); + render_push_2d(scaled_pos, scaled_size, rgba(128, 128, 128, 255), texture_from_list(track_images, data)); +} + +static void page_circut_init(menu_t *menu) { + menu_page_t *page = menu_push(menu, "SELECT RACING CIRCUT", page_circut_draw); + flags_add(page->layout_flags, MENU_FIXED); + page->title_pos = vec2i(0, 30); + page->title_anchor = UI_POS_TOP | UI_POS_CENTER; + page->items_pos = vec2i(0, -100); + page->items_anchor = UI_POS_BOTTOM | UI_POS_CENTER; + for (int i = 0; i < len(def.circuts); i++) { + if (!def.circuts[i].is_bonus_circut || save.has_bonus_circuts) { + menu_page_add_button(page, i, def.circuts[i].name, button_circut_select); + } + } +} + +#define objects_unpack(DEST, SRC) \ + objects_unpack_imp((Object **)&DEST, sizeof(DEST)/sizeof(Object*), SRC) + +static void objects_unpack_imp(Object **dest_array, int len, Object *src) { + int i; + for (i = 0; src && i < len; i++) { + dest_array[i] = src; + src = src->next; + } + error_if(i != len, "expected %d models got %d", len, i) +} + + +void main_menu_init(void) { + g.is_attract_mode = false; + + main_menu = mem_bump(sizeof(menu_t)); + + background = image_get_texture("wipeout/textures/wipeout1.tim"); + track_images = image_get_compressed_textures("wipeout/textures/track.cmp"); + + objects_unpack(models.race_classes, objects_load("wipeout/common/leeg.prm", image_get_compressed_textures("wipeout/common/leeg.cmp"))); + objects_unpack(models.teams, objects_load("wipeout/common/teams.prm", texture_list_empty())); + objects_unpack(models.pilots, objects_load("wipeout/common/pilot.prm", image_get_compressed_textures("wipeout/common/pilot.cmp"))); + objects_unpack(models.options, objects_load("wipeout/common/alopt.prm", image_get_compressed_textures("wipeout/common/alopt.cmp"))); + objects_unpack(models.rescue, objects_load("wipeout/common/rescu.prm", image_get_compressed_textures("wipeout/common/rescu.cmp"))); + objects_unpack(models.controller, objects_load("wipeout/common/pad1.prm", image_get_compressed_textures("wipeout/common/pad1.cmp"))); + objects_unpack(models.misc, objects_load("wipeout/common/msdos.prm", image_get_compressed_textures("wipeout/common/msdos.cmp"))); + + menu_reset(main_menu); + page_main_init(main_menu); +} + +void main_menu_update(void) { + render_set_view_2d(); + render_push_2d(vec2i(0, 0), render_size(), rgba(128, 128, 128, 255), background); + + menu_update(main_menu); +} + diff --git a/src/wipeout/main_menu.h b/src/wipeout/main_menu.h index a939c32..0326b91 100755 --- a/src/wipeout/main_menu.h +++ b/src/wipeout/main_menu.h @@ -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 diff --git a/src/wipeout/menu.c b/src/wipeout/menu.c index 4235fd6..4cd005a 100644 --- a/src/wipeout/menu.c +++ b/src/wipeout/menu.c @@ -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); + } + } + } +} diff --git a/src/wipeout/menu.h b/src/wipeout/menu.h index ffeada4..b559339 100644 --- a/src/wipeout/menu.h +++ b/src/wipeout/menu.h @@ -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 diff --git a/src/wipeout/object.c b/src/wipeout/object.c index 1665f73..361a6fe 100755 --- a/src/wipeout/object.c +++ b/src/wipeout/object.c @@ -1,782 +1,782 @@ -#include "../types.h" -#include "../mem.h" -#include "../render.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 "hud.h" -#include "object.h" - -Object *objects_load(char *name, texture_list_t tl) { - uint32_t length = 0; - uint8_t *bytes = platform_load_asset(name, &length); - if (!bytes) { - die("Failed to load file %s\n", name); - } - printf("load: %s\n", name); - - Object *objectList = mem_mark(); - Object *prevObject = NULL; - uint32_t p = 0; - - while (p < length) { - Object *object = mem_bump(sizeof(Object)); - if (prevObject) { - prevObject->next = object; - } - prevObject = object; - - for (int i = 0; i < 16; i++) { - object->name[i] = get_i8(bytes, &p); - } - - object->mat = mat4_identity(); - object->vertices_len = get_i16(bytes, &p); p += 2; - object->vertices = NULL; get_i32(bytes, &p); - object->normals_len = get_i16(bytes, &p); p += 2; - object->normals = NULL; get_i32(bytes, &p); - object->primitives_len = get_i16(bytes, &p); p += 2; - object->primitives = NULL; get_i32(bytes, &p); - get_i32(bytes, &p); - get_i32(bytes, &p); - get_i32(bytes, &p); // Skeleton ref - object->extent = get_i32(bytes, &p); - object->flags = get_i16(bytes, &p); p += 2; - object->next = NULL; get_i32(bytes, &p); - - p += 3 * 3 * 2; // relative rot matrix - p += 2; // padding - - object->origin.x = get_i32(bytes, &p); - object->origin.y = get_i32(bytes, &p); - object->origin.z = get_i32(bytes, &p); - - p += 3 * 3 * 2; // absolute rot matrix - p += 2; // padding - p += 3 * 4; // absolute translation matrix - p += 2; // skeleton update flag - p += 2; // padding - p += 4; // skeleton super - p += 4; // skeleton sub - p += 4; // skeleton next - - object->radius = 0; - object->vertices = mem_bump(object->vertices_len * sizeof(vec3_t)); - for (int i = 0; i < object->vertices_len; i++) { - object->vertices[i].x = get_i16(bytes, &p); - object->vertices[i].y = get_i16(bytes, &p); - object->vertices[i].z = get_i16(bytes, &p); - p += 2; // padding - - if (fabsf(object->vertices[i].x) > object->radius) { - object->radius = fabsf(object->vertices[i].x); - } - if (fabsf(object->vertices[i].y) > object->radius) { - object->radius = fabsf(object->vertices[i].y); - } - if (fabsf(object->vertices[i].z) > object->radius) { - object->radius = fabsf(object->vertices[i].z); - } - } - - - - object->normals = mem_bump(object->normals_len * sizeof(vec3_t)); - for (int i = 0; i < object->normals_len; i++) { - object->normals[i].x = get_i16(bytes, &p); - object->normals[i].y = get_i16(bytes, &p); - object->normals[i].z = get_i16(bytes, &p); - p += 2; // padding - } - - object->primitives = mem_mark(); - for (int i = 0; i < object->primitives_len; i++) { - Prm prm; - int16_t prm_type = get_i16(bytes, &p); - int16_t prm_flag = get_i16(bytes, &p); - - switch (prm_type) { - case PRM_TYPE_F3: - prm.ptr = mem_bump(sizeof(F3)); - prm.f3->coords[0] = get_i16(bytes, &p); - prm.f3->coords[1] = get_i16(bytes, &p); - prm.f3->coords[2] = get_i16(bytes, &p); - prm.f3->pad1 = get_i16(bytes, &p); - prm.f3->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_F4: - prm.ptr = mem_bump(sizeof(F4)); - prm.f4->coords[0] = get_i16(bytes, &p); - prm.f4->coords[1] = get_i16(bytes, &p); - prm.f4->coords[2] = get_i16(bytes, &p); - prm.f4->coords[3] = get_i16(bytes, &p); - prm.f4->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_FT3: - prm.ptr = mem_bump(sizeof(FT3)); - prm.ft3->coords[0] = get_i16(bytes, &p); - prm.ft3->coords[1] = get_i16(bytes, &p); - prm.ft3->coords[2] = get_i16(bytes, &p); - - prm.ft3->texture = texture_from_list(tl, get_i16(bytes, &p)); - prm.ft3->cba = get_i16(bytes, &p); - prm.ft3->tsb = get_i16(bytes, &p); - prm.ft3->u0 = get_i8(bytes, &p); - prm.ft3->v0 = get_i8(bytes, &p); - prm.ft3->u1 = get_i8(bytes, &p); - prm.ft3->v1 = get_i8(bytes, &p); - prm.ft3->u2 = get_i8(bytes, &p); - prm.ft3->v2 = get_i8(bytes, &p); - - prm.ft3->pad1 = get_i16(bytes, &p); - prm.ft3->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_FT4: - prm.ptr = mem_bump(sizeof(FT4)); - prm.ft4->coords[0] = get_i16(bytes, &p); - prm.ft4->coords[1] = get_i16(bytes, &p); - prm.ft4->coords[2] = get_i16(bytes, &p); - prm.ft4->coords[3] = get_i16(bytes, &p); - - prm.ft4->texture = texture_from_list(tl, get_i16(bytes, &p)); - prm.ft4->cba = get_i16(bytes, &p); - prm.ft4->tsb = get_i16(bytes, &p); - prm.ft4->u0 = get_i8(bytes, &p); - prm.ft4->v0 = get_i8(bytes, &p); - prm.ft4->u1 = get_i8(bytes, &p); - prm.ft4->v1 = get_i8(bytes, &p); - prm.ft4->u2 = get_i8(bytes, &p); - prm.ft4->v2 = get_i8(bytes, &p); - prm.ft4->u3 = get_i8(bytes, &p); - prm.ft4->v3 = get_i8(bytes, &p); - prm.ft4->pad1 = get_i16(bytes, &p); - prm.ft4->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_G3: - prm.ptr = mem_bump(sizeof(G3)); - prm.g3->coords[0] = get_i16(bytes, &p); - prm.g3->coords[1] = get_i16(bytes, &p); - prm.g3->coords[2] = get_i16(bytes, &p); - prm.g3->pad1 = get_i16(bytes, &p); - prm.g3->color[0] = rgba_from_u32(get_u32(bytes, &p)); - prm.g3->color[1] = rgba_from_u32(get_u32(bytes, &p)); - prm.g3->color[2] = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_G4: - prm.ptr = mem_bump(sizeof(G4)); - prm.g4->coords[0] = get_i16(bytes, &p); - prm.g4->coords[1] = get_i16(bytes, &p); - prm.g4->coords[2] = get_i16(bytes, &p); - prm.g4->coords[3] = get_i16(bytes, &p); - prm.g4->color[0] = rgba_from_u32(get_u32(bytes, &p)); - prm.g4->color[1] = rgba_from_u32(get_u32(bytes, &p)); - prm.g4->color[2] = rgba_from_u32(get_u32(bytes, &p)); - prm.g4->color[3] = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_GT3: - prm.ptr = mem_bump(sizeof(GT3)); - prm.gt3->coords[0] = get_i16(bytes, &p); - prm.gt3->coords[1] = get_i16(bytes, &p); - prm.gt3->coords[2] = get_i16(bytes, &p); - - prm.gt3->texture = texture_from_list(tl, get_i16(bytes, &p)); - prm.gt3->cba = get_i16(bytes, &p); - prm.gt3->tsb = get_i16(bytes, &p); - prm.gt3->u0 = get_i8(bytes, &p); - prm.gt3->v0 = get_i8(bytes, &p); - prm.gt3->u1 = get_i8(bytes, &p); - prm.gt3->v1 = get_i8(bytes, &p); - prm.gt3->u2 = get_i8(bytes, &p); - prm.gt3->v2 = get_i8(bytes, &p); - prm.gt3->pad1 = get_i16(bytes, &p); - prm.gt3->color[0] = rgba_from_u32(get_u32(bytes, &p)); - prm.gt3->color[1] = rgba_from_u32(get_u32(bytes, &p)); - prm.gt3->color[2] = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_GT4: - prm.ptr = mem_bump(sizeof(GT4)); - prm.gt4->coords[0] = get_i16(bytes, &p); - prm.gt4->coords[1] = get_i16(bytes, &p); - prm.gt4->coords[2] = get_i16(bytes, &p); - prm.gt4->coords[3] = get_i16(bytes, &p); - - prm.gt4->texture = texture_from_list(tl, get_i16(bytes, &p)); - prm.gt4->cba = get_i16(bytes, &p); - prm.gt4->tsb = get_i16(bytes, &p); - prm.gt4->u0 = get_i8(bytes, &p); - prm.gt4->v0 = get_i8(bytes, &p); - prm.gt4->u1 = get_i8(bytes, &p); - prm.gt4->v1 = get_i8(bytes, &p); - prm.gt4->u2 = get_i8(bytes, &p); - prm.gt4->v2 = get_i8(bytes, &p); - prm.gt4->u3 = get_i8(bytes, &p); - prm.gt4->v3 = get_i8(bytes, &p); - prm.gt4->pad1 = get_i16(bytes, &p); - prm.gt4->color[0] = rgba_from_u32(get_u32(bytes, &p)); - prm.gt4->color[1] = rgba_from_u32(get_u32(bytes, &p)); - prm.gt4->color[2] = rgba_from_u32(get_u32(bytes, &p)); - prm.gt4->color[3] = rgba_from_u32(get_u32(bytes, &p)); - break; - - - case PRM_TYPE_LSF3: - prm.ptr = mem_bump(sizeof(LSF3)); - prm.lsf3->coords[0] = get_i16(bytes, &p); - prm.lsf3->coords[1] = get_i16(bytes, &p); - prm.lsf3->coords[2] = get_i16(bytes, &p); - prm.lsf3->normal = get_i16(bytes, &p); - prm.lsf3->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_LSF4: - prm.ptr = mem_bump(sizeof(LSF4)); - prm.lsf4->coords[0] = get_i16(bytes, &p); - prm.lsf4->coords[1] = get_i16(bytes, &p); - prm.lsf4->coords[2] = get_i16(bytes, &p); - prm.lsf4->coords[3] = get_i16(bytes, &p); - prm.lsf4->normal = get_i16(bytes, &p); - prm.lsf4->pad1 = get_i16(bytes, &p); - prm.lsf4->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_LSFT3: - prm.ptr = mem_bump(sizeof(LSFT3)); - prm.lsft3->coords[0] = get_i16(bytes, &p); - prm.lsft3->coords[1] = get_i16(bytes, &p); - prm.lsft3->coords[2] = get_i16(bytes, &p); - prm.lsft3->normal = get_i16(bytes, &p); - - prm.lsft3->texture = texture_from_list(tl, get_i16(bytes, &p)); - prm.lsft3->cba = get_i16(bytes, &p); - prm.lsft3->tsb = get_i16(bytes, &p); - prm.lsft3->u0 = get_i8(bytes, &p); - prm.lsft3->v0 = get_i8(bytes, &p); - prm.lsft3->u1 = get_i8(bytes, &p); - prm.lsft3->v1 = get_i8(bytes, &p); - prm.lsft3->u2 = get_i8(bytes, &p); - prm.lsft3->v2 = get_i8(bytes, &p); - prm.lsft3->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_LSFT4: - prm.ptr = mem_bump(sizeof(LSFT4)); - prm.lsft4->coords[0] = get_i16(bytes, &p); - prm.lsft4->coords[1] = get_i16(bytes, &p); - prm.lsft4->coords[2] = get_i16(bytes, &p); - prm.lsft4->coords[3] = get_i16(bytes, &p); - prm.lsft4->normal = get_i16(bytes, &p); - - prm.lsft4->texture = texture_from_list(tl, get_i16(bytes, &p)); - prm.lsft4->cba = get_i16(bytes, &p); - prm.lsft4->tsb = get_i16(bytes, &p); - prm.lsft4->u0 = get_i8(bytes, &p); - prm.lsft4->v0 = get_i8(bytes, &p); - prm.lsft4->u1 = get_i8(bytes, &p); - prm.lsft4->v1 = get_i8(bytes, &p); - prm.lsft4->u2 = get_i8(bytes, &p); - prm.lsft4->v2 = get_i8(bytes, &p); - prm.lsft4->u3 = get_i8(bytes, &p); - prm.lsft4->v3 = get_i8(bytes, &p); - prm.lsft4->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_LSG3: - prm.ptr = mem_bump(sizeof(LSG3)); - prm.lsg3->coords[0] = get_i16(bytes, &p); - prm.lsg3->coords[1] = get_i16(bytes, &p); - prm.lsg3->coords[2] = get_i16(bytes, &p); - prm.lsg3->normals[0] = get_i16(bytes, &p); - prm.lsg3->normals[1] = get_i16(bytes, &p); - prm.lsg3->normals[2] = get_i16(bytes, &p); - prm.lsg3->color[0] = rgba_from_u32(get_u32(bytes, &p)); - prm.lsg3->color[1] = rgba_from_u32(get_u32(bytes, &p)); - prm.lsg3->color[2] = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_LSG4: - prm.ptr = mem_bump(sizeof(LSG4)); - prm.lsg4->coords[0] = get_i16(bytes, &p); - prm.lsg4->coords[1] = get_i16(bytes, &p); - prm.lsg4->coords[2] = get_i16(bytes, &p); - prm.lsg4->coords[3] = get_i16(bytes, &p); - prm.lsg4->normals[0] = get_i16(bytes, &p); - prm.lsg4->normals[1] = get_i16(bytes, &p); - prm.lsg4->normals[2] = get_i16(bytes, &p); - prm.lsg4->normals[3] = get_i16(bytes, &p); - prm.lsg4->color[0] = rgba_from_u32(get_u32(bytes, &p)); - prm.lsg4->color[1] = rgba_from_u32(get_u32(bytes, &p)); - prm.lsg4->color[2] = rgba_from_u32(get_u32(bytes, &p)); - prm.lsg4->color[3] = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_LSGT3: - prm.ptr = mem_bump(sizeof(LSGT3)); - prm.lsgt3->coords[0] = get_i16(bytes, &p); - prm.lsgt3->coords[1] = get_i16(bytes, &p); - prm.lsgt3->coords[2] = get_i16(bytes, &p); - prm.lsgt3->normals[0] = get_i16(bytes, &p); - prm.lsgt3->normals[1] = get_i16(bytes, &p); - prm.lsgt3->normals[2] = get_i16(bytes, &p); - - prm.lsgt3->texture = texture_from_list(tl, get_i16(bytes, &p)); - prm.lsgt3->cba = get_i16(bytes, &p); - prm.lsgt3->tsb = get_i16(bytes, &p); - prm.lsgt3->u0 = get_i8(bytes, &p); - prm.lsgt3->v0 = get_i8(bytes, &p); - prm.lsgt3->u1 = get_i8(bytes, &p); - prm.lsgt3->v1 = get_i8(bytes, &p); - prm.lsgt3->u2 = get_i8(bytes, &p); - prm.lsgt3->v2 = get_i8(bytes, &p); - prm.lsgt3->color[0] = rgba_from_u32(get_u32(bytes, &p)); - prm.lsgt3->color[1] = rgba_from_u32(get_u32(bytes, &p)); - prm.lsgt3->color[2] = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_LSGT4: - prm.ptr = mem_bump(sizeof(LSGT4)); - prm.lsgt4->coords[0] = get_i16(bytes, &p); - prm.lsgt4->coords[1] = get_i16(bytes, &p); - prm.lsgt4->coords[2] = get_i16(bytes, &p); - prm.lsgt4->coords[3] = get_i16(bytes, &p); - prm.lsgt4->normals[0] = get_i16(bytes, &p); - prm.lsgt4->normals[1] = get_i16(bytes, &p); - prm.lsgt4->normals[2] = get_i16(bytes, &p); - prm.lsgt4->normals[3] = get_i16(bytes, &p); - - prm.lsgt4->texture = texture_from_list(tl, get_i16(bytes, &p)); - prm.lsgt4->cba = get_i16(bytes, &p); - prm.lsgt4->tsb = get_i16(bytes, &p); - prm.lsgt4->u0 = get_i8(bytes, &p); - prm.lsgt4->v0 = get_i8(bytes, &p); - prm.lsgt4->u1 = get_i8(bytes, &p); - prm.lsgt4->v1 = get_i8(bytes, &p); - prm.lsgt4->u2 = get_i8(bytes, &p); - prm.lsgt4->v2 = get_i8(bytes, &p); - prm.lsgt4->pad1 = get_i16(bytes, &p); - prm.lsgt4->color[0] = rgba_from_u32(get_u32(bytes, &p)); - prm.lsgt4->color[1] = rgba_from_u32(get_u32(bytes, &p)); - prm.lsgt4->color[2] = rgba_from_u32(get_u32(bytes, &p)); - prm.lsgt4->color[3] = rgba_from_u32(get_u32(bytes, &p)); - break; - - - case PRM_TYPE_TSPR: - case PRM_TYPE_BSPR: - prm.ptr = mem_bump(sizeof(SPR)); - prm.spr->coord = get_i16(bytes, &p); - prm.spr->width = get_i16(bytes, &p); - prm.spr->height = get_i16(bytes, &p); - prm.spr->texture = texture_from_list(tl, get_i16(bytes, &p)); - prm.spr->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_SPLINE: - prm.ptr = mem_bump(sizeof(Spline)); - prm.spline->control1.x = get_i32(bytes, &p); - prm.spline->control1.y = get_i32(bytes, &p); - prm.spline->control1.z = get_i32(bytes, &p); - p += 4; // padding - prm.spline->position.x = get_i32(bytes, &p); - prm.spline->position.y = get_i32(bytes, &p); - prm.spline->position.z = get_i32(bytes, &p); - p += 4; // padding - prm.spline->control2.x = get_i32(bytes, &p); - prm.spline->control2.y = get_i32(bytes, &p); - prm.spline->control2.z = get_i32(bytes, &p); - p += 4; // padding - prm.spline->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - case PRM_TYPE_POINT_LIGHT: - prm.ptr = mem_bump(sizeof(PointLight)); - prm.pointLight->position.x = get_i32(bytes, &p); - prm.pointLight->position.y = get_i32(bytes, &p); - prm.pointLight->position.z = get_i32(bytes, &p); - p += 4; // padding - prm.pointLight->color = rgba_from_u32(get_u32(bytes, &p)); - prm.pointLight->startFalloff = get_i16(bytes, &p); - prm.pointLight->endFalloff = get_i16(bytes, &p); - break; - - case PRM_TYPE_SPOT_LIGHT: - prm.ptr = mem_bump(sizeof(SpotLight)); - prm.spotLight->position.x = get_i32(bytes, &p); - prm.spotLight->position.y = get_i32(bytes, &p); - prm.spotLight->position.z = get_i32(bytes, &p); - p += 4; // padding - prm.spotLight->direction.x = get_i16(bytes, &p); - prm.spotLight->direction.y = get_i16(bytes, &p); - prm.spotLight->direction.z = get_i16(bytes, &p); - p += 2; // padding - prm.spotLight->color = rgba_from_u32(get_u32(bytes, &p)); - prm.spotLight->startFalloff = get_i16(bytes, &p); - prm.spotLight->endFalloff = get_i16(bytes, &p); - prm.spotLight->coneAngle = get_i16(bytes, &p); - prm.spotLight->spreadAngle = get_i16(bytes, &p); - break; - - case PRM_TYPE_INFINITE_LIGHT: - prm.ptr = mem_bump(sizeof(InfiniteLight)); - prm.infiniteLight->direction.x = get_i16(bytes, &p); - prm.infiniteLight->direction.y = get_i16(bytes, &p); - prm.infiniteLight->direction.z = get_i16(bytes, &p); - p += 2; // padding - prm.infiniteLight->color = rgba_from_u32(get_u32(bytes, &p)); - break; - - - default: - die("bad primitive type %x \n", prm_type); - } // switch - - prm.f3->type = prm_type; - prm.f3->flag = prm_flag; - } // each prim - } // each object - - mem_temp_free(bytes); - return objectList; -} - - -void object_draw(Object *object, mat4_t *mat) { - vec3_t *vertex = object->vertices; - - Prm poly = {.primitive = object->primitives}; - int primitives_len = object->primitives_len; - - render_set_model_mat(mat); - - // TODO: check for PRM_SINGLE_SIDED - - for (int i = 0; i < primitives_len; i++) { - int coord0; - int coord1; - int coord2; - int coord3; - switch (poly.primitive->type) { - case PRM_TYPE_GT3: - coord0 = poly.gt3->coords[0]; - coord1 = poly.gt3->coords[1]; - coord2 = poly.gt3->coords[2]; - - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .uv = {poly.gt3->u2, poly.gt3->v2}, - .color = poly.gt3->color[2] - }, - { - .pos = vertex[coord1], - .uv = {poly.gt3->u1, poly.gt3->v1}, - .color = poly.gt3->color[1] - }, - { - .pos = vertex[coord0], - .uv = {poly.gt3->u0, poly.gt3->v0}, - .color = poly.gt3->color[0] - }, - } - }, poly.gt3->texture); - - poly.gt3 += 1; - break; - - case PRM_TYPE_GT4: - coord0 = poly.gt4->coords[0]; - coord1 = poly.gt4->coords[1]; - coord2 = poly.gt4->coords[2]; - coord3 = poly.gt4->coords[3]; - - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .uv = {poly.gt4->u2, poly.gt4->v2}, - .color = poly.gt4->color[2] - }, - { - .pos = vertex[coord1], - .uv = {poly.gt4->u1, poly.gt4->v1}, - .color = poly.gt4->color[1] - }, - { - .pos = vertex[coord0], - .uv = {poly.gt4->u0, poly.gt4->v0}, - .color = poly.gt4->color[0] - }, - } - }, poly.gt4->texture); - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .uv = {poly.gt4->u2, poly.gt4->v2}, - .color = poly.gt4->color[2] - }, - { - .pos = vertex[coord3], - .uv = {poly.gt4->u3, poly.gt4->v3}, - .color = poly.gt4->color[3] - }, - { - .pos = vertex[coord1], - .uv = {poly.gt4->u1, poly.gt4->v1}, - .color = poly.gt4->color[1] - }, - } - }, poly.gt4->texture); - - poly.gt4 += 1; - break; - - case PRM_TYPE_FT3: - coord0 = poly.ft3->coords[0]; - coord1 = poly.ft3->coords[1]; - coord2 = poly.ft3->coords[2]; - - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .uv = {poly.ft3->u2, poly.ft3->v2}, - .color = poly.ft3->color - }, - { - .pos = vertex[coord1], - .uv = {poly.ft3->u1, poly.ft3->v1}, - .color = poly.ft3->color - }, - { - .pos = vertex[coord0], - .uv = {poly.ft3->u0, poly.ft3->v0}, - .color = poly.ft3->color - }, - } - }, poly.ft3->texture); - - poly.ft3 += 1; - break; - - case PRM_TYPE_FT4: - coord0 = poly.ft4->coords[0]; - coord1 = poly.ft4->coords[1]; - coord2 = poly.ft4->coords[2]; - coord3 = poly.ft4->coords[3]; - - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .uv = {poly.ft4->u2, poly.ft4->v2}, - .color = poly.ft4->color - }, - { - .pos = vertex[coord1], - .uv = {poly.ft4->u1, poly.ft4->v1}, - .color = poly.ft4->color - }, - { - .pos = vertex[coord0], - .uv = {poly.ft4->u0, poly.ft4->v0}, - .color = poly.ft4->color - }, - } - }, poly.ft4->texture); - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .uv = {poly.ft4->u2, poly.ft4->v2}, - .color = poly.ft4->color - }, - { - .pos = vertex[coord3], - .uv = {poly.ft4->u3, poly.ft4->v3}, - .color = poly.ft4->color - }, - { - .pos = vertex[coord1], - .uv = {poly.ft4->u1, poly.ft4->v1}, - .color = poly.ft4->color - }, - } - }, poly.ft4->texture); - - poly.ft4 += 1; - break; - - case PRM_TYPE_G3: - coord0 = poly.g3->coords[0]; - coord1 = poly.g3->coords[1]; - coord2 = poly.g3->coords[2]; - - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .color = poly.g3->color[2] - }, - { - .pos = vertex[coord1], - .color = poly.g3->color[1] - }, - { - .pos = vertex[coord0], - .color = poly.g3->color[0] - }, - } - }, RENDER_NO_TEXTURE); - - poly.g3 += 1; - break; - - case PRM_TYPE_G4: - coord0 = poly.g4->coords[0]; - coord1 = poly.g4->coords[1]; - coord2 = poly.g4->coords[2]; - coord3 = poly.g4->coords[3]; - - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .color = poly.g4->color[2] - }, - { - .pos = vertex[coord1], - .color = poly.g4->color[1] - }, - { - .pos = vertex[coord0], - .color = poly.g4->color[0] - }, - } - }, RENDER_NO_TEXTURE); - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .color = poly.g4->color[2] - }, - { - .pos = vertex[coord3], - .color = poly.g4->color[3] - }, - { - .pos = vertex[coord1], - .color = poly.g4->color[1] - }, - } - }, RENDER_NO_TEXTURE); - - poly.g4 += 1; - break; - - case PRM_TYPE_F3: - coord0 = poly.f3->coords[0]; - coord1 = poly.f3->coords[1]; - coord2 = poly.f3->coords[2]; - - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .color = poly.f3->color - }, - { - .pos = vertex[coord1], - .color = poly.f3->color - }, - { - .pos = vertex[coord0], - .color = poly.f3->color - }, - } - }, RENDER_NO_TEXTURE); - - poly.f3 += 1; - break; - - case PRM_TYPE_F4: - coord0 = poly.f4->coords[0]; - coord1 = poly.f4->coords[1]; - coord2 = poly.f4->coords[2]; - coord3 = poly.f4->coords[3]; - - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .color = poly.f4->color - }, - { - .pos = vertex[coord1], - .color = poly.f4->color - }, - { - .pos = vertex[coord0], - .color = poly.f4->color - }, - } - }, RENDER_NO_TEXTURE); - render_push_tris((tris_t) { - .vertices = { - { - .pos = vertex[coord2], - .color = poly.f4->color - }, - { - .pos = vertex[coord3], - .color = poly.f4->color - }, - { - .pos = vertex[coord1], - .color = poly.f4->color - }, - } - }, RENDER_NO_TEXTURE); - - poly.f4 += 1; - break; - - case PRM_TYPE_TSPR: - case PRM_TYPE_BSPR: - coord0 = poly.spr->coord; - - render_push_sprite( - vec3( - vertex[coord0].x, - vertex[coord0].y + ((poly.primitive->type == PRM_TYPE_TSPR ? poly.spr->height : -poly.spr->height) >> 1), - vertex[coord0].z - ), - vec2i(poly.spr->width, poly.spr->height), - poly.spr->color, - poly.spr->texture - ); - - poly.spr += 1; - break; - - default: - break; - - } - } -} +#include "../types.h" +#include "../mem.h" +#include "../render.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 "hud.h" +#include "object.h" + +Object *objects_load(char *name, texture_list_t tl) { + uint32_t length = 0; + uint8_t *bytes = platform_load_asset(name, &length); + if (!bytes) { + die("Failed to load file %s\n", name); + } + printf("load: %s\n", name); + + Object *objectList = mem_mark(); + Object *prevObject = NULL; + uint32_t p = 0; + + while (p < length) { + Object *object = mem_bump(sizeof(Object)); + if (prevObject) { + prevObject->next = object; + } + prevObject = object; + + for (int i = 0; i < 16; i++) { + object->name[i] = get_i8(bytes, &p); + } + + object->mat = mat4_identity(); + object->vertices_len = get_i16(bytes, &p); p += 2; + object->vertices = NULL; get_i32(bytes, &p); + object->normals_len = get_i16(bytes, &p); p += 2; + object->normals = NULL; get_i32(bytes, &p); + object->primitives_len = get_i16(bytes, &p); p += 2; + object->primitives = NULL; get_i32(bytes, &p); + get_i32(bytes, &p); + get_i32(bytes, &p); + get_i32(bytes, &p); // Skeleton ref + object->extent = get_i32(bytes, &p); + object->flags = get_i16(bytes, &p); p += 2; + object->next = NULL; get_i32(bytes, &p); + + p += 3 * 3 * 2; // relative rot matrix + p += 2; // padding + + object->origin.x = get_i32(bytes, &p); + object->origin.y = get_i32(bytes, &p); + object->origin.z = get_i32(bytes, &p); + + p += 3 * 3 * 2; // absolute rot matrix + p += 2; // padding + p += 3 * 4; // absolute translation matrix + p += 2; // skeleton update flag + p += 2; // padding + p += 4; // skeleton super + p += 4; // skeleton sub + p += 4; // skeleton next + + object->radius = 0; + object->vertices = mem_bump(object->vertices_len * sizeof(vec3_t)); + for (int i = 0; i < object->vertices_len; i++) { + object->vertices[i].x = get_i16(bytes, &p); + object->vertices[i].y = get_i16(bytes, &p); + object->vertices[i].z = get_i16(bytes, &p); + p += 2; // padding + + if (fabsf(object->vertices[i].x) > object->radius) { + object->radius = fabsf(object->vertices[i].x); + } + if (fabsf(object->vertices[i].y) > object->radius) { + object->radius = fabsf(object->vertices[i].y); + } + if (fabsf(object->vertices[i].z) > object->radius) { + object->radius = fabsf(object->vertices[i].z); + } + } + + + + object->normals = mem_bump(object->normals_len * sizeof(vec3_t)); + for (int i = 0; i < object->normals_len; i++) { + object->normals[i].x = get_i16(bytes, &p); + object->normals[i].y = get_i16(bytes, &p); + object->normals[i].z = get_i16(bytes, &p); + p += 2; // padding + } + + object->primitives = mem_mark(); + for (int i = 0; i < object->primitives_len; i++) { + Prm prm; + int16_t prm_type = get_i16(bytes, &p); + int16_t prm_flag = get_i16(bytes, &p); + + switch (prm_type) { + case PRM_TYPE_F3: + prm.ptr = mem_bump(sizeof(F3)); + prm.f3->coords[0] = get_i16(bytes, &p); + prm.f3->coords[1] = get_i16(bytes, &p); + prm.f3->coords[2] = get_i16(bytes, &p); + prm.f3->pad1 = get_i16(bytes, &p); + prm.f3->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_F4: + prm.ptr = mem_bump(sizeof(F4)); + prm.f4->coords[0] = get_i16(bytes, &p); + prm.f4->coords[1] = get_i16(bytes, &p); + prm.f4->coords[2] = get_i16(bytes, &p); + prm.f4->coords[3] = get_i16(bytes, &p); + prm.f4->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_FT3: + prm.ptr = mem_bump(sizeof(FT3)); + prm.ft3->coords[0] = get_i16(bytes, &p); + prm.ft3->coords[1] = get_i16(bytes, &p); + prm.ft3->coords[2] = get_i16(bytes, &p); + + prm.ft3->texture = texture_from_list(tl, get_i16(bytes, &p)); + prm.ft3->cba = get_i16(bytes, &p); + prm.ft3->tsb = get_i16(bytes, &p); + prm.ft3->u0 = get_i8(bytes, &p); + prm.ft3->v0 = get_i8(bytes, &p); + prm.ft3->u1 = get_i8(bytes, &p); + prm.ft3->v1 = get_i8(bytes, &p); + prm.ft3->u2 = get_i8(bytes, &p); + prm.ft3->v2 = get_i8(bytes, &p); + + prm.ft3->pad1 = get_i16(bytes, &p); + prm.ft3->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_FT4: + prm.ptr = mem_bump(sizeof(FT4)); + prm.ft4->coords[0] = get_i16(bytes, &p); + prm.ft4->coords[1] = get_i16(bytes, &p); + prm.ft4->coords[2] = get_i16(bytes, &p); + prm.ft4->coords[3] = get_i16(bytes, &p); + + prm.ft4->texture = texture_from_list(tl, get_i16(bytes, &p)); + prm.ft4->cba = get_i16(bytes, &p); + prm.ft4->tsb = get_i16(bytes, &p); + prm.ft4->u0 = get_i8(bytes, &p); + prm.ft4->v0 = get_i8(bytes, &p); + prm.ft4->u1 = get_i8(bytes, &p); + prm.ft4->v1 = get_i8(bytes, &p); + prm.ft4->u2 = get_i8(bytes, &p); + prm.ft4->v2 = get_i8(bytes, &p); + prm.ft4->u3 = get_i8(bytes, &p); + prm.ft4->v3 = get_i8(bytes, &p); + prm.ft4->pad1 = get_i16(bytes, &p); + prm.ft4->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_G3: + prm.ptr = mem_bump(sizeof(G3)); + prm.g3->coords[0] = get_i16(bytes, &p); + prm.g3->coords[1] = get_i16(bytes, &p); + prm.g3->coords[2] = get_i16(bytes, &p); + prm.g3->pad1 = get_i16(bytes, &p); + prm.g3->color[0] = rgba_from_u32(get_u32(bytes, &p)); + prm.g3->color[1] = rgba_from_u32(get_u32(bytes, &p)); + prm.g3->color[2] = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_G4: + prm.ptr = mem_bump(sizeof(G4)); + prm.g4->coords[0] = get_i16(bytes, &p); + prm.g4->coords[1] = get_i16(bytes, &p); + prm.g4->coords[2] = get_i16(bytes, &p); + prm.g4->coords[3] = get_i16(bytes, &p); + prm.g4->color[0] = rgba_from_u32(get_u32(bytes, &p)); + prm.g4->color[1] = rgba_from_u32(get_u32(bytes, &p)); + prm.g4->color[2] = rgba_from_u32(get_u32(bytes, &p)); + prm.g4->color[3] = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_GT3: + prm.ptr = mem_bump(sizeof(GT3)); + prm.gt3->coords[0] = get_i16(bytes, &p); + prm.gt3->coords[1] = get_i16(bytes, &p); + prm.gt3->coords[2] = get_i16(bytes, &p); + + prm.gt3->texture = texture_from_list(tl, get_i16(bytes, &p)); + prm.gt3->cba = get_i16(bytes, &p); + prm.gt3->tsb = get_i16(bytes, &p); + prm.gt3->u0 = get_i8(bytes, &p); + prm.gt3->v0 = get_i8(bytes, &p); + prm.gt3->u1 = get_i8(bytes, &p); + prm.gt3->v1 = get_i8(bytes, &p); + prm.gt3->u2 = get_i8(bytes, &p); + prm.gt3->v2 = get_i8(bytes, &p); + prm.gt3->pad1 = get_i16(bytes, &p); + prm.gt3->color[0] = rgba_from_u32(get_u32(bytes, &p)); + prm.gt3->color[1] = rgba_from_u32(get_u32(bytes, &p)); + prm.gt3->color[2] = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_GT4: + prm.ptr = mem_bump(sizeof(GT4)); + prm.gt4->coords[0] = get_i16(bytes, &p); + prm.gt4->coords[1] = get_i16(bytes, &p); + prm.gt4->coords[2] = get_i16(bytes, &p); + prm.gt4->coords[3] = get_i16(bytes, &p); + + prm.gt4->texture = texture_from_list(tl, get_i16(bytes, &p)); + prm.gt4->cba = get_i16(bytes, &p); + prm.gt4->tsb = get_i16(bytes, &p); + prm.gt4->u0 = get_i8(bytes, &p); + prm.gt4->v0 = get_i8(bytes, &p); + prm.gt4->u1 = get_i8(bytes, &p); + prm.gt4->v1 = get_i8(bytes, &p); + prm.gt4->u2 = get_i8(bytes, &p); + prm.gt4->v2 = get_i8(bytes, &p); + prm.gt4->u3 = get_i8(bytes, &p); + prm.gt4->v3 = get_i8(bytes, &p); + prm.gt4->pad1 = get_i16(bytes, &p); + prm.gt4->color[0] = rgba_from_u32(get_u32(bytes, &p)); + prm.gt4->color[1] = rgba_from_u32(get_u32(bytes, &p)); + prm.gt4->color[2] = rgba_from_u32(get_u32(bytes, &p)); + prm.gt4->color[3] = rgba_from_u32(get_u32(bytes, &p)); + break; + + + case PRM_TYPE_LSF3: + prm.ptr = mem_bump(sizeof(LSF3)); + prm.lsf3->coords[0] = get_i16(bytes, &p); + prm.lsf3->coords[1] = get_i16(bytes, &p); + prm.lsf3->coords[2] = get_i16(bytes, &p); + prm.lsf3->normal = get_i16(bytes, &p); + prm.lsf3->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_LSF4: + prm.ptr = mem_bump(sizeof(LSF4)); + prm.lsf4->coords[0] = get_i16(bytes, &p); + prm.lsf4->coords[1] = get_i16(bytes, &p); + prm.lsf4->coords[2] = get_i16(bytes, &p); + prm.lsf4->coords[3] = get_i16(bytes, &p); + prm.lsf4->normal = get_i16(bytes, &p); + prm.lsf4->pad1 = get_i16(bytes, &p); + prm.lsf4->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_LSFT3: + prm.ptr = mem_bump(sizeof(LSFT3)); + prm.lsft3->coords[0] = get_i16(bytes, &p); + prm.lsft3->coords[1] = get_i16(bytes, &p); + prm.lsft3->coords[2] = get_i16(bytes, &p); + prm.lsft3->normal = get_i16(bytes, &p); + + prm.lsft3->texture = texture_from_list(tl, get_i16(bytes, &p)); + prm.lsft3->cba = get_i16(bytes, &p); + prm.lsft3->tsb = get_i16(bytes, &p); + prm.lsft3->u0 = get_i8(bytes, &p); + prm.lsft3->v0 = get_i8(bytes, &p); + prm.lsft3->u1 = get_i8(bytes, &p); + prm.lsft3->v1 = get_i8(bytes, &p); + prm.lsft3->u2 = get_i8(bytes, &p); + prm.lsft3->v2 = get_i8(bytes, &p); + prm.lsft3->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_LSFT4: + prm.ptr = mem_bump(sizeof(LSFT4)); + prm.lsft4->coords[0] = get_i16(bytes, &p); + prm.lsft4->coords[1] = get_i16(bytes, &p); + prm.lsft4->coords[2] = get_i16(bytes, &p); + prm.lsft4->coords[3] = get_i16(bytes, &p); + prm.lsft4->normal = get_i16(bytes, &p); + + prm.lsft4->texture = texture_from_list(tl, get_i16(bytes, &p)); + prm.lsft4->cba = get_i16(bytes, &p); + prm.lsft4->tsb = get_i16(bytes, &p); + prm.lsft4->u0 = get_i8(bytes, &p); + prm.lsft4->v0 = get_i8(bytes, &p); + prm.lsft4->u1 = get_i8(bytes, &p); + prm.lsft4->v1 = get_i8(bytes, &p); + prm.lsft4->u2 = get_i8(bytes, &p); + prm.lsft4->v2 = get_i8(bytes, &p); + prm.lsft4->u3 = get_i8(bytes, &p); + prm.lsft4->v3 = get_i8(bytes, &p); + prm.lsft4->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_LSG3: + prm.ptr = mem_bump(sizeof(LSG3)); + prm.lsg3->coords[0] = get_i16(bytes, &p); + prm.lsg3->coords[1] = get_i16(bytes, &p); + prm.lsg3->coords[2] = get_i16(bytes, &p); + prm.lsg3->normals[0] = get_i16(bytes, &p); + prm.lsg3->normals[1] = get_i16(bytes, &p); + prm.lsg3->normals[2] = get_i16(bytes, &p); + prm.lsg3->color[0] = rgba_from_u32(get_u32(bytes, &p)); + prm.lsg3->color[1] = rgba_from_u32(get_u32(bytes, &p)); + prm.lsg3->color[2] = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_LSG4: + prm.ptr = mem_bump(sizeof(LSG4)); + prm.lsg4->coords[0] = get_i16(bytes, &p); + prm.lsg4->coords[1] = get_i16(bytes, &p); + prm.lsg4->coords[2] = get_i16(bytes, &p); + prm.lsg4->coords[3] = get_i16(bytes, &p); + prm.lsg4->normals[0] = get_i16(bytes, &p); + prm.lsg4->normals[1] = get_i16(bytes, &p); + prm.lsg4->normals[2] = get_i16(bytes, &p); + prm.lsg4->normals[3] = get_i16(bytes, &p); + prm.lsg4->color[0] = rgba_from_u32(get_u32(bytes, &p)); + prm.lsg4->color[1] = rgba_from_u32(get_u32(bytes, &p)); + prm.lsg4->color[2] = rgba_from_u32(get_u32(bytes, &p)); + prm.lsg4->color[3] = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_LSGT3: + prm.ptr = mem_bump(sizeof(LSGT3)); + prm.lsgt3->coords[0] = get_i16(bytes, &p); + prm.lsgt3->coords[1] = get_i16(bytes, &p); + prm.lsgt3->coords[2] = get_i16(bytes, &p); + prm.lsgt3->normals[0] = get_i16(bytes, &p); + prm.lsgt3->normals[1] = get_i16(bytes, &p); + prm.lsgt3->normals[2] = get_i16(bytes, &p); + + prm.lsgt3->texture = texture_from_list(tl, get_i16(bytes, &p)); + prm.lsgt3->cba = get_i16(bytes, &p); + prm.lsgt3->tsb = get_i16(bytes, &p); + prm.lsgt3->u0 = get_i8(bytes, &p); + prm.lsgt3->v0 = get_i8(bytes, &p); + prm.lsgt3->u1 = get_i8(bytes, &p); + prm.lsgt3->v1 = get_i8(bytes, &p); + prm.lsgt3->u2 = get_i8(bytes, &p); + prm.lsgt3->v2 = get_i8(bytes, &p); + prm.lsgt3->color[0] = rgba_from_u32(get_u32(bytes, &p)); + prm.lsgt3->color[1] = rgba_from_u32(get_u32(bytes, &p)); + prm.lsgt3->color[2] = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_LSGT4: + prm.ptr = mem_bump(sizeof(LSGT4)); + prm.lsgt4->coords[0] = get_i16(bytes, &p); + prm.lsgt4->coords[1] = get_i16(bytes, &p); + prm.lsgt4->coords[2] = get_i16(bytes, &p); + prm.lsgt4->coords[3] = get_i16(bytes, &p); + prm.lsgt4->normals[0] = get_i16(bytes, &p); + prm.lsgt4->normals[1] = get_i16(bytes, &p); + prm.lsgt4->normals[2] = get_i16(bytes, &p); + prm.lsgt4->normals[3] = get_i16(bytes, &p); + + prm.lsgt4->texture = texture_from_list(tl, get_i16(bytes, &p)); + prm.lsgt4->cba = get_i16(bytes, &p); + prm.lsgt4->tsb = get_i16(bytes, &p); + prm.lsgt4->u0 = get_i8(bytes, &p); + prm.lsgt4->v0 = get_i8(bytes, &p); + prm.lsgt4->u1 = get_i8(bytes, &p); + prm.lsgt4->v1 = get_i8(bytes, &p); + prm.lsgt4->u2 = get_i8(bytes, &p); + prm.lsgt4->v2 = get_i8(bytes, &p); + prm.lsgt4->pad1 = get_i16(bytes, &p); + prm.lsgt4->color[0] = rgba_from_u32(get_u32(bytes, &p)); + prm.lsgt4->color[1] = rgba_from_u32(get_u32(bytes, &p)); + prm.lsgt4->color[2] = rgba_from_u32(get_u32(bytes, &p)); + prm.lsgt4->color[3] = rgba_from_u32(get_u32(bytes, &p)); + break; + + + case PRM_TYPE_TSPR: + case PRM_TYPE_BSPR: + prm.ptr = mem_bump(sizeof(SPR)); + prm.spr->coord = get_i16(bytes, &p); + prm.spr->width = get_i16(bytes, &p); + prm.spr->height = get_i16(bytes, &p); + prm.spr->texture = texture_from_list(tl, get_i16(bytes, &p)); + prm.spr->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_SPLINE: + prm.ptr = mem_bump(sizeof(Spline)); + prm.spline->control1.x = get_i32(bytes, &p); + prm.spline->control1.y = get_i32(bytes, &p); + prm.spline->control1.z = get_i32(bytes, &p); + p += 4; // padding + prm.spline->position.x = get_i32(bytes, &p); + prm.spline->position.y = get_i32(bytes, &p); + prm.spline->position.z = get_i32(bytes, &p); + p += 4; // padding + prm.spline->control2.x = get_i32(bytes, &p); + prm.spline->control2.y = get_i32(bytes, &p); + prm.spline->control2.z = get_i32(bytes, &p); + p += 4; // padding + prm.spline->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + case PRM_TYPE_POINT_LIGHT: + prm.ptr = mem_bump(sizeof(PointLight)); + prm.pointLight->position.x = get_i32(bytes, &p); + prm.pointLight->position.y = get_i32(bytes, &p); + prm.pointLight->position.z = get_i32(bytes, &p); + p += 4; // padding + prm.pointLight->color = rgba_from_u32(get_u32(bytes, &p)); + prm.pointLight->startFalloff = get_i16(bytes, &p); + prm.pointLight->endFalloff = get_i16(bytes, &p); + break; + + case PRM_TYPE_SPOT_LIGHT: + prm.ptr = mem_bump(sizeof(SpotLight)); + prm.spotLight->position.x = get_i32(bytes, &p); + prm.spotLight->position.y = get_i32(bytes, &p); + prm.spotLight->position.z = get_i32(bytes, &p); + p += 4; // padding + prm.spotLight->direction.x = get_i16(bytes, &p); + prm.spotLight->direction.y = get_i16(bytes, &p); + prm.spotLight->direction.z = get_i16(bytes, &p); + p += 2; // padding + prm.spotLight->color = rgba_from_u32(get_u32(bytes, &p)); + prm.spotLight->startFalloff = get_i16(bytes, &p); + prm.spotLight->endFalloff = get_i16(bytes, &p); + prm.spotLight->coneAngle = get_i16(bytes, &p); + prm.spotLight->spreadAngle = get_i16(bytes, &p); + break; + + case PRM_TYPE_INFINITE_LIGHT: + prm.ptr = mem_bump(sizeof(InfiniteLight)); + prm.infiniteLight->direction.x = get_i16(bytes, &p); + prm.infiniteLight->direction.y = get_i16(bytes, &p); + prm.infiniteLight->direction.z = get_i16(bytes, &p); + p += 2; // padding + prm.infiniteLight->color = rgba_from_u32(get_u32(bytes, &p)); + break; + + + default: + die("bad primitive type %x \n", prm_type); + } // switch + + prm.f3->type = prm_type; + prm.f3->flag = prm_flag; + } // each prim + } // each object + + mem_temp_free(bytes); + return objectList; +} + + +void object_draw(Object *object, mat4_t *mat) { + vec3_t *vertex = object->vertices; + + Prm poly = {.primitive = object->primitives}; + int primitives_len = object->primitives_len; + + render_set_model_mat(mat); + + // TODO: check for PRM_SINGLE_SIDED + + for (int i = 0; i < primitives_len; i++) { + int coord0; + int coord1; + int coord2; + int coord3; + switch (poly.primitive->type) { + case PRM_TYPE_GT3: + coord0 = poly.gt3->coords[0]; + coord1 = poly.gt3->coords[1]; + coord2 = poly.gt3->coords[2]; + + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .uv = {poly.gt3->u2, poly.gt3->v2}, + .color = poly.gt3->color[2] + }, + { + .pos = vertex[coord1], + .uv = {poly.gt3->u1, poly.gt3->v1}, + .color = poly.gt3->color[1] + }, + { + .pos = vertex[coord0], + .uv = {poly.gt3->u0, poly.gt3->v0}, + .color = poly.gt3->color[0] + }, + } + }, poly.gt3->texture); + + poly.gt3 += 1; + break; + + case PRM_TYPE_GT4: + coord0 = poly.gt4->coords[0]; + coord1 = poly.gt4->coords[1]; + coord2 = poly.gt4->coords[2]; + coord3 = poly.gt4->coords[3]; + + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .uv = {poly.gt4->u2, poly.gt4->v2}, + .color = poly.gt4->color[2] + }, + { + .pos = vertex[coord1], + .uv = {poly.gt4->u1, poly.gt4->v1}, + .color = poly.gt4->color[1] + }, + { + .pos = vertex[coord0], + .uv = {poly.gt4->u0, poly.gt4->v0}, + .color = poly.gt4->color[0] + }, + } + }, poly.gt4->texture); + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .uv = {poly.gt4->u2, poly.gt4->v2}, + .color = poly.gt4->color[2] + }, + { + .pos = vertex[coord3], + .uv = {poly.gt4->u3, poly.gt4->v3}, + .color = poly.gt4->color[3] + }, + { + .pos = vertex[coord1], + .uv = {poly.gt4->u1, poly.gt4->v1}, + .color = poly.gt4->color[1] + }, + } + }, poly.gt4->texture); + + poly.gt4 += 1; + break; + + case PRM_TYPE_FT3: + coord0 = poly.ft3->coords[0]; + coord1 = poly.ft3->coords[1]; + coord2 = poly.ft3->coords[2]; + + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .uv = {poly.ft3->u2, poly.ft3->v2}, + .color = poly.ft3->color + }, + { + .pos = vertex[coord1], + .uv = {poly.ft3->u1, poly.ft3->v1}, + .color = poly.ft3->color + }, + { + .pos = vertex[coord0], + .uv = {poly.ft3->u0, poly.ft3->v0}, + .color = poly.ft3->color + }, + } + }, poly.ft3->texture); + + poly.ft3 += 1; + break; + + case PRM_TYPE_FT4: + coord0 = poly.ft4->coords[0]; + coord1 = poly.ft4->coords[1]; + coord2 = poly.ft4->coords[2]; + coord3 = poly.ft4->coords[3]; + + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .uv = {poly.ft4->u2, poly.ft4->v2}, + .color = poly.ft4->color + }, + { + .pos = vertex[coord1], + .uv = {poly.ft4->u1, poly.ft4->v1}, + .color = poly.ft4->color + }, + { + .pos = vertex[coord0], + .uv = {poly.ft4->u0, poly.ft4->v0}, + .color = poly.ft4->color + }, + } + }, poly.ft4->texture); + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .uv = {poly.ft4->u2, poly.ft4->v2}, + .color = poly.ft4->color + }, + { + .pos = vertex[coord3], + .uv = {poly.ft4->u3, poly.ft4->v3}, + .color = poly.ft4->color + }, + { + .pos = vertex[coord1], + .uv = {poly.ft4->u1, poly.ft4->v1}, + .color = poly.ft4->color + }, + } + }, poly.ft4->texture); + + poly.ft4 += 1; + break; + + case PRM_TYPE_G3: + coord0 = poly.g3->coords[0]; + coord1 = poly.g3->coords[1]; + coord2 = poly.g3->coords[2]; + + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .color = poly.g3->color[2] + }, + { + .pos = vertex[coord1], + .color = poly.g3->color[1] + }, + { + .pos = vertex[coord0], + .color = poly.g3->color[0] + }, + } + }, RENDER_NO_TEXTURE); + + poly.g3 += 1; + break; + + case PRM_TYPE_G4: + coord0 = poly.g4->coords[0]; + coord1 = poly.g4->coords[1]; + coord2 = poly.g4->coords[2]; + coord3 = poly.g4->coords[3]; + + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .color = poly.g4->color[2] + }, + { + .pos = vertex[coord1], + .color = poly.g4->color[1] + }, + { + .pos = vertex[coord0], + .color = poly.g4->color[0] + }, + } + }, RENDER_NO_TEXTURE); + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .color = poly.g4->color[2] + }, + { + .pos = vertex[coord3], + .color = poly.g4->color[3] + }, + { + .pos = vertex[coord1], + .color = poly.g4->color[1] + }, + } + }, RENDER_NO_TEXTURE); + + poly.g4 += 1; + break; + + case PRM_TYPE_F3: + coord0 = poly.f3->coords[0]; + coord1 = poly.f3->coords[1]; + coord2 = poly.f3->coords[2]; + + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .color = poly.f3->color + }, + { + .pos = vertex[coord1], + .color = poly.f3->color + }, + { + .pos = vertex[coord0], + .color = poly.f3->color + }, + } + }, RENDER_NO_TEXTURE); + + poly.f3 += 1; + break; + + case PRM_TYPE_F4: + coord0 = poly.f4->coords[0]; + coord1 = poly.f4->coords[1]; + coord2 = poly.f4->coords[2]; + coord3 = poly.f4->coords[3]; + + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .color = poly.f4->color + }, + { + .pos = vertex[coord1], + .color = poly.f4->color + }, + { + .pos = vertex[coord0], + .color = poly.f4->color + }, + } + }, RENDER_NO_TEXTURE); + render_push_tris((tris_t) { + .vertices = { + { + .pos = vertex[coord2], + .color = poly.f4->color + }, + { + .pos = vertex[coord3], + .color = poly.f4->color + }, + { + .pos = vertex[coord1], + .color = poly.f4->color + }, + } + }, RENDER_NO_TEXTURE); + + poly.f4 += 1; + break; + + case PRM_TYPE_TSPR: + case PRM_TYPE_BSPR: + coord0 = poly.spr->coord; + + render_push_sprite( + vec3( + vertex[coord0].x, + vertex[coord0].y + ((poly.primitive->type == PRM_TYPE_TSPR ? poly.spr->height : -poly.spr->height) >> 1), + vertex[coord0].z + ), + vec2i(poly.spr->width, poly.spr->height), + poly.spr->color, + poly.spr->texture + ); + + poly.spr += 1; + break; + + default: + break; + + } + } +} diff --git a/src/wipeout/object.h b/src/wipeout/object.h index 3e4912f..bb94414 100755 --- a/src/wipeout/object.h +++ b/src/wipeout/object.h @@ -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 \ No newline at end of file diff --git a/src/wipeout/race.c b/src/wipeout/race.c index 187c2ec..e3f0fac 100755 --- a/src/wipeout/race.c +++ b/src/wipeout/race.c @@ -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; +} diff --git a/src/wipeout/race.h b/src/wipeout/race.h index 65192b8..ca6c04e 100755 --- a/src/wipeout/race.h +++ b/src/wipeout/race.h @@ -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 diff --git a/src/wipeout/scene.c b/src/wipeout/scene.c index 3cce86c..a2da410 100755 --- a/src/wipeout/scene.c +++ b/src/wipeout/scene.c @@ -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; + } + } +} diff --git a/src/wipeout/scene.h b/src/wipeout/scene.h index a0c204a..2197bbe 100755 --- a/src/wipeout/scene.h +++ b/src/wipeout/scene.h @@ -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 diff --git a/src/wipeout/track.c b/src/wipeout/track.c index 41cb6b4..d1c6798 100755 --- a/src/wipeout/track.c +++ b/src/wipeout/track.c @@ -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; +} diff --git a/src/wipeout/track.h b/src/wipeout/track.h index 7b7e8a1..be9c8f4 100755 --- a/src/wipeout/track.h +++ b/src/wipeout/track.h @@ -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 diff --git a/src/wipeout/weapon.c b/src/wipeout/weapon.c index 5ec8779..2095f43 100755 --- a/src/wipeout/weapon.c +++ b/src/wipeout/weapon.c @@ -1,689 +1,689 @@ -#include "../mem.h" -#include "../utils.h" -#include "../system.h" - -#include "track.h" -#include "ship.h" -#include "weapon.h" -#include "object.h" -#include "game.h" -#include "image.h" -#include "particle.h" - -extern int32_t ctrlNeedTargetIcon; -extern int ctrlnearShip; -int16_t Shielded = 0; - -typedef struct weapon_t { - float timer; - ship_t *owner; - ship_t *target; - section_t *section; - Object *model; - bool active; - - int16_t trail_particle; - int16_t track_hit_particle; - int16_t ship_hit_particle; - float trail_spawn_timer; - - int16_t type; - vec3_t acceleration; - vec3_t velocity; - vec3_t position; - vec3_t angle; - float drag; - - void (*update_func)(struct weapon_t *); -} weapon_t; - - -weapon_t *weapons; -int weapons_active = 0; - -struct { - uint16_t reticle; - Object *rocket; - Object *mine; - Object *missile; - Object *shield; - Object *shield_internal; - Object *ebolt; -} weapon_assets; - -void weapon_update_wait_for_delay(weapon_t *self); - -void weapon_fire_mine(ship_t *ship); -void weapon_update_mine_wait_for_release(weapon_t *self); -void weapon_update_mine(weapon_t *self); -void weapon_update_mine_lights(weapon_t *self, int index); - -void weapon_fire_missile(ship_t *ship); -void weapon_update_missile(weapon_t *self); - -void weapon_fire_rocket(ship_t *ship); -void weapon_update_rocket(weapon_t *self); - -void weapon_fire_ebolt(ship_t *ship); -void weapon_update_ebolt(weapon_t *self); - -void weapon_fire_shield(ship_t *ship); -void weapon_update_shield(weapon_t *self); - -void weapon_fire_turbo(ship_t *ship); - -void invert_shield_polys(Object *shield); - -void weapons_load(void) { - weapons = mem_bump(sizeof(weapon_t) * WEAPONS_MAX); - weapon_assets.reticle = image_get_texture("wipeout/textures/target2.tim"); - - texture_list_t weapon_textures = image_get_compressed_textures("wipeout/common/mine.cmp"); - weapon_assets.rocket = objects_load("wipeout/common/rock.prm", weapon_textures); - weapon_assets.mine = objects_load("wipeout/common/mine.prm", weapon_textures); - weapon_assets.missile = objects_load("wipeout/common/miss.prm", weapon_textures); - weapon_assets.shield = objects_load("wipeout/common/shld.prm", weapon_textures); - weapon_assets.shield_internal = objects_load("wipeout/common/shld.prm", weapon_textures); - weapon_assets.ebolt = objects_load("wipeout/common/ebolt.prm", weapon_textures); - - // Invert shield polys for internal view - Prm poly = {.primitive = weapon_assets.shield_internal->primitives}; - int primitives_len = weapon_assets.shield_internal->primitives_len; - for (int k = 0; k < primitives_len; k++) { - switch (poly.primitive->type) { - case PRM_TYPE_G3 : - swap(poly.g3->coords[0], poly.g3->coords[2]); - poly.g3 += 1; - break; - - case PRM_TYPE_G4 : - swap(poly.g4->coords[0], poly.g4->coords[3]); - poly.g4 += 1; - break; - } - } - - weapons_init(); -} - -void weapons_init(void) { - weapons_active = 0; -} - -weapon_t *weapon_init(ship_t *ship) { - if (weapons_active == WEAPONS_MAX) { - return NULL; - } - - weapon_t *weapon = &weapons[weapons_active++]; - weapon->timer = 0; - weapon->owner = ship; - weapon->section = ship->section; - weapon->position = ship->position; - weapon->angle = ship->angle; - weapon->acceleration = vec3(0, 0, 0); - weapon->velocity = vec3(0, 0, 0); - weapon->acceleration = vec3(0, 0, 0); - weapon->target = NULL; - weapon->model = NULL; - weapon->active = true; - weapon->trail_particle = PARTICLE_TYPE_NONE; - weapon->track_hit_particle = PARTICLE_TYPE_NONE; - weapon->ship_hit_particle = PARTICLE_TYPE_NONE; - weapon->trail_spawn_timer = 0; - weapon->drag = 0; - return weapon; -} - -void weapons_fire(ship_t *ship, int weapon_type) { - switch (weapon_type) { - case WEAPON_TYPE_MINE: weapon_fire_mine(ship); break; - case WEAPON_TYPE_MISSILE: weapon_fire_missile(ship); break; - case WEAPON_TYPE_ROCKET: weapon_fire_rocket(ship); break; - case WEAPON_TYPE_EBOLT: weapon_fire_ebolt(ship); break; - case WEAPON_TYPE_SHIELD: weapon_fire_shield(ship); break; - case WEAPON_TYPE_TURBO: weapon_fire_turbo(ship); break; - default: die("Inavlid weapon type %d", weapon_type); - } - ship->weapon_type = WEAPON_TYPE_NONE; -} - -void weapons_fire_delayed(ship_t *ship, int weapon_type) { - weapon_t *weapon = weapon_init(ship); - if (!weapon) { - return; - } - weapon->type = weapon_type; - weapon->timer = WEAPON_AI_DELAY; - weapon->update_func = weapon_update_wait_for_delay; -} - -bool weapon_collides_with_track(weapon_t *self); - -void weapons_update(void) { - for (int i = 0; i < weapons_active; i++) { - weapon_t *weapon = &weapons[i]; - - weapon->timer -= system_tick(); - (weapon->update_func)(weapon); - - // Handle projectiles - if (weapon->acceleration.x != 0 || weapon->acceleration.z != 0) { - weapon->velocity = vec3_add(weapon->velocity, vec3_mulf(weapon->acceleration, 30 * system_tick())); - weapon->velocity = vec3_sub(weapon->velocity, vec3_mulf(weapon->velocity, weapon->drag * 30 * system_tick())); - weapon->position = vec3_add(weapon->position, vec3_mulf(weapon->velocity, 30 * system_tick())); - - // Move along track normal - track_face_t *face = track_section_get_base_face(weapon->section); - vec3_t face_point = face->tris[0].vertices[0].pos; - vec3_t face_normal = face->normal; - float height = vec3_distance_to_plane(weapon->position, face_point, face_normal); - - if (height < 2000) { - weapon->position = vec3_add(weapon->position, vec3_mulf(face_normal, (200 - height) * 30 * system_tick())); - } - - // Trail - if (weapon->trail_particle != PARTICLE_TYPE_NONE) { - weapon->trail_spawn_timer += system_tick(); - while (weapon->trail_spawn_timer > 0) { - vec3_t pos = vec3_sub(weapon->position, vec3_mulf(weapon->velocity, 30 * system_tick() * weapon->trail_spawn_timer)); - vec3_t velocity = vec3(rand_float(-128, 128), rand_float(-128, 128), rand_float(-128, 128)); - particles_spawn(pos, weapon->trail_particle, velocity, 128); - weapon->trail_spawn_timer -= WEAPON_PARTICLE_SPAWN_RATE; - } - } - - // Track collision - weapon->section = track_nearest_section(weapon->position, weapon->section, NULL); - if (weapon_collides_with_track(weapon)) { - for (int p = 0; p < 32; p++) { - vec3_t velocity = vec3(rand_float(-512, 512), rand_float(-512, 512), rand_float(-512, 512)); - particles_spawn(weapon->position, weapon->track_hit_particle, velocity, 256); - } - sfx_play_at(SFX_EXPLOSION_2, weapon->position, vec3(0,0,0), 1); - weapon->active = false; - } - } - - // If this weapon is released, we have to rewind one step - if (!weapon->active) { - weapons[i--] = weapons[--weapons_active]; - continue; - } - } -} - -void weapons_draw(void) { - mat4_t mat = mat4_identity(); - for (int i = 0; i < weapons_active; i++) { - weapon_t *weapon = &weapons[i]; - if (weapon->model) { - mat4_set_translation(&mat, weapon->position); - mat4_set_yaw_pitch_roll(&mat, weapon->angle); - if (weapon->model == weapon_assets.mine) { - weapon_update_mine_lights(weapon, i); - } - object_draw(weapon->model, &mat); - } - } -} - - - -void weapon_set_trajectory(weapon_t *self) { - ship_t *ship = self->owner; - track_face_t *face = track_section_get_base_face(ship->section); - - vec3_t face_point = face->tris[0].vertices[0].pos; - vec3_t target = vec3_add(ship->position, vec3_mulf(ship->dir_forward, 64)); - float target_height = vec3_distance_to_plane(target, face_point, face->normal); - float ship_height = vec3_distance_to_plane(target, face_point, face->normal); - - float nudge = target_height * 0.95 - ship_height; - - self->acceleration = vec3_sub(vec3_sub(target, vec3_mulf(face->normal, nudge)), ship->position); - self->velocity = vec3_mulf(ship->velocity, 0.015625); - self->angle = ship->angle; -} - -void weapon_follow_target(weapon_t *self) { - vec3_t angular_velocity = vec3(0, 0, 0); - if (self->target) { - vec3_t dir = vec3_mulf(vec3_sub(self->target->position, self->position), 0.125 * 30 * system_tick()); - float height = sqrt(dir.x * dir.x + dir.z * dir.z); - angular_velocity.y = -atan2(dir.x, dir.z) - self->angle.y; - angular_velocity.x = -atan2(dir.y, height) - self->angle.x; - } - - angular_velocity = vec3_wrap_angle(angular_velocity); - self->angle = vec3_add(self->angle, vec3_mulf(angular_velocity, 30 * system_tick() * 0.25)); - self->angle = vec3_wrap_angle(self->angle); - - self->acceleration.x = -sin(self->angle.y) * cos(self->angle.x) * 256; - self->acceleration.y = -sin(self->angle.x) * 256; - self->acceleration.z = cos(self->angle.y) * cos(self->angle.x) * 256; -} - -ship_t *weapon_collides_with_ship(weapon_t *self) { - for (int i = 0; i < NUM_PILOTS; i++) { - ship_t *ship = &g.ships[i]; - if (ship == self->owner) { - continue; - } - - float distance = vec3_len(vec3_sub(ship->position, self->position)); - if (distance < 512) { - for (int p = 0; p < 32; p++) { - vec3_t velocity = vec3(rand_float(-512, 512), rand_float(-512, 512), rand_float(-512, 512)); - velocity = vec3_add(velocity, vec3_mulf(ship->velocity, 0.25)); - particles_spawn(self->position, self->ship_hit_particle, velocity, 256); - } - return ship; - } - } - - return NULL; -} - - -bool weapon_collides_with_track(weapon_t *self) { - if (flags_is(self->section->flags, SECTION_JUMP)) { - return false; - } - - track_face_t *face = g.track.faces + self->section->face_start; - for (int i = 0; i < self->section->face_count; i++) { - vec3_t face_point = face->tris[0].vertices[0].pos; - float distance = vec3_distance_to_plane(self->position, face_point, face->normal); - if (distance < 0) { - return true; - } - face++; - } - - return false; -} - -void weapon_update_wait_for_delay(weapon_t *self) { - if (self->timer <= 0) { - weapons_fire(self->owner, self->type); - self->active = false; - } -} - - -void weapon_fire_mine(ship_t *ship) { - float timer = 0; - for (int i = 0; i < WEAPON_MINE_COUNT; i++) { - weapon_t *self = weapon_init(ship); - if (!self) { - return; - } - timer += WEAPON_MINE_RELEASE_RATE; - self->timer = timer; - self->update_func = weapon_update_mine_wait_for_release; - } -} - - - -void weapon_update_mine_wait_for_release(weapon_t *self) { - if (self->timer <= 0) { - self->timer = WEAPON_MINE_DURATION; - self->update_func = weapon_update_mine; - self->model = weapon_assets.mine; - self->position = self->owner->position; - self->angle.y = rand_float(0, M_PI * 2); - - self->trail_particle = PARTICLE_TYPE_NONE; - self->track_hit_particle = PARTICLE_TYPE_NONE; - self->ship_hit_particle = PARTICLE_TYPE_FIRE; - - if (self->owner->pilot == g.pilot) { - sfx_play(SFX_MINE_DROP); - } - } -} - -void weapon_update_mine_lights(weapon_t *self, int index) { - Prm prm = {.primitive = self->model->primitives}; - - uint8_t r = sin(system_cycle_time() * M_PI * 2 + index * 0.66) * 128 + 128; - for (int i = 0; i < 8; i++) { - switch (prm.primitive->type) { - case PRM_TYPE_GT3: - prm.gt3->color[0].r = 230; - prm.gt3->color[1].r = r; - prm.gt3->color[2].r = r; - prm.gt3->color[0].g = 0; - prm.gt3->color[1].g = 0x40; - prm.gt3->color[2].g = 0x40; - prm.gt3->color[0].b = 0; - prm.gt3->color[1].b = 0; - prm.gt3->color[2].b = 0; - prm.gt3 += 1; - break; - } - } -} - -void weapon_update_mine(weapon_t *self) { - if (self->timer <= 0) { - self->active = false; - return; - } - - // TODO: oscilate perpendicular to track!? - self->angle.y += system_tick(); - - ship_t *ship = weapon_collides_with_ship(self); - if (ship) { - sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); - self->active = false; - if (flags_not(ship->flags, SHIP_SHIELDED)) { - if (ship->pilot == g.pilot) { - ship->velocity = vec3_sub(ship->velocity, vec3_mulf(ship->velocity, 0.125)); - // SetShake(20); // FIXME - } - else { - ship->speed = ship->speed * 0.125; - } - } - } -} - - -void weapon_fire_missile(ship_t *ship) { - weapon_t *self = weapon_init(ship); - if (!self) { - return; - } - - self->timer = WEAPON_MISSILE_DURATION; - self->model = weapon_assets.missile; - self->update_func = weapon_update_missile; - self->trail_particle = PARTICLE_TYPE_SMOKE; - self->track_hit_particle = PARTICLE_TYPE_FIRE_WHITE; - self->ship_hit_particle = PARTICLE_TYPE_FIRE; - self->target = ship->weapon_target; - self->drag = 0.25; - weapon_set_trajectory(self); - - if (self->owner->pilot == g.pilot) { - sfx_play(SFX_MISSILE_FIRE); - } -} - -void weapon_update_missile(weapon_t *self) { - if (self->timer <= 0) { - self->active = false; - return; - } - - weapon_follow_target(self); - - // Collision with other ships - ship_t *ship = weapon_collides_with_ship(self); - if (ship) { - sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); - self->active = false; - - if (flags_not(ship->flags, SHIP_SHIELDED)) { - if (ship->pilot == g.pilot) { - ship->velocity = vec3_sub(ship->velocity, vec3_mulf(ship->velocity, 0.75)); - ship->angular_velocity.z += rand_float(-0.1, 0.1); - ship->turn_rate_from_hit = rand_float(-0.1, 0.1); - // SetShake(20); // FIXME - } - else { - ship->speed = ship->speed * 0.03125; - ship->angular_velocity.z += 10 * M_PI; - ship->turn_rate_from_hit = rand_float(-M_PI, M_PI); - } - } - } -} - -void weapon_fire_rocket(ship_t *ship) { - weapon_t *self = weapon_init(ship); - if (!self) { - return; - } - - self->timer = WEAPON_ROCKET_DURATION; - self->model = weapon_assets.rocket; - self->update_func = weapon_update_rocket; - self->trail_particle = PARTICLE_TYPE_SMOKE; - self->track_hit_particle = PARTICLE_TYPE_FIRE_WHITE; - self->ship_hit_particle = PARTICLE_TYPE_FIRE; - self->drag = 0.03125; - weapon_set_trajectory(self); - - if (self->owner->pilot == g.pilot) { - sfx_play(SFX_MISSILE_FIRE); - } -} - -void weapon_update_rocket(weapon_t *self) { - if (self->timer <= 0) { - self->active = false; - return; - } - - // Collision with other ships - ship_t *ship = weapon_collides_with_ship(self); - if (ship) { - sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); - self->active = false; - - if (flags_not(ship->flags, SHIP_SHIELDED)) { - if (ship->pilot == g.pilot) { - ship->velocity = vec3_sub(ship->velocity, vec3_mulf(ship->velocity, 0.75)); - ship->angular_velocity.z += rand_float(-0.1, 0.1);; - ship->turn_rate_from_hit = rand_float(-0.1, 0.1);; - // SetShake(20); // FIXME - } - else { - ship->speed = ship->speed * 0.03125; - ship->angular_velocity.z += 10 * M_PI; - ship->turn_rate_from_hit = rand_float(-M_PI, M_PI); - } - } - } -} - - -void weapon_fire_ebolt(ship_t *ship) { - weapon_t *self = weapon_init(ship); - if (!self) { - return; - } - - self->timer = WEAPON_EBOLT_DURATION; - self->model = weapon_assets.ebolt; - self->update_func = weapon_update_ebolt; - self->trail_particle = PARTICLE_TYPE_EBOLT; - self->track_hit_particle = PARTICLE_TYPE_EBOLT; - self->ship_hit_particle = PARTICLE_TYPE_GREENY; - self->target = ship->weapon_target; - self->drag = 0.25; - weapon_set_trajectory(self); - - if (self->owner->pilot == g.pilot) { - sfx_play(SFX_EBOLT); - } -} - -void weapon_update_ebolt(weapon_t *self) { - if (self->timer <= 0) { - self->active = false; - return; - } - - weapon_follow_target(self); - - // Collision with other ships - ship_t *ship = weapon_collides_with_ship(self); - if (ship) { - sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); - self->active = false; - - if (flags_not(ship->flags, SHIP_SHIELDED)) { - flags_add(ship->flags, SHIP_ELECTROED); - ship->ebolt_timer = WEAPON_EBOLT_DURATION; - } - } -} - -void weapon_fire_shield(ship_t *ship) { - weapon_t *self = weapon_init(ship); - if (!self) { - return; - } - - self->timer = WEAPON_SHIELD_DURATION; - self->model = weapon_assets.shield; - self->update_func = weapon_update_shield; - - flags_add(self->owner->flags, SHIP_SHIELDED); -} - -void weapon_update_shield(weapon_t *self) { - if (self->timer <= 0) { - self->active = false; - flags_rm(self->owner->flags, SHIP_SHIELDED); - return; - } - - - if (flags_is(self->owner->flags, SHIP_VIEW_INTERNAL)) { - self->position = ship_cockpit(self->owner); - self->model = weapon_assets.shield_internal; - } - else { - self->position = self->owner->position; - self->model = weapon_assets.shield; - } - self->angle = self->owner->angle; - - - Prm poly = {.primitive = self->model->primitives}; - int primitives_len = self->model->primitives_len; - uint8_t col0, col1, col2, col3; - int16_t *coords; - uint8_t shield_alpha = 48; - - // FIXME: this looks kinda close to the PSX original!? - float color_timer = self->timer * 0.05; - for (int k = 0; k < primitives_len; k++) { - switch (poly.primitive->type) { - case PRM_TYPE_G3 : - coords = poly.g3->coords; - - col0 = sin(color_timer * coords[0]) * 127 + 128; - col1 = sin(color_timer * coords[1]) * 127 + 128; - col2 = sin(color_timer * coords[2]) * 127 + 128; - - poly.g3->color[0].r = col0; - poly.g3->color[0].g = col0; - poly.g3->color[0].b = 255; - poly.g3->color[0].a = shield_alpha; - - poly.g3->color[1].r = col1; - poly.g3->color[1].g = col1; - poly.g3->color[1].b = 255; - poly.g3->color[1].a = shield_alpha; - - poly.g3->color[2].r = col2; - poly.g3->color[2].g = col2; - poly.g3->color[2].b = 255; - poly.g3->color[2].a = shield_alpha; - poly.g3 += 1; - break; - - case PRM_TYPE_G4 : - coords = poly.g4->coords; - - col0 = sin(color_timer * coords[0]) * 127 + 128; - col1 = sin(color_timer * coords[1]) * 127 + 128; - col2 = sin(color_timer * coords[2]) * 127 + 128; - col3 = sin(color_timer * coords[3]) * 127 + 128; - - poly.g4->color[0].r = col0; - poly.g4->color[0].g = col0; - poly.g4->color[0].b = 255; - poly.g4->color[0].a = shield_alpha; - - poly.g4->color[1].r = col1; - poly.g4->color[1].g = col1; - poly.g4->color[1].b = 255; - poly.g4->color[1].a = shield_alpha; - - poly.g4->color[2].r = col2; - poly.g4->color[2].g = col2; - poly.g4->color[2].b = 255; - poly.g4->color[2].a = shield_alpha; - - poly.g4->color[3].r = col3; - poly.g4->color[3].g = col3; - poly.g4->color[3].b = 255; - poly.g4->color[3].a = shield_alpha; - poly.g4 += 1; - break; - } - } -} - - -void weapon_fire_turbo(ship_t *ship) { - ship->velocity = vec3_add(ship->velocity, vec3_mulf(ship->dir_forward, 39321)); // unitVecNose.vx) << 3) * FR60) / 50 - - if (ship->pilot == g.pilot) { - sfx_t *sfx = sfx_play(SFX_MISSILE_FIRE); - sfx->pitch = 0.25; - } -} - -int weapon_get_random_type(int type_class) { - if (type_class == WEAPON_CLASS_ANY) { - int index = rand_int(0, 65); - if (index < 17) { - return WEAPON_TYPE_ROCKET; - } - else if (index < 35) { - return WEAPON_TYPE_MINE; - } - else if (index < 45) { - return WEAPON_TYPE_SHIELD; - } - else if (index < 53) { - return WEAPON_TYPE_MISSILE; - } - else if (index < 59) { - return WEAPON_TYPE_TURBO; - } - else { - return WEAPON_TYPE_EBOLT; - } - } - else if (type_class == WEAPON_CLASS_PROJECTILE) { - int index = rand_int(0, 60); - if (index < 27) { - return WEAPON_TYPE_ROCKET; - } - else if (index < 40) { - return WEAPON_TYPE_MISSILE; - } - else if (index < 50) { - return WEAPON_TYPE_TURBO; - } - else { - return WEAPON_TYPE_EBOLT; - } - } - else { - die("Unknown WEAPON_CLASS_ %d", type_class); - } -} - +#include "../mem.h" +#include "../utils.h" +#include "../system.h" + +#include "track.h" +#include "ship.h" +#include "weapon.h" +#include "object.h" +#include "game.h" +#include "image.h" +#include "particle.h" + +extern int32_t ctrlNeedTargetIcon; +extern int ctrlnearShip; +int16_t Shielded = 0; + +typedef struct weapon_t { + float timer; + ship_t *owner; + ship_t *target; + section_t *section; + Object *model; + bool active; + + int16_t trail_particle; + int16_t track_hit_particle; + int16_t ship_hit_particle; + float trail_spawn_timer; + + int16_t type; + vec3_t acceleration; + vec3_t velocity; + vec3_t position; + vec3_t angle; + float drag; + + void (*update_func)(struct weapon_t *); +} weapon_t; + + +weapon_t *weapons; +int weapons_active = 0; + +struct { + uint16_t reticle; + Object *rocket; + Object *mine; + Object *missile; + Object *shield; + Object *shield_internal; + Object *ebolt; +} weapon_assets; + +void weapon_update_wait_for_delay(weapon_t *self); + +void weapon_fire_mine(ship_t *ship); +void weapon_update_mine_wait_for_release(weapon_t *self); +void weapon_update_mine(weapon_t *self); +void weapon_update_mine_lights(weapon_t *self, int index); + +void weapon_fire_missile(ship_t *ship); +void weapon_update_missile(weapon_t *self); + +void weapon_fire_rocket(ship_t *ship); +void weapon_update_rocket(weapon_t *self); + +void weapon_fire_ebolt(ship_t *ship); +void weapon_update_ebolt(weapon_t *self); + +void weapon_fire_shield(ship_t *ship); +void weapon_update_shield(weapon_t *self); + +void weapon_fire_turbo(ship_t *ship); + +void invert_shield_polys(Object *shield); + +void weapons_load(void) { + weapons = mem_bump(sizeof(weapon_t) * WEAPONS_MAX); + weapon_assets.reticle = image_get_texture("wipeout/textures/target2.tim"); + + texture_list_t weapon_textures = image_get_compressed_textures("wipeout/common/mine.cmp"); + weapon_assets.rocket = objects_load("wipeout/common/rock.prm", weapon_textures); + weapon_assets.mine = objects_load("wipeout/common/mine.prm", weapon_textures); + weapon_assets.missile = objects_load("wipeout/common/miss.prm", weapon_textures); + weapon_assets.shield = objects_load("wipeout/common/shld.prm", weapon_textures); + weapon_assets.shield_internal = objects_load("wipeout/common/shld.prm", weapon_textures); + weapon_assets.ebolt = objects_load("wipeout/common/ebolt.prm", weapon_textures); + + // Invert shield polys for internal view + Prm poly = {.primitive = weapon_assets.shield_internal->primitives}; + int primitives_len = weapon_assets.shield_internal->primitives_len; + for (int k = 0; k < primitives_len; k++) { + switch (poly.primitive->type) { + case PRM_TYPE_G3 : + swap(poly.g3->coords[0], poly.g3->coords[2]); + poly.g3 += 1; + break; + + case PRM_TYPE_G4 : + swap(poly.g4->coords[0], poly.g4->coords[3]); + poly.g4 += 1; + break; + } + } + + weapons_init(); +} + +void weapons_init(void) { + weapons_active = 0; +} + +weapon_t *weapon_init(ship_t *ship) { + if (weapons_active == WEAPONS_MAX) { + return NULL; + } + + weapon_t *weapon = &weapons[weapons_active++]; + weapon->timer = 0; + weapon->owner = ship; + weapon->section = ship->section; + weapon->position = ship->position; + weapon->angle = ship->angle; + weapon->acceleration = vec3(0, 0, 0); + weapon->velocity = vec3(0, 0, 0); + weapon->acceleration = vec3(0, 0, 0); + weapon->target = NULL; + weapon->model = NULL; + weapon->active = true; + weapon->trail_particle = PARTICLE_TYPE_NONE; + weapon->track_hit_particle = PARTICLE_TYPE_NONE; + weapon->ship_hit_particle = PARTICLE_TYPE_NONE; + weapon->trail_spawn_timer = 0; + weapon->drag = 0; + return weapon; +} + +void weapons_fire(ship_t *ship, int weapon_type) { + switch (weapon_type) { + case WEAPON_TYPE_MINE: weapon_fire_mine(ship); break; + case WEAPON_TYPE_MISSILE: weapon_fire_missile(ship); break; + case WEAPON_TYPE_ROCKET: weapon_fire_rocket(ship); break; + case WEAPON_TYPE_EBOLT: weapon_fire_ebolt(ship); break; + case WEAPON_TYPE_SHIELD: weapon_fire_shield(ship); break; + case WEAPON_TYPE_TURBO: weapon_fire_turbo(ship); break; + default: die("Inavlid weapon type %d", weapon_type); + } + ship->weapon_type = WEAPON_TYPE_NONE; +} + +void weapons_fire_delayed(ship_t *ship, int weapon_type) { + weapon_t *weapon = weapon_init(ship); + if (!weapon) { + return; + } + weapon->type = weapon_type; + weapon->timer = WEAPON_AI_DELAY; + weapon->update_func = weapon_update_wait_for_delay; +} + +bool weapon_collides_with_track(weapon_t *self); + +void weapons_update(void) { + for (int i = 0; i < weapons_active; i++) { + weapon_t *weapon = &weapons[i]; + + weapon->timer -= system_tick(); + (weapon->update_func)(weapon); + + // Handle projectiles + if (weapon->acceleration.x != 0 || weapon->acceleration.z != 0) { + weapon->velocity = vec3_add(weapon->velocity, vec3_mulf(weapon->acceleration, 30 * system_tick())); + weapon->velocity = vec3_sub(weapon->velocity, vec3_mulf(weapon->velocity, weapon->drag * 30 * system_tick())); + weapon->position = vec3_add(weapon->position, vec3_mulf(weapon->velocity, 30 * system_tick())); + + // Move along track normal + track_face_t *face = track_section_get_base_face(weapon->section); + vec3_t face_point = face->tris[0].vertices[0].pos; + vec3_t face_normal = face->normal; + float height = vec3_distance_to_plane(weapon->position, face_point, face_normal); + + if (height < 2000) { + weapon->position = vec3_add(weapon->position, vec3_mulf(face_normal, (200 - height) * 30 * system_tick())); + } + + // Trail + if (weapon->trail_particle != PARTICLE_TYPE_NONE) { + weapon->trail_spawn_timer += system_tick(); + while (weapon->trail_spawn_timer > 0) { + vec3_t pos = vec3_sub(weapon->position, vec3_mulf(weapon->velocity, 30 * system_tick() * weapon->trail_spawn_timer)); + vec3_t velocity = vec3(rand_float(-128, 128), rand_float(-128, 128), rand_float(-128, 128)); + particles_spawn(pos, weapon->trail_particle, velocity, 128); + weapon->trail_spawn_timer -= WEAPON_PARTICLE_SPAWN_RATE; + } + } + + // Track collision + weapon->section = track_nearest_section(weapon->position, weapon->section, NULL); + if (weapon_collides_with_track(weapon)) { + for (int p = 0; p < 32; p++) { + vec3_t velocity = vec3(rand_float(-512, 512), rand_float(-512, 512), rand_float(-512, 512)); + particles_spawn(weapon->position, weapon->track_hit_particle, velocity, 256); + } + sfx_play_at(SFX_EXPLOSION_2, weapon->position, vec3(0,0,0), 1); + weapon->active = false; + } + } + + // If this weapon is released, we have to rewind one step + if (!weapon->active) { + weapons[i--] = weapons[--weapons_active]; + continue; + } + } +} + +void weapons_draw(void) { + mat4_t mat = mat4_identity(); + for (int i = 0; i < weapons_active; i++) { + weapon_t *weapon = &weapons[i]; + if (weapon->model) { + mat4_set_translation(&mat, weapon->position); + mat4_set_yaw_pitch_roll(&mat, weapon->angle); + if (weapon->model == weapon_assets.mine) { + weapon_update_mine_lights(weapon, i); + } + object_draw(weapon->model, &mat); + } + } +} + + + +void weapon_set_trajectory(weapon_t *self) { + ship_t *ship = self->owner; + track_face_t *face = track_section_get_base_face(ship->section); + + vec3_t face_point = face->tris[0].vertices[0].pos; + vec3_t target = vec3_add(ship->position, vec3_mulf(ship->dir_forward, 64)); + float target_height = vec3_distance_to_plane(target, face_point, face->normal); + float ship_height = vec3_distance_to_plane(target, face_point, face->normal); + + float nudge = target_height * 0.95 - ship_height; + + self->acceleration = vec3_sub(vec3_sub(target, vec3_mulf(face->normal, nudge)), ship->position); + self->velocity = vec3_mulf(ship->velocity, 0.015625); + self->angle = ship->angle; +} + +void weapon_follow_target(weapon_t *self) { + vec3_t angular_velocity = vec3(0, 0, 0); + if (self->target) { + vec3_t dir = vec3_mulf(vec3_sub(self->target->position, self->position), 0.125 * 30 * system_tick()); + float height = sqrt(dir.x * dir.x + dir.z * dir.z); + angular_velocity.y = -atan2(dir.x, dir.z) - self->angle.y; + angular_velocity.x = -atan2(dir.y, height) - self->angle.x; + } + + angular_velocity = vec3_wrap_angle(angular_velocity); + self->angle = vec3_add(self->angle, vec3_mulf(angular_velocity, 30 * system_tick() * 0.25)); + self->angle = vec3_wrap_angle(self->angle); + + self->acceleration.x = -sin(self->angle.y) * cos(self->angle.x) * 256; + self->acceleration.y = -sin(self->angle.x) * 256; + self->acceleration.z = cos(self->angle.y) * cos(self->angle.x) * 256; +} + +ship_t *weapon_collides_with_ship(weapon_t *self) { + for (int i = 0; i < NUM_PILOTS; i++) { + ship_t *ship = &g.ships[i]; + if (ship == self->owner) { + continue; + } + + float distance = vec3_len(vec3_sub(ship->position, self->position)); + if (distance < 512) { + for (int p = 0; p < 32; p++) { + vec3_t velocity = vec3(rand_float(-512, 512), rand_float(-512, 512), rand_float(-512, 512)); + velocity = vec3_add(velocity, vec3_mulf(ship->velocity, 0.25)); + particles_spawn(self->position, self->ship_hit_particle, velocity, 256); + } + return ship; + } + } + + return NULL; +} + + +bool weapon_collides_with_track(weapon_t *self) { + if (flags_is(self->section->flags, SECTION_JUMP)) { + return false; + } + + track_face_t *face = g.track.faces + self->section->face_start; + for (int i = 0; i < self->section->face_count; i++) { + vec3_t face_point = face->tris[0].vertices[0].pos; + float distance = vec3_distance_to_plane(self->position, face_point, face->normal); + if (distance < 0) { + return true; + } + face++; + } + + return false; +} + +void weapon_update_wait_for_delay(weapon_t *self) { + if (self->timer <= 0) { + weapons_fire(self->owner, self->type); + self->active = false; + } +} + + +void weapon_fire_mine(ship_t *ship) { + float timer = 0; + for (int i = 0; i < WEAPON_MINE_COUNT; i++) { + weapon_t *self = weapon_init(ship); + if (!self) { + return; + } + timer += WEAPON_MINE_RELEASE_RATE; + self->timer = timer; + self->update_func = weapon_update_mine_wait_for_release; + } +} + + + +void weapon_update_mine_wait_for_release(weapon_t *self) { + if (self->timer <= 0) { + self->timer = WEAPON_MINE_DURATION; + self->update_func = weapon_update_mine; + self->model = weapon_assets.mine; + self->position = self->owner->position; + self->angle.y = rand_float(0, M_PI * 2); + + self->trail_particle = PARTICLE_TYPE_NONE; + self->track_hit_particle = PARTICLE_TYPE_NONE; + self->ship_hit_particle = PARTICLE_TYPE_FIRE; + + if (self->owner->pilot == g.pilot) { + sfx_play(SFX_MINE_DROP); + } + } +} + +void weapon_update_mine_lights(weapon_t *self, int index) { + Prm prm = {.primitive = self->model->primitives}; + + uint8_t r = sin(system_cycle_time() * M_PI * 2 + index * 0.66) * 128 + 128; + for (int i = 0; i < 8; i++) { + switch (prm.primitive->type) { + case PRM_TYPE_GT3: + prm.gt3->color[0].r = 230; + prm.gt3->color[1].r = r; + prm.gt3->color[2].r = r; + prm.gt3->color[0].g = 0; + prm.gt3->color[1].g = 0x40; + prm.gt3->color[2].g = 0x40; + prm.gt3->color[0].b = 0; + prm.gt3->color[1].b = 0; + prm.gt3->color[2].b = 0; + prm.gt3 += 1; + break; + } + } +} + +void weapon_update_mine(weapon_t *self) { + if (self->timer <= 0) { + self->active = false; + return; + } + + // TODO: oscilate perpendicular to track!? + self->angle.y += system_tick(); + + ship_t *ship = weapon_collides_with_ship(self); + if (ship) { + sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); + self->active = false; + if (flags_not(ship->flags, SHIP_SHIELDED)) { + if (ship->pilot == g.pilot) { + ship->velocity = vec3_sub(ship->velocity, vec3_mulf(ship->velocity, 0.125)); + // SetShake(20); // FIXME + } + else { + ship->speed = ship->speed * 0.125; + } + } + } +} + + +void weapon_fire_missile(ship_t *ship) { + weapon_t *self = weapon_init(ship); + if (!self) { + return; + } + + self->timer = WEAPON_MISSILE_DURATION; + self->model = weapon_assets.missile; + self->update_func = weapon_update_missile; + self->trail_particle = PARTICLE_TYPE_SMOKE; + self->track_hit_particle = PARTICLE_TYPE_FIRE_WHITE; + self->ship_hit_particle = PARTICLE_TYPE_FIRE; + self->target = ship->weapon_target; + self->drag = 0.25; + weapon_set_trajectory(self); + + if (self->owner->pilot == g.pilot) { + sfx_play(SFX_MISSILE_FIRE); + } +} + +void weapon_update_missile(weapon_t *self) { + if (self->timer <= 0) { + self->active = false; + return; + } + + weapon_follow_target(self); + + // Collision with other ships + ship_t *ship = weapon_collides_with_ship(self); + if (ship) { + sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); + self->active = false; + + if (flags_not(ship->flags, SHIP_SHIELDED)) { + if (ship->pilot == g.pilot) { + ship->velocity = vec3_sub(ship->velocity, vec3_mulf(ship->velocity, 0.75)); + ship->angular_velocity.z += rand_float(-0.1, 0.1); + ship->turn_rate_from_hit = rand_float(-0.1, 0.1); + // SetShake(20); // FIXME + } + else { + ship->speed = ship->speed * 0.03125; + ship->angular_velocity.z += 10 * M_PI; + ship->turn_rate_from_hit = rand_float(-M_PI, M_PI); + } + } + } +} + +void weapon_fire_rocket(ship_t *ship) { + weapon_t *self = weapon_init(ship); + if (!self) { + return; + } + + self->timer = WEAPON_ROCKET_DURATION; + self->model = weapon_assets.rocket; + self->update_func = weapon_update_rocket; + self->trail_particle = PARTICLE_TYPE_SMOKE; + self->track_hit_particle = PARTICLE_TYPE_FIRE_WHITE; + self->ship_hit_particle = PARTICLE_TYPE_FIRE; + self->drag = 0.03125; + weapon_set_trajectory(self); + + if (self->owner->pilot == g.pilot) { + sfx_play(SFX_MISSILE_FIRE); + } +} + +void weapon_update_rocket(weapon_t *self) { + if (self->timer <= 0) { + self->active = false; + return; + } + + // Collision with other ships + ship_t *ship = weapon_collides_with_ship(self); + if (ship) { + sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); + self->active = false; + + if (flags_not(ship->flags, SHIP_SHIELDED)) { + if (ship->pilot == g.pilot) { + ship->velocity = vec3_sub(ship->velocity, vec3_mulf(ship->velocity, 0.75)); + ship->angular_velocity.z += rand_float(-0.1, 0.1);; + ship->turn_rate_from_hit = rand_float(-0.1, 0.1);; + // SetShake(20); // FIXME + } + else { + ship->speed = ship->speed * 0.03125; + ship->angular_velocity.z += 10 * M_PI; + ship->turn_rate_from_hit = rand_float(-M_PI, M_PI); + } + } + } +} + + +void weapon_fire_ebolt(ship_t *ship) { + weapon_t *self = weapon_init(ship); + if (!self) { + return; + } + + self->timer = WEAPON_EBOLT_DURATION; + self->model = weapon_assets.ebolt; + self->update_func = weapon_update_ebolt; + self->trail_particle = PARTICLE_TYPE_EBOLT; + self->track_hit_particle = PARTICLE_TYPE_EBOLT; + self->ship_hit_particle = PARTICLE_TYPE_GREENY; + self->target = ship->weapon_target; + self->drag = 0.25; + weapon_set_trajectory(self); + + if (self->owner->pilot == g.pilot) { + sfx_play(SFX_EBOLT); + } +} + +void weapon_update_ebolt(weapon_t *self) { + if (self->timer <= 0) { + self->active = false; + return; + } + + weapon_follow_target(self); + + // Collision with other ships + ship_t *ship = weapon_collides_with_ship(self); + if (ship) { + sfx_play_at(SFX_EXPLOSION_1, self->position, vec3(0,0,0), 1); + self->active = false; + + if (flags_not(ship->flags, SHIP_SHIELDED)) { + flags_add(ship->flags, SHIP_ELECTROED); + ship->ebolt_timer = WEAPON_EBOLT_DURATION; + } + } +} + +void weapon_fire_shield(ship_t *ship) { + weapon_t *self = weapon_init(ship); + if (!self) { + return; + } + + self->timer = WEAPON_SHIELD_DURATION; + self->model = weapon_assets.shield; + self->update_func = weapon_update_shield; + + flags_add(self->owner->flags, SHIP_SHIELDED); +} + +void weapon_update_shield(weapon_t *self) { + if (self->timer <= 0) { + self->active = false; + flags_rm(self->owner->flags, SHIP_SHIELDED); + return; + } + + + if (flags_is(self->owner->flags, SHIP_VIEW_INTERNAL)) { + self->position = ship_cockpit(self->owner); + self->model = weapon_assets.shield_internal; + } + else { + self->position = self->owner->position; + self->model = weapon_assets.shield; + } + self->angle = self->owner->angle; + + + Prm poly = {.primitive = self->model->primitives}; + int primitives_len = self->model->primitives_len; + uint8_t col0, col1, col2, col3; + int16_t *coords; + uint8_t shield_alpha = 48; + + // FIXME: this looks kinda close to the PSX original!? + float color_timer = self->timer * 0.05; + for (int k = 0; k < primitives_len; k++) { + switch (poly.primitive->type) { + case PRM_TYPE_G3 : + coords = poly.g3->coords; + + col0 = sin(color_timer * coords[0]) * 127 + 128; + col1 = sin(color_timer * coords[1]) * 127 + 128; + col2 = sin(color_timer * coords[2]) * 127 + 128; + + poly.g3->color[0].r = col0; + poly.g3->color[0].g = col0; + poly.g3->color[0].b = 255; + poly.g3->color[0].a = shield_alpha; + + poly.g3->color[1].r = col1; + poly.g3->color[1].g = col1; + poly.g3->color[1].b = 255; + poly.g3->color[1].a = shield_alpha; + + poly.g3->color[2].r = col2; + poly.g3->color[2].g = col2; + poly.g3->color[2].b = 255; + poly.g3->color[2].a = shield_alpha; + poly.g3 += 1; + break; + + case PRM_TYPE_G4 : + coords = poly.g4->coords; + + col0 = sin(color_timer * coords[0]) * 127 + 128; + col1 = sin(color_timer * coords[1]) * 127 + 128; + col2 = sin(color_timer * coords[2]) * 127 + 128; + col3 = sin(color_timer * coords[3]) * 127 + 128; + + poly.g4->color[0].r = col0; + poly.g4->color[0].g = col0; + poly.g4->color[0].b = 255; + poly.g4->color[0].a = shield_alpha; + + poly.g4->color[1].r = col1; + poly.g4->color[1].g = col1; + poly.g4->color[1].b = 255; + poly.g4->color[1].a = shield_alpha; + + poly.g4->color[2].r = col2; + poly.g4->color[2].g = col2; + poly.g4->color[2].b = 255; + poly.g4->color[2].a = shield_alpha; + + poly.g4->color[3].r = col3; + poly.g4->color[3].g = col3; + poly.g4->color[3].b = 255; + poly.g4->color[3].a = shield_alpha; + poly.g4 += 1; + break; + } + } +} + + +void weapon_fire_turbo(ship_t *ship) { + ship->velocity = vec3_add(ship->velocity, vec3_mulf(ship->dir_forward, 39321)); // unitVecNose.vx) << 3) * FR60) / 50 + + if (ship->pilot == g.pilot) { + sfx_t *sfx = sfx_play(SFX_MISSILE_FIRE); + sfx->pitch = 0.25; + } +} + +int weapon_get_random_type(int type_class) { + if (type_class == WEAPON_CLASS_ANY) { + int index = rand_int(0, 65); + if (index < 17) { + return WEAPON_TYPE_ROCKET; + } + else if (index < 35) { + return WEAPON_TYPE_MINE; + } + else if (index < 45) { + return WEAPON_TYPE_SHIELD; + } + else if (index < 53) { + return WEAPON_TYPE_MISSILE; + } + else if (index < 59) { + return WEAPON_TYPE_TURBO; + } + else { + return WEAPON_TYPE_EBOLT; + } + } + else if (type_class == WEAPON_CLASS_PROJECTILE) { + int index = rand_int(0, 60); + if (index < 27) { + return WEAPON_TYPE_ROCKET; + } + else if (index < 40) { + return WEAPON_TYPE_MISSILE; + } + else if (index < 50) { + return WEAPON_TYPE_TURBO; + } + else { + return WEAPON_TYPE_EBOLT; + } + } + else { + die("Unknown WEAPON_CLASS_ %d", type_class); + } +} + diff --git a/src/wipeout/weapon.h b/src/wipeout/weapon.h index 9cfccdb..17b3f8d 100755 --- a/src/wipeout/weapon.h +++ b/src/wipeout/weapon.h @@ -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