add Dockerfile
This commit is contained in:
+299
@@ -0,0 +1,299 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "input.h"
|
||||
#include "utils.h"
|
||||
|
||||
static const char *button_names[] = {
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
[INPUT_KEY_A] = "A",
|
||||
[INPUT_KEY_B] = "B",
|
||||
[INPUT_KEY_C] = "C",
|
||||
[INPUT_KEY_D] = "D",
|
||||
[INPUT_KEY_E] = "E",
|
||||
[INPUT_KEY_F] = "F",
|
||||
[INPUT_KEY_G] = "G",
|
||||
[INPUT_KEY_H] = "H",
|
||||
[INPUT_KEY_I] = "I",
|
||||
[INPUT_KEY_J] = "J",
|
||||
[INPUT_KEY_K] = "K",
|
||||
[INPUT_KEY_L] = "L",
|
||||
[INPUT_KEY_M] = "M",
|
||||
[INPUT_KEY_N] = "N",
|
||||
[INPUT_KEY_O] = "O",
|
||||
[INPUT_KEY_P] = "P",
|
||||
[INPUT_KEY_Q] = "Q",
|
||||
[INPUT_KEY_R] = "R",
|
||||
[INPUT_KEY_S] = "S",
|
||||
[INPUT_KEY_T] = "T",
|
||||
[INPUT_KEY_U] = "U",
|
||||
[INPUT_KEY_V] = "V",
|
||||
[INPUT_KEY_W] = "W",
|
||||
[INPUT_KEY_X] = "X",
|
||||
[INPUT_KEY_Y] = "Y",
|
||||
[INPUT_KEY_Z] = "Z",
|
||||
[INPUT_KEY_1] = "1",
|
||||
[INPUT_KEY_2] = "2",
|
||||
[INPUT_KEY_3] = "3",
|
||||
[INPUT_KEY_4] = "4",
|
||||
[INPUT_KEY_5] = "5",
|
||||
[INPUT_KEY_6] = "6",
|
||||
[INPUT_KEY_7] = "7",
|
||||
[INPUT_KEY_8] = "8",
|
||||
[INPUT_KEY_9] = "9",
|
||||
[INPUT_KEY_0] = "0",
|
||||
[INPUT_KEY_RETURN] = "RETURN",
|
||||
[INPUT_KEY_ESCAPE] = "ESCAPE",
|
||||
[INPUT_KEY_BACKSPACE] = "BACKSP",
|
||||
[INPUT_KEY_TAB] = "TAB",
|
||||
[INPUT_KEY_SPACE] = "SPACE",
|
||||
[INPUT_KEY_MINUS] = "MINUS",
|
||||
[INPUT_KEY_EQUALS] = "EQUALS",
|
||||
[INPUT_KEY_LEFTBRACKET] = "LBRACKET",
|
||||
[INPUT_KEY_RIGHTBRACKET] = "RBRACKET",
|
||||
[INPUT_KEY_BACKSLASH] = "BSLASH",
|
||||
[INPUT_KEY_HASH] = "HASH",
|
||||
[INPUT_KEY_SEMICOLON] = "SMICOL",
|
||||
[INPUT_KEY_APOSTROPHE] = "APO",
|
||||
[INPUT_KEY_TILDE] = "TILDE",
|
||||
[INPUT_KEY_COMMA] = "COMMA",
|
||||
[INPUT_KEY_PERIOD] = "PERIOD",
|
||||
[INPUT_KEY_SLASH] = "SLASH",
|
||||
[INPUT_KEY_CAPSLOCK] = "CAPS",
|
||||
[INPUT_KEY_F1] = "F1",
|
||||
[INPUT_KEY_F2] = "F2",
|
||||
[INPUT_KEY_F3] = "F3",
|
||||
[INPUT_KEY_F4] = "F4",
|
||||
[INPUT_KEY_F5] = "F5",
|
||||
[INPUT_KEY_F6] = "F6",
|
||||
[INPUT_KEY_F7] = "F7",
|
||||
[INPUT_KEY_F8] = "F8",
|
||||
[INPUT_KEY_F9] = "F9",
|
||||
[INPUT_KEY_F10] = "F10",
|
||||
[INPUT_KEY_F11] = "F11",
|
||||
[INPUT_KEY_F12] = "F12",
|
||||
[INPUT_KEY_PRINTSCREEN] = "PRTSC",
|
||||
[INPUT_KEY_SCROLLLOCK] = "SCRLK",
|
||||
[INPUT_KEY_PAUSE] = "PAUSE",
|
||||
[INPUT_KEY_INSERT] = "INSERT",
|
||||
[INPUT_KEY_HOME] = "HOME",
|
||||
[INPUT_KEY_PAGEUP] = "PG UP",
|
||||
[INPUT_KEY_DELETE] = "DELETE",
|
||||
[INPUT_KEY_END] = "END",
|
||||
[INPUT_KEY_PAGEDOWN] = "PG DOWN",
|
||||
[INPUT_KEY_RIGHT] = "RIGHT",
|
||||
[INPUT_KEY_LEFT] = "LEFT",
|
||||
[INPUT_KEY_DOWN] = "DOWN",
|
||||
[INPUT_KEY_UP] = "UP",
|
||||
[INPUT_KEY_NUMLOCK] = "NLOCK",
|
||||
[INPUT_KEY_KP_DIVIDE] = "KPDIV",
|
||||
[INPUT_KEY_KP_MULTIPLY] = "KPMUL",
|
||||
[INPUT_KEY_KP_MINUS] = "KPMINUS",
|
||||
[INPUT_KEY_KP_PLUS] = "KPPLUS",
|
||||
[INPUT_KEY_KP_ENTER] = "KPENTER",
|
||||
[INPUT_KEY_KP_1] = "KP1",
|
||||
[INPUT_KEY_KP_2] = "KP2",
|
||||
[INPUT_KEY_KP_3] = "KP3",
|
||||
[INPUT_KEY_KP_4] = "KP4",
|
||||
[INPUT_KEY_KP_5] = "KP5",
|
||||
[INPUT_KEY_KP_6] = "KP6",
|
||||
[INPUT_KEY_KP_7] = "KP7",
|
||||
[INPUT_KEY_KP_8] = "KP8",
|
||||
[INPUT_KEY_KP_9] = "KP9",
|
||||
[INPUT_KEY_KP_0] = "KP0",
|
||||
[INPUT_KEY_KP_PERIOD] = "KPPERIOD",
|
||||
|
||||
[INPUT_KEY_LCTRL] = "LCTRL",
|
||||
[INPUT_KEY_LSHIFT] = "LSHIFT",
|
||||
[INPUT_KEY_LALT] = "LALT",
|
||||
[INPUT_KEY_LGUI] = "LGUI",
|
||||
[INPUT_KEY_RCTRL] = "RCTRL",
|
||||
[INPUT_KEY_RSHIFT] = "RSHIFT",
|
||||
[INPUT_KEY_RALT] = "RALT",
|
||||
NULL,
|
||||
[INPUT_GAMEPAD_A] = "A",
|
||||
[INPUT_GAMEPAD_Y] = "Y",
|
||||
[INPUT_GAMEPAD_B] = "B",
|
||||
[INPUT_GAMEPAD_X] = "X",
|
||||
[INPUT_GAMEPAD_L_SHOULDER] = "LSHLDR",
|
||||
[INPUT_GAMEPAD_R_SHOULDER] = "RSHLDR",
|
||||
[INPUT_GAMEPAD_L_TRIGGER] = "LTRIG",
|
||||
[INPUT_GAMEPAD_R_TRIGGER] = "RTRIG",
|
||||
[INPUT_GAMEPAD_SELECT] = "SELECT",
|
||||
[INPUT_GAMEPAD_START] = "START",
|
||||
[INPUT_GAMEPAD_L_STICK_PRESS] = "LSTK",
|
||||
[INPUT_GAMEPAD_R_STICK_PRESS] = "RSTK",
|
||||
[INPUT_GAMEPAD_DPAD_UP] = "DPUP",
|
||||
[INPUT_GAMEPAD_DPAD_DOWN] = "DPDOWN",
|
||||
[INPUT_GAMEPAD_DPAD_LEFT] = "DPLEFT",
|
||||
[INPUT_GAMEPAD_DPAD_RIGHT] = "DPRIGHT",
|
||||
[INPUT_GAMEPAD_HOME] = "HOME",
|
||||
[INPUT_GAMEPAD_L_STICK_UP] = "LSTKUP",
|
||||
[INPUT_GAMEPAD_L_STICK_DOWN] = "LSTKDOWN",
|
||||
[INPUT_GAMEPAD_L_STICK_LEFT] = "LSTKLEFT",
|
||||
[INPUT_GAMEPAD_L_STICK_RIGHT] = "LSTKRIGHT",
|
||||
[INPUT_GAMEPAD_R_STICK_UP] = "RSTKUP",
|
||||
[INPUT_GAMEPAD_R_STICK_DOWN] = "RSTKDOWN",
|
||||
[INPUT_GAMEPAD_R_STICK_LEFT] = "RSTKLEFT",
|
||||
[INPUT_GAMEPAD_R_STICK_RIGHT] = "RSTKRIGHT",
|
||||
NULL,
|
||||
[INPUT_MOUSE_LEFT] = "MLEFT",
|
||||
[INPUT_MOUSE_MIDDLE] = "MMIDDLE",
|
||||
[INPUT_MOUSE_RIGHT] = "MRIGHT",
|
||||
[INPUT_MOUSE_WHEEL_UP] = "MWUP",
|
||||
[INPUT_MOUSE_WHEEL_DOWN] = "MWDOWN",
|
||||
};
|
||||
|
||||
static float actions_state[INPUT_ACTION_MAX];
|
||||
static bool actions_pressed[INPUT_ACTION_MAX];
|
||||
static bool actions_released[INPUT_ACTION_MAX];
|
||||
|
||||
static uint8_t expected_button[INPUT_ACTION_MAX];
|
||||
static uint8_t bindings[INPUT_LAYER_MAX][INPUT_BUTTON_MAX];
|
||||
|
||||
static input_capture_callback_t capture_callback;
|
||||
static void *capture_user;
|
||||
|
||||
static int32_t mouse_x;
|
||||
static int32_t mouse_y;
|
||||
|
||||
void input_init() {
|
||||
input_unbind_all(INPUT_LAYER_SYSTEM);
|
||||
input_unbind_all(INPUT_LAYER_USER);
|
||||
}
|
||||
|
||||
void input_cleanup() {
|
||||
|
||||
}
|
||||
|
||||
void input_clear() {
|
||||
clear(actions_pressed);
|
||||
clear(actions_released);
|
||||
}
|
||||
|
||||
void input_set_layer_button_state(input_layer_t layer, button_t button, float state) {
|
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer);
|
||||
|
||||
uint8_t action = bindings[layer][button];
|
||||
if (action == INPUT_ACTION_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t expected = expected_button[action];
|
||||
if (!expected || expected == button) {
|
||||
state = (state > INPUT_DEADZONE) ? state : 0;
|
||||
|
||||
if (state && !actions_state[action]) {
|
||||
actions_pressed[action] = true;
|
||||
expected_button[action] = button;
|
||||
}
|
||||
else if (!state && actions_state[action]) {
|
||||
actions_released[action] = true;
|
||||
expected_button[action] = INPUT_BUTTON_NONE;
|
||||
}
|
||||
actions_state[action] = state;
|
||||
}
|
||||
}
|
||||
|
||||
void input_set_button_state(button_t button, float state) {
|
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button);
|
||||
|
||||
input_set_layer_button_state(INPUT_LAYER_SYSTEM, button, state);
|
||||
input_set_layer_button_state(INPUT_LAYER_USER, button, state);
|
||||
|
||||
if (capture_callback) {
|
||||
if (state > INPUT_DEADZONE_CAPTURE) {
|
||||
capture_callback(capture_user, button, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void input_set_mouse_pos(int32_t x, int32_t y) {
|
||||
mouse_x = x;
|
||||
mouse_y = y;
|
||||
}
|
||||
|
||||
void input_capture(input_capture_callback_t cb, void *user) {
|
||||
capture_callback = cb;
|
||||
capture_user = user;
|
||||
input_clear();
|
||||
}
|
||||
|
||||
void input_textinput(int32_t ascii_char) {
|
||||
if (capture_callback) {
|
||||
capture_callback(capture_user, INPUT_INVALID, ascii_char);
|
||||
}
|
||||
}
|
||||
|
||||
void input_bind(input_layer_t layer, button_t button, uint8_t action) {
|
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button);
|
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action);
|
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer);
|
||||
|
||||
actions_state[action] = 0;
|
||||
bindings[layer][button] = action;
|
||||
}
|
||||
|
||||
uint8_t input_bound_to_action(button_t button) {
|
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button);
|
||||
return bindings[INPUT_LAYER_USER][button];
|
||||
}
|
||||
|
||||
void input_unbind(input_layer_t layer, button_t button) {
|
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer);
|
||||
error_if(button < 0 || button >= INPUT_BUTTON_MAX, "Invalid input button %d", button);
|
||||
|
||||
bindings[layer][button] = INPUT_ACTION_NONE;
|
||||
}
|
||||
|
||||
void input_unbind_all(input_layer_t layer) {
|
||||
error_if(layer < 0 || layer >= INPUT_LAYER_MAX, "Invalid input layer %d", layer);
|
||||
|
||||
for (uint32_t button = 0; button < INPUT_BUTTON_MAX; button++) {
|
||||
input_unbind(layer, button);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float input_state(uint8_t action) {
|
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action);
|
||||
return actions_state[action];
|
||||
}
|
||||
|
||||
|
||||
bool input_pressed(uint8_t action) {
|
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action);
|
||||
return actions_pressed[action];
|
||||
}
|
||||
|
||||
|
||||
bool input_released(uint8_t action) {
|
||||
error_if(action < 0 || action >= INPUT_ACTION_MAX, "Invalid input action %d", action);
|
||||
return actions_released[action];
|
||||
}
|
||||
|
||||
vec2_t input_mouse_pos() {
|
||||
return vec2(mouse_x, mouse_y);
|
||||
}
|
||||
|
||||
|
||||
button_t input_name_to_button(const char *name) {
|
||||
for (int32_t i = 0; i < INPUT_BUTTON_MAX; i++) {
|
||||
if (button_names[i] && strcmp(name, button_names[i]) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return INPUT_INVALID;
|
||||
}
|
||||
|
||||
const char *input_button_to_name(button_t button) {
|
||||
if (
|
||||
button < 0 || button >= INPUT_BUTTON_MAX ||
|
||||
!button_names[button]
|
||||
) {
|
||||
return NULL;
|
||||
}
|
||||
return button_names[button];
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
#ifndef INPUT_H
|
||||
#define INPUT_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
typedef enum {
|
||||
INPUT_INVALID = 0,
|
||||
INPUT_KEY_A = 4,
|
||||
INPUT_KEY_B = 5,
|
||||
INPUT_KEY_C = 6,
|
||||
INPUT_KEY_D = 7,
|
||||
INPUT_KEY_E = 8,
|
||||
INPUT_KEY_F = 9,
|
||||
INPUT_KEY_G = 10,
|
||||
INPUT_KEY_H = 11,
|
||||
INPUT_KEY_I = 12,
|
||||
INPUT_KEY_J = 13,
|
||||
INPUT_KEY_K = 14,
|
||||
INPUT_KEY_L = 15,
|
||||
INPUT_KEY_M = 16,
|
||||
INPUT_KEY_N = 17,
|
||||
INPUT_KEY_O = 18,
|
||||
INPUT_KEY_P = 19,
|
||||
INPUT_KEY_Q = 20,
|
||||
INPUT_KEY_R = 21,
|
||||
INPUT_KEY_S = 22,
|
||||
INPUT_KEY_T = 23,
|
||||
INPUT_KEY_U = 24,
|
||||
INPUT_KEY_V = 25,
|
||||
INPUT_KEY_W = 26,
|
||||
INPUT_KEY_X = 27,
|
||||
INPUT_KEY_Y = 28,
|
||||
INPUT_KEY_Z = 29,
|
||||
INPUT_KEY_1 = 30,
|
||||
INPUT_KEY_2 = 31,
|
||||
INPUT_KEY_3 = 32,
|
||||
INPUT_KEY_4 = 33,
|
||||
INPUT_KEY_5 = 34,
|
||||
INPUT_KEY_6 = 35,
|
||||
INPUT_KEY_7 = 36,
|
||||
INPUT_KEY_8 = 37,
|
||||
INPUT_KEY_9 = 38,
|
||||
INPUT_KEY_0 = 39,
|
||||
INPUT_KEY_RETURN = 40,
|
||||
INPUT_KEY_ESCAPE = 41,
|
||||
INPUT_KEY_BACKSPACE = 42,
|
||||
INPUT_KEY_TAB = 43,
|
||||
INPUT_KEY_SPACE = 44,
|
||||
INPUT_KEY_MINUS = 45,
|
||||
INPUT_KEY_EQUALS = 46,
|
||||
INPUT_KEY_LEFTBRACKET = 47,
|
||||
INPUT_KEY_RIGHTBRACKET = 48,
|
||||
INPUT_KEY_BACKSLASH = 49,
|
||||
INPUT_KEY_HASH = 50,
|
||||
INPUT_KEY_SEMICOLON = 51,
|
||||
INPUT_KEY_APOSTROPHE = 52,
|
||||
INPUT_KEY_TILDE = 53,
|
||||
INPUT_KEY_COMMA = 54,
|
||||
INPUT_KEY_PERIOD = 55,
|
||||
INPUT_KEY_SLASH = 56,
|
||||
INPUT_KEY_CAPSLOCK = 57,
|
||||
INPUT_KEY_F1 = 58,
|
||||
INPUT_KEY_F2 = 59,
|
||||
INPUT_KEY_F3 = 60,
|
||||
INPUT_KEY_F4 = 61,
|
||||
INPUT_KEY_F5 = 62,
|
||||
INPUT_KEY_F6 = 63,
|
||||
INPUT_KEY_F7 = 64,
|
||||
INPUT_KEY_F8 = 65,
|
||||
INPUT_KEY_F9 = 66,
|
||||
INPUT_KEY_F10 = 67,
|
||||
INPUT_KEY_F11 = 68,
|
||||
INPUT_KEY_F12 = 69,
|
||||
INPUT_KEY_PRINTSCREEN = 70,
|
||||
INPUT_KEY_SCROLLLOCK = 71,
|
||||
INPUT_KEY_PAUSE = 72,
|
||||
INPUT_KEY_INSERT = 73,
|
||||
INPUT_KEY_HOME = 74,
|
||||
INPUT_KEY_PAGEUP = 75,
|
||||
INPUT_KEY_DELETE = 76,
|
||||
INPUT_KEY_END = 77,
|
||||
INPUT_KEY_PAGEDOWN = 78,
|
||||
INPUT_KEY_RIGHT = 79,
|
||||
INPUT_KEY_LEFT = 80,
|
||||
INPUT_KEY_DOWN = 81,
|
||||
INPUT_KEY_UP = 82,
|
||||
INPUT_KEY_NUMLOCK = 83,
|
||||
INPUT_KEY_KP_DIVIDE = 84,
|
||||
INPUT_KEY_KP_MULTIPLY = 85,
|
||||
INPUT_KEY_KP_MINUS = 86,
|
||||
INPUT_KEY_KP_PLUS = 87,
|
||||
INPUT_KEY_KP_ENTER = 88,
|
||||
INPUT_KEY_KP_1 = 89,
|
||||
INPUT_KEY_KP_2 = 90,
|
||||
INPUT_KEY_KP_3 = 91,
|
||||
INPUT_KEY_KP_4 = 92,
|
||||
INPUT_KEY_KP_5 = 93,
|
||||
INPUT_KEY_KP_6 = 94,
|
||||
INPUT_KEY_KP_7 = 95,
|
||||
INPUT_KEY_KP_8 = 96,
|
||||
INPUT_KEY_KP_9 = 97,
|
||||
INPUT_KEY_KP_0 = 98,
|
||||
INPUT_KEY_KP_PERIOD = 99,
|
||||
|
||||
INPUT_KEY_LCTRL = 100,
|
||||
INPUT_KEY_LSHIFT = 101,
|
||||
INPUT_KEY_LALT = 102,
|
||||
INPUT_KEY_LGUI = 103,
|
||||
INPUT_KEY_RCTRL = 104,
|
||||
INPUT_KEY_RSHIFT = 105,
|
||||
INPUT_KEY_RALT = 106,
|
||||
|
||||
INPUT_KEY_MAX = 107,
|
||||
|
||||
INPUT_GAMEPAD_A = 108,
|
||||
INPUT_GAMEPAD_Y = 109,
|
||||
INPUT_GAMEPAD_B = 110,
|
||||
INPUT_GAMEPAD_X = 111,
|
||||
INPUT_GAMEPAD_L_SHOULDER = 112,
|
||||
INPUT_GAMEPAD_R_SHOULDER = 113,
|
||||
INPUT_GAMEPAD_L_TRIGGER = 114,
|
||||
INPUT_GAMEPAD_R_TRIGGER = 115,
|
||||
INPUT_GAMEPAD_SELECT = 116,
|
||||
INPUT_GAMEPAD_START = 117,
|
||||
INPUT_GAMEPAD_L_STICK_PRESS = 118,
|
||||
INPUT_GAMEPAD_R_STICK_PRESS = 119,
|
||||
INPUT_GAMEPAD_DPAD_UP = 120,
|
||||
INPUT_GAMEPAD_DPAD_DOWN = 121,
|
||||
INPUT_GAMEPAD_DPAD_LEFT = 122,
|
||||
INPUT_GAMEPAD_DPAD_RIGHT = 123,
|
||||
INPUT_GAMEPAD_HOME = 124,
|
||||
INPUT_GAMEPAD_L_STICK_UP = 125,
|
||||
INPUT_GAMEPAD_L_STICK_DOWN = 126,
|
||||
INPUT_GAMEPAD_L_STICK_LEFT = 127,
|
||||
INPUT_GAMEPAD_L_STICK_RIGHT = 128,
|
||||
INPUT_GAMEPAD_R_STICK_UP = 129,
|
||||
INPUT_GAMEPAD_R_STICK_DOWN = 130,
|
||||
INPUT_GAMEPAD_R_STICK_LEFT = 131,
|
||||
INPUT_GAMEPAD_R_STICK_RIGHT = 132,
|
||||
|
||||
INPUT_MOUSE_LEFT = 134,
|
||||
INPUT_MOUSE_MIDDLE = 135,
|
||||
INPUT_MOUSE_RIGHT = 136,
|
||||
INPUT_MOUSE_WHEEL_UP = 137,
|
||||
INPUT_MOUSE_WHEEL_DOWN = 138,
|
||||
|
||||
INPUT_BUTTON_MAX = 139
|
||||
} button_t;
|
||||
|
||||
typedef enum {
|
||||
INPUT_LAYER_SYSTEM = 0,
|
||||
INPUT_LAYER_USER = 1,
|
||||
INPUT_LAYER_MAX = 2
|
||||
} input_layer_t;
|
||||
|
||||
typedef void(*input_capture_callback_t)
|
||||
(void *user, button_t button, int32_t ascii_char);
|
||||
|
||||
void input_init();
|
||||
void input_cleanup();
|
||||
void input_clear();
|
||||
|
||||
void input_bind(input_layer_t layer, button_t button, uint8_t action);
|
||||
void input_unbind(input_layer_t layer,button_t button);
|
||||
void input_unbind_all(input_layer_t layer);
|
||||
|
||||
uint8_t input_bound_to_action(button_t button);
|
||||
|
||||
void input_set_button_state(button_t button, float state);
|
||||
void input_set_mouse_pos(int32_t x, int32_t y);
|
||||
void input_textinput(int32_t ascii_char);
|
||||
|
||||
void input_capture(input_capture_callback_t cb, void *user);
|
||||
|
||||
float input_state(uint8_t action);
|
||||
bool input_pressed(uint8_t action);
|
||||
bool input_released(uint8_t action);
|
||||
vec2_t input_mouse_pos();
|
||||
|
||||
button_t input_name_to_button(const char *name);
|
||||
const char *input_button_to_name(button_t button);
|
||||
|
||||
|
||||
#define INPUT_ACTION_COMMAND 31
|
||||
#define INPUT_ACTION_MAX 32
|
||||
#define INPUT_DEADZONE 0.1
|
||||
#define INPUT_DEADZONE_CAPTURE 0.5
|
||||
#define INPUT_ACTION_NONE 255
|
||||
#define INPUT_BUTTON_NONE 0
|
||||
|
||||
#endif
|
||||
+4274
File diff suppressed because it is too large
Load Diff
+728
@@ -0,0 +1,728 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2023, Dominic Szablewski - https://phoboslab.org
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
QOA - The "Quite OK Audio" format for fast, lossy audio compression
|
||||
|
||||
|
||||
-- Data Format
|
||||
|
||||
QOA encodes pulse-code modulated (PCM) audio data with up to 255 channels,
|
||||
sample rates from 1 up to 16777215 hertz and a bit depth of 16 bits.
|
||||
|
||||
The compression method employed in QOA is lossy; it discards some information
|
||||
from the uncompressed PCM data. For many types of audio signals this compression
|
||||
is "transparent", i.e. the difference from the original file is often not
|
||||
audible.
|
||||
|
||||
QOA encodes 20 samples of 16 bit PCM data into slices of 64 bits. A single
|
||||
sample therefore requires 3.2 bits of storage space, resulting in a 5x
|
||||
compression (16 / 3.2).
|
||||
|
||||
A QOA file consists of an 8 byte file header, followed by a number of frames.
|
||||
Each frame contains an 8 byte frame header, the current 16 byte en-/decoder
|
||||
state per channel and 256 slices per channel. Each slice is 8 bytes wide and
|
||||
encodes 20 samples of audio data.
|
||||
|
||||
All values, including the slices, are big endian. The file layout is as follows:
|
||||
|
||||
struct {
|
||||
struct {
|
||||
char magic[4]; // magic bytes "qoaf"
|
||||
uint32_t samples; // samples per channel in this file
|
||||
} file_header;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
uint8_t num_channels; // no. of channels
|
||||
uint24_t samplerate; // samplerate in hz
|
||||
uint16_t fsamples; // samples per channel in this frame
|
||||
uint16_t fsize; // frame size (includes this header)
|
||||
} frame_header;
|
||||
|
||||
struct {
|
||||
int16_t history[4]; // most recent last
|
||||
int16_t weights[4]; // most recent last
|
||||
} lms_state[num_channels];
|
||||
|
||||
qoa_slice_t slices[256][num_channels];
|
||||
|
||||
} frames[ceil(samples / (256 * 20))];
|
||||
} qoa_file_t;
|
||||
|
||||
Each `qoa_slice_t` contains a quantized scalefactor `sf_quant` and 20 quantized
|
||||
residuals `qrNN`:
|
||||
|
||||
.- QOA_SLICE -- 64 bits, 20 samples --------------------------/ /------------.
|
||||
| Byte[0] | Byte[1] | Byte[2] \ \ Byte[7] |
|
||||
| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 / / 2 1 0 |
|
||||
|------------+--------+--------+--------+---------+---------+-\ \--+---------|
|
||||
| sf_quant | qr00 | qr01 | qr02 | qr03 | qr04 | / / | qr19 |
|
||||
`-------------------------------------------------------------\ \------------`
|
||||
|
||||
Each frame except the last must contain exactly 256 slices per channel. The last
|
||||
frame may contain between 1 .. 256 (inclusive) slices per channel. The last
|
||||
slice (for each channel) in the last frame may contain less than 20 samples; the
|
||||
slice still must be 8 bytes wide, with the unused samples zeroed out.
|
||||
|
||||
Channels are interleaved per slice. E.g. for 2 channel stereo:
|
||||
slice[0] = L, slice[1] = R, slice[2] = L, slice[3] = R ...
|
||||
|
||||
A valid QOA file or stream must have at least one frame. Each frame must contain
|
||||
at least one channel and one sample with a samplerate between 1 .. 16777215
|
||||
(inclusive).
|
||||
|
||||
If the total number of samples is not known by the encoder, the samples in the
|
||||
file header may be set to 0x00000000 to indicate that the encoder is
|
||||
"streaming". In a streaming context, the samplerate and number of channels may
|
||||
differ from frame to frame. For static files (those with samples set to a
|
||||
non-zero value), each frame must have the same number of channels and same
|
||||
samplerate.
|
||||
|
||||
Note that this implementation of QOA only handles files with a known total
|
||||
number of samples.
|
||||
|
||||
A decoder should support at least 8 channels. The channel layout for channel
|
||||
counts 1 .. 8 is:
|
||||
|
||||
1. Mono
|
||||
2. L, R
|
||||
3. L, R, C
|
||||
4. FL, FR, B/SL, B/SR
|
||||
5. FL, FR, C, B/SL, B/SR
|
||||
6. FL, FR, C, LFE, B/SL, B/SR
|
||||
7. FL, FR, C, LFE, B, SL, SR
|
||||
8. FL, FR, C, LFE, BL, BR, SL, SR
|
||||
|
||||
QOA predicts each audio sample based on the previously decoded ones using a
|
||||
"Sign-Sign Least Mean Squares Filter" (LMS). This prediction plus the
|
||||
dequantized residual forms the final output sample.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Header - Public functions */
|
||||
|
||||
#ifndef QOA_H
|
||||
#define QOA_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define QOA_MIN_FILESIZE 16
|
||||
#define QOA_MAX_CHANNELS 8
|
||||
|
||||
#define QOA_SLICE_LEN 20
|
||||
#define QOA_SLICES_PER_FRAME 256
|
||||
#define QOA_FRAME_LEN (QOA_SLICES_PER_FRAME * QOA_SLICE_LEN)
|
||||
#define QOA_LMS_LEN 4
|
||||
#define QOA_MAGIC 0x716f6166 /* 'qoaf' */
|
||||
|
||||
#define QOA_FRAME_SIZE(channels, slices) \
|
||||
(8 + QOA_LMS_LEN * 4 * channels + 8 * slices * channels)
|
||||
|
||||
typedef struct {
|
||||
int history[QOA_LMS_LEN];
|
||||
int weights[QOA_LMS_LEN];
|
||||
} qoa_lms_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned int channels;
|
||||
unsigned int samplerate;
|
||||
unsigned int samples;
|
||||
qoa_lms_t lms[QOA_MAX_CHANNELS];
|
||||
#ifdef QOA_RECORD_TOTAL_ERROR
|
||||
double error;
|
||||
#endif
|
||||
} qoa_desc;
|
||||
|
||||
unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes);
|
||||
unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes);
|
||||
void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len);
|
||||
|
||||
unsigned int qoa_max_frame_size(qoa_desc *qoa);
|
||||
unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa);
|
||||
unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len);
|
||||
short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file);
|
||||
|
||||
#ifndef QOA_NO_STDIO
|
||||
|
||||
int qoa_write(const char *filename, const short *sample_data, qoa_desc *qoa);
|
||||
void *qoa_read(const char *filename, qoa_desc *qoa);
|
||||
|
||||
#endif /* QOA_NO_STDIO */
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* QOA_H */
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Implementation */
|
||||
|
||||
#ifdef QOA_IMPLEMENTATION
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef QOA_MALLOC
|
||||
#define QOA_MALLOC(sz) malloc(sz)
|
||||
#define QOA_FREE(p) free(p)
|
||||
#endif
|
||||
|
||||
typedef unsigned long long qoa_uint64_t;
|
||||
|
||||
|
||||
/* The quant_tab provides an index into the dequant_tab for residuals in the
|
||||
range of -8 .. 8. It maps this range to just 3bits and becomes less accurate at
|
||||
the higher end. Note that the residual zero is identical to the lowest positive
|
||||
value. This is mostly fine, since the qoa_div() function always rounds away
|
||||
from zero. */
|
||||
|
||||
static const int qoa_quant_tab[17] = {
|
||||
7, 7, 7, 5, 5, 3, 3, 1, /* -8..-1 */
|
||||
0, /* 0 */
|
||||
0, 2, 2, 4, 4, 6, 6, 6 /* 1.. 8 */
|
||||
};
|
||||
|
||||
|
||||
/* We have 16 different scalefactors. Like the quantized residuals these become
|
||||
less accurate at the higher end. In theory, the highest scalefactor that we
|
||||
would need to encode the highest 16bit residual is (2**16)/8 = 8192. However we
|
||||
rely on the LMS filter to predict samples accurately enough that a maximum
|
||||
residual of one quarter of the 16 bit range is sufficient. I.e. with the
|
||||
scalefactor 2048 times the quant range of 8 we can encode residuals up to 2**14.
|
||||
|
||||
The scalefactor values are computed as:
|
||||
scalefactor_tab[s] <- round(pow(s + 1, 2.75)) */
|
||||
|
||||
static const int qoa_scalefactor_tab[16] = {
|
||||
1, 7, 21, 45, 84, 138, 211, 304, 421, 562, 731, 928, 1157, 1419, 1715, 2048
|
||||
};
|
||||
|
||||
|
||||
/* The reciprocal_tab maps each of the 16 scalefactors to their rounded
|
||||
reciprocals 1/scalefactor. This allows us to calculate the scaled residuals in
|
||||
the encoder with just one multiplication instead of an expensive division. We
|
||||
do this in .16 fixed point with integers, instead of floats.
|
||||
|
||||
The reciprocal_tab is computed as:
|
||||
reciprocal_tab[s] <- ((1<<16) + scalefactor_tab[s] - 1) / scalefactor_tab[s] */
|
||||
|
||||
static const int qoa_reciprocal_tab[16] = {
|
||||
65536, 9363, 3121, 1457, 781, 475, 311, 216, 156, 117, 90, 71, 57, 47, 39, 32
|
||||
};
|
||||
|
||||
|
||||
/* The dequant_tab maps each of the scalefactors and quantized residuals to
|
||||
their unscaled & dequantized version.
|
||||
|
||||
Since qoa_div rounds away from the zero, the smallest entries are mapped to 3/4
|
||||
instead of 1. The dequant_tab assumes the following dequantized values for each
|
||||
of the quant_tab indices and is computed as:
|
||||
float dqt[8] = {0.75, -0.75, 2.5, -2.5, 4.5, -4.5, 7, -7};
|
||||
dequant_tab[s][q] <- round_ties_away_from_zero(scalefactor_tab[s] * dqt[q])
|
||||
|
||||
The rounding employed here is "to nearest, ties away from zero", i.e. positive
|
||||
and negative values are treated symmetrically.
|
||||
*/
|
||||
|
||||
static const int qoa_dequant_tab[16][8] = {
|
||||
{ 1, -1, 3, -3, 5, -5, 7, -7},
|
||||
{ 5, -5, 18, -18, 32, -32, 49, -49},
|
||||
{ 16, -16, 53, -53, 95, -95, 147, -147},
|
||||
{ 34, -34, 113, -113, 203, -203, 315, -315},
|
||||
{ 63, -63, 210, -210, 378, -378, 588, -588},
|
||||
{ 104, -104, 345, -345, 621, -621, 966, -966},
|
||||
{ 158, -158, 528, -528, 950, -950, 1477, -1477},
|
||||
{ 228, -228, 760, -760, 1368, -1368, 2128, -2128},
|
||||
{ 316, -316, 1053, -1053, 1895, -1895, 2947, -2947},
|
||||
{ 422, -422, 1405, -1405, 2529, -2529, 3934, -3934},
|
||||
{ 548, -548, 1828, -1828, 3290, -3290, 5117, -5117},
|
||||
{ 696, -696, 2320, -2320, 4176, -4176, 6496, -6496},
|
||||
{ 868, -868, 2893, -2893, 5207, -5207, 8099, -8099},
|
||||
{1064, -1064, 3548, -3548, 6386, -6386, 9933, -9933},
|
||||
{1286, -1286, 4288, -4288, 7718, -7718, 12005, -12005},
|
||||
{1536, -1536, 5120, -5120, 9216, -9216, 14336, -14336},
|
||||
};
|
||||
|
||||
|
||||
/* The Least Mean Squares Filter is the heart of QOA. It predicts the next
|
||||
sample based on the previous 4 reconstructed samples. It does so by continuously
|
||||
adjusting 4 weights based on the residual of the previous prediction.
|
||||
|
||||
The next sample is predicted as the sum of (weight[i] * history[i]).
|
||||
|
||||
The adjustment of the weights is done with a "Sign-Sign-LMS" that adds or
|
||||
subtracts the residual to each weight, based on the corresponding sample from
|
||||
the history. This, surprisingly, is sufficient to get worthwhile predictions.
|
||||
|
||||
This is all done with fixed point integers. Hence the right-shifts when updating
|
||||
the weights and calculating the prediction. */
|
||||
|
||||
static int qoa_lms_predict(qoa_lms_t *lms) {
|
||||
int prediction = 0;
|
||||
for (int i = 0; i < QOA_LMS_LEN; i++) {
|
||||
prediction += lms->weights[i] * lms->history[i];
|
||||
}
|
||||
return prediction >> 13;
|
||||
}
|
||||
|
||||
static void qoa_lms_update(qoa_lms_t *lms, int sample, int residual) {
|
||||
int delta = residual >> 4;
|
||||
for (int i = 0; i < QOA_LMS_LEN; i++) {
|
||||
lms->weights[i] += lms->history[i] < 0 ? -delta : delta;
|
||||
}
|
||||
|
||||
for (int i = 0; i < QOA_LMS_LEN-1; i++) {
|
||||
lms->history[i] = lms->history[i+1];
|
||||
}
|
||||
lms->history[QOA_LMS_LEN-1] = sample;
|
||||
}
|
||||
|
||||
|
||||
/* qoa_div() implements a rounding division, but avoids rounding to zero for
|
||||
small numbers. E.g. 0.1 will be rounded to 1. Note that 0 itself still
|
||||
returns as 0, which is handled in the qoa_quant_tab[].
|
||||
qoa_div() takes an index into the .16 fixed point qoa_reciprocal_tab as an
|
||||
argument, so it can do the division with a cheaper integer multiplication. */
|
||||
|
||||
static inline int qoa_div(int v, int scalefactor) {
|
||||
int reciprocal = qoa_reciprocal_tab[scalefactor];
|
||||
int n = (v * reciprocal + (1 << 15)) >> 16;
|
||||
n = n + ((v > 0) - (v < 0)) - ((n > 0) - (n < 0)); /* round away from 0 */
|
||||
return n;
|
||||
}
|
||||
|
||||
static inline int qoa_clamp(int v, int min, int max) {
|
||||
if (v < min) { return min; }
|
||||
if (v > max) { return max; }
|
||||
return v;
|
||||
}
|
||||
|
||||
/* This specialized clamp function for the signed 16 bit range improves decode
|
||||
performance quite a bit. The extra if() statement works nicely with the CPUs
|
||||
branch prediction as this branch is rarely taken. */
|
||||
|
||||
static inline int qoa_clamp_s16(int v) {
|
||||
if ((unsigned int)(v + 32768) > 65535) {
|
||||
if (v < -32768) { return -32768; }
|
||||
if (v > 32767) { return 32767; }
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline qoa_uint64_t qoa_read_u64(const unsigned char *bytes, unsigned int *p) {
|
||||
bytes += *p;
|
||||
*p += 8;
|
||||
return
|
||||
((qoa_uint64_t)(bytes[0]) << 56) | ((qoa_uint64_t)(bytes[1]) << 48) |
|
||||
((qoa_uint64_t)(bytes[2]) << 40) | ((qoa_uint64_t)(bytes[3]) << 32) |
|
||||
((qoa_uint64_t)(bytes[4]) << 24) | ((qoa_uint64_t)(bytes[5]) << 16) |
|
||||
((qoa_uint64_t)(bytes[6]) << 8) | ((qoa_uint64_t)(bytes[7]) << 0);
|
||||
}
|
||||
|
||||
static inline void qoa_write_u64(qoa_uint64_t v, unsigned char *bytes, unsigned int *p) {
|
||||
bytes += *p;
|
||||
*p += 8;
|
||||
bytes[0] = (v >> 56) & 0xff;
|
||||
bytes[1] = (v >> 48) & 0xff;
|
||||
bytes[2] = (v >> 40) & 0xff;
|
||||
bytes[3] = (v >> 32) & 0xff;
|
||||
bytes[4] = (v >> 24) & 0xff;
|
||||
bytes[5] = (v >> 16) & 0xff;
|
||||
bytes[6] = (v >> 8) & 0xff;
|
||||
bytes[7] = (v >> 0) & 0xff;
|
||||
}
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Encoder */
|
||||
|
||||
unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes) {
|
||||
unsigned int p = 0;
|
||||
qoa_write_u64(((qoa_uint64_t)QOA_MAGIC << 32) | qoa->samples, bytes, &p);
|
||||
return p;
|
||||
}
|
||||
|
||||
unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes) {
|
||||
unsigned int channels = qoa->channels;
|
||||
|
||||
unsigned int p = 0;
|
||||
unsigned int slices = (frame_len + QOA_SLICE_LEN - 1) / QOA_SLICE_LEN;
|
||||
unsigned int frame_size = QOA_FRAME_SIZE(channels, slices);
|
||||
int prev_scalefactor[QOA_MAX_CHANNELS] = {0};
|
||||
|
||||
/* Write the frame header */
|
||||
qoa_write_u64((
|
||||
(qoa_uint64_t)qoa->channels << 56 |
|
||||
(qoa_uint64_t)qoa->samplerate << 32 |
|
||||
(qoa_uint64_t)frame_len << 16 |
|
||||
(qoa_uint64_t)frame_size
|
||||
), bytes, &p);
|
||||
|
||||
|
||||
for (int c = 0; c < channels; c++) {
|
||||
/* If the weights have grown too large, reset them to 0. This may happen
|
||||
with certain high-frequency sounds. This is a last resort and will
|
||||
introduce quite a bit of noise, but should at least prevent pops/clicks */
|
||||
int weights_sum =
|
||||
qoa->lms[c].weights[0] * qoa->lms[c].weights[0] +
|
||||
qoa->lms[c].weights[1] * qoa->lms[c].weights[1] +
|
||||
qoa->lms[c].weights[2] * qoa->lms[c].weights[2] +
|
||||
qoa->lms[c].weights[3] * qoa->lms[c].weights[3];
|
||||
if (weights_sum > 0x2fffffff) {
|
||||
qoa->lms[c].weights[0] = 0;
|
||||
qoa->lms[c].weights[1] = 0;
|
||||
qoa->lms[c].weights[2] = 0;
|
||||
qoa->lms[c].weights[3] = 0;
|
||||
}
|
||||
|
||||
/* Write the current LMS state */
|
||||
qoa_uint64_t weights = 0;
|
||||
qoa_uint64_t history = 0;
|
||||
for (int i = 0; i < QOA_LMS_LEN; i++) {
|
||||
history = (history << 16) | (qoa->lms[c].history[i] & 0xffff);
|
||||
weights = (weights << 16) | (qoa->lms[c].weights[i] & 0xffff);
|
||||
}
|
||||
qoa_write_u64(history, bytes, &p);
|
||||
qoa_write_u64(weights, bytes, &p);
|
||||
}
|
||||
|
||||
/* We encode all samples with the channels interleaved on a slice level.
|
||||
E.g. for stereo: (ch-0, slice 0), (ch 1, slice 0), (ch 0, slice 1), ...*/
|
||||
for (int sample_index = 0; sample_index < frame_len; sample_index += QOA_SLICE_LEN) {
|
||||
|
||||
for (int c = 0; c < channels; c++) {
|
||||
int slice_len = qoa_clamp(QOA_SLICE_LEN, 0, frame_len - sample_index);
|
||||
int slice_start = sample_index * channels + c;
|
||||
int slice_end = (sample_index + slice_len) * channels + c;
|
||||
|
||||
/* Brute for search for the best scalefactor. Just go through all
|
||||
16 scalefactors, encode all samples for the current slice and
|
||||
meassure the total squared error. */
|
||||
qoa_uint64_t best_error = -1;
|
||||
qoa_uint64_t best_slice;
|
||||
qoa_lms_t best_lms;
|
||||
int best_scalefactor;
|
||||
|
||||
for (int sfi = 0; sfi < 16; sfi++) {
|
||||
/* There is a strong correlation between the scalefactors of
|
||||
neighboring slices. As an optimization, start testing
|
||||
the best scalefactor of the previous slice first. */
|
||||
int scalefactor = (sfi + prev_scalefactor[c]) % 16;
|
||||
|
||||
/* We have to reset the LMS state to the last known good one
|
||||
before trying each scalefactor, as each pass updates the LMS
|
||||
state when encoding. */
|
||||
qoa_lms_t lms = qoa->lms[c];
|
||||
qoa_uint64_t slice = scalefactor;
|
||||
qoa_uint64_t current_error = 0;
|
||||
|
||||
for (int si = slice_start; si < slice_end; si += channels) {
|
||||
int sample = sample_data[si];
|
||||
int predicted = qoa_lms_predict(&lms);
|
||||
|
||||
int residual = sample - predicted;
|
||||
int scaled = qoa_div(residual, scalefactor);
|
||||
int clamped = qoa_clamp(scaled, -8, 8);
|
||||
int quantized = qoa_quant_tab[clamped + 8];
|
||||
int dequantized = qoa_dequant_tab[scalefactor][quantized];
|
||||
int reconstructed = qoa_clamp_s16(predicted + dequantized);
|
||||
|
||||
long long error = (sample - reconstructed);
|
||||
current_error += error * error;
|
||||
if (current_error > best_error) {
|
||||
break;
|
||||
}
|
||||
|
||||
qoa_lms_update(&lms, reconstructed, dequantized);
|
||||
slice = (slice << 3) | quantized;
|
||||
}
|
||||
|
||||
if (current_error < best_error) {
|
||||
best_error = current_error;
|
||||
best_slice = slice;
|
||||
best_lms = lms;
|
||||
best_scalefactor = scalefactor;
|
||||
}
|
||||
}
|
||||
|
||||
prev_scalefactor[c] = best_scalefactor;
|
||||
|
||||
qoa->lms[c] = best_lms;
|
||||
#ifdef QOA_RECORD_TOTAL_ERROR
|
||||
qoa->error += best_error;
|
||||
#endif
|
||||
|
||||
/* If this slice was shorter than QOA_SLICE_LEN, we have to left-
|
||||
shift all encoded data, to ensure the rightmost bits are the empty
|
||||
ones. This should only happen in the last frame of a file as all
|
||||
slices are completely filled otherwise. */
|
||||
best_slice <<= (QOA_SLICE_LEN - slice_len) * 3;
|
||||
qoa_write_u64(best_slice, bytes, &p);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len) {
|
||||
if (
|
||||
qoa->samples == 0 ||
|
||||
qoa->samplerate == 0 || qoa->samplerate > 0xffffff ||
|
||||
qoa->channels == 0 || qoa->channels > QOA_MAX_CHANNELS
|
||||
) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Calculate the encoded size and allocate */
|
||||
unsigned int num_frames = (qoa->samples + QOA_FRAME_LEN-1) / QOA_FRAME_LEN;
|
||||
unsigned int num_slices = (qoa->samples + QOA_SLICE_LEN-1) / QOA_SLICE_LEN;
|
||||
unsigned int encoded_size = 8 + /* 8 byte file header */
|
||||
num_frames * 8 + /* 8 byte frame headers */
|
||||
num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */
|
||||
num_slices * 8 * qoa->channels; /* 8 byte slices */
|
||||
|
||||
unsigned char *bytes = QOA_MALLOC(encoded_size);
|
||||
|
||||
for (int c = 0; c < qoa->channels; c++) {
|
||||
/* Set the initial LMS weights to {0, 0, -1, 2}. This helps with the
|
||||
prediction of the first few ms of a file. */
|
||||
qoa->lms[c].weights[0] = 0;
|
||||
qoa->lms[c].weights[1] = 0;
|
||||
qoa->lms[c].weights[2] = -(1<<13);
|
||||
qoa->lms[c].weights[3] = (1<<14);
|
||||
|
||||
/* Explicitly set the history samples to 0, as we might have some
|
||||
garbage in there. */
|
||||
for (int i = 0; i < QOA_LMS_LEN; i++) {
|
||||
qoa->lms[c].history[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Encode the header and go through all frames */
|
||||
unsigned int p = qoa_encode_header(qoa, bytes);
|
||||
#ifdef QOA_RECORD_TOTAL_ERROR
|
||||
qoa->error = 0;
|
||||
#endif
|
||||
|
||||
int frame_len = QOA_FRAME_LEN;
|
||||
for (int sample_index = 0; sample_index < qoa->samples; sample_index += frame_len) {
|
||||
frame_len = qoa_clamp(QOA_FRAME_LEN, 0, qoa->samples - sample_index);
|
||||
const short *frame_samples = sample_data + sample_index * qoa->channels;
|
||||
unsigned int frame_size = qoa_encode_frame(frame_samples, qoa, frame_len, bytes + p);
|
||||
p += frame_size;
|
||||
}
|
||||
|
||||
*out_len = p;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
Decoder */
|
||||
|
||||
unsigned int qoa_max_frame_size(qoa_desc *qoa) {
|
||||
return QOA_FRAME_SIZE(qoa->channels, QOA_SLICES_PER_FRAME);
|
||||
}
|
||||
|
||||
unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa) {
|
||||
unsigned int p = 0;
|
||||
if (size < QOA_MIN_FILESIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Read the file header, verify the magic number ('qoaf') and read the
|
||||
total number of samples. */
|
||||
qoa_uint64_t file_header = qoa_read_u64(bytes, &p);
|
||||
|
||||
if ((file_header >> 32) != QOA_MAGIC) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
qoa->samples = file_header & 0xffffffff;
|
||||
if (!qoa->samples) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Peek into the first frame header to get the number of channels and
|
||||
the samplerate. */
|
||||
qoa_uint64_t frame_header = qoa_read_u64(bytes, &p);
|
||||
qoa->channels = (frame_header >> 56) & 0x0000ff;
|
||||
qoa->samplerate = (frame_header >> 32) & 0xffffff;
|
||||
|
||||
if (qoa->channels == 0 || qoa->samples == 0 || qoa->samplerate == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 8;
|
||||
}
|
||||
|
||||
unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len) {
|
||||
unsigned int p = 0;
|
||||
*frame_len = 0;
|
||||
|
||||
if (size < 8 + QOA_LMS_LEN * 4 * qoa->channels) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read and verify the frame header */
|
||||
qoa_uint64_t frame_header = qoa_read_u64(bytes, &p);
|
||||
int channels = (frame_header >> 56) & 0x0000ff;
|
||||
int samplerate = (frame_header >> 32) & 0xffffff;
|
||||
int samples = (frame_header >> 16) & 0x00ffff;
|
||||
int frame_size = (frame_header ) & 0x00ffff;
|
||||
|
||||
int data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels;
|
||||
int num_slices = data_size / 8;
|
||||
int max_total_samples = num_slices * QOA_SLICE_LEN;
|
||||
|
||||
if (
|
||||
channels != qoa->channels ||
|
||||
samplerate != qoa->samplerate ||
|
||||
frame_size > size ||
|
||||
samples * channels > max_total_samples
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Read the LMS state: 4 x 2 bytes history, 4 x 2 bytes weights per channel */
|
||||
for (int c = 0; c < channels; c++) {
|
||||
qoa_uint64_t history = qoa_read_u64(bytes, &p);
|
||||
qoa_uint64_t weights = qoa_read_u64(bytes, &p);
|
||||
for (int i = 0; i < QOA_LMS_LEN; i++) {
|
||||
qoa->lms[c].history[i] = ((signed short)(history >> 48));
|
||||
history <<= 16;
|
||||
qoa->lms[c].weights[i] = ((signed short)(weights >> 48));
|
||||
weights <<= 16;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Decode all slices for all channels in this frame */
|
||||
for (int sample_index = 0; sample_index < samples; sample_index += QOA_SLICE_LEN) {
|
||||
for (int c = 0; c < channels; c++) {
|
||||
qoa_uint64_t slice = qoa_read_u64(bytes, &p);
|
||||
|
||||
int scalefactor = (slice >> 60) & 0xf;
|
||||
int slice_start = sample_index * channels + c;
|
||||
int slice_end = qoa_clamp(sample_index + QOA_SLICE_LEN, 0, samples) * channels + c;
|
||||
|
||||
for (int si = slice_start; si < slice_end; si += channels) {
|
||||
int predicted = qoa_lms_predict(&qoa->lms[c]);
|
||||
int quantized = (slice >> 57) & 0x7;
|
||||
int dequantized = qoa_dequant_tab[scalefactor][quantized];
|
||||
int reconstructed = qoa_clamp_s16(predicted + dequantized);
|
||||
|
||||
sample_data[si] = reconstructed;
|
||||
slice <<= 3;
|
||||
|
||||
qoa_lms_update(&qoa->lms[c], reconstructed, dequantized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*frame_len = samples;
|
||||
return p;
|
||||
}
|
||||
|
||||
short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) {
|
||||
unsigned int p = qoa_decode_header(bytes, size, qoa);
|
||||
if (!p) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Calculate the required size of the sample buffer and allocate */
|
||||
int total_samples = qoa->samples * qoa->channels;
|
||||
short *sample_data = QOA_MALLOC(total_samples * sizeof(short));
|
||||
|
||||
unsigned int sample_index = 0;
|
||||
unsigned int frame_len;
|
||||
unsigned int frame_size;
|
||||
|
||||
/* Decode all frames */
|
||||
do {
|
||||
short *sample_ptr = sample_data + sample_index * qoa->channels;
|
||||
frame_size = qoa_decode_frame(bytes + p, size - p, qoa, sample_ptr, &frame_len);
|
||||
|
||||
p += frame_size;
|
||||
sample_index += frame_len;
|
||||
} while (frame_size && sample_index < qoa->samples);
|
||||
|
||||
qoa->samples = sample_index;
|
||||
return sample_data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
File read/write convenience functions */
|
||||
|
||||
#ifndef QOA_NO_STDIO
|
||||
#include <stdio.h>
|
||||
|
||||
int qoa_write(const char *filename, const short *sample_data, qoa_desc *qoa) {
|
||||
FILE *f = fopen(filename, "wb");
|
||||
unsigned int size;
|
||||
void *encoded;
|
||||
|
||||
if (!f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
encoded = qoa_encode(sample_data, qoa, &size);
|
||||
if (!encoded) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fwrite(encoded, 1, size, f);
|
||||
fclose(f);
|
||||
|
||||
QOA_FREE(encoded);
|
||||
return size;
|
||||
}
|
||||
|
||||
void *qoa_read(const char *filename, qoa_desc *qoa) {
|
||||
FILE *f = fopen(filename, "rb");
|
||||
int size, bytes_read;
|
||||
void *data;
|
||||
short *sample_data;
|
||||
|
||||
if (!f) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
size = ftell(f);
|
||||
if (size <= 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
data = QOA_MALLOC(size);
|
||||
if (!data) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bytes_read = fread(data, 1, size, f);
|
||||
fclose(f);
|
||||
|
||||
sample_data = qoa_decode(data, bytes_read, qoa);
|
||||
QOA_FREE(data);
|
||||
return sample_data;
|
||||
}
|
||||
|
||||
#endif /* QOA_NO_STDIO */
|
||||
#endif /* QOA_IMPLEMENTATION */
|
||||
+11057
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,324 @@
|
||||
#if defined(SOKOL_IMPL) && !defined(SOKOL_TIME_IMPL)
|
||||
#define SOKOL_TIME_IMPL
|
||||
#endif
|
||||
#ifndef SOKOL_TIME_INCLUDED
|
||||
/*
|
||||
sokol_time.h -- simple cross-platform time measurement
|
||||
|
||||
Project URL: https://github.com/floooh/sokol
|
||||
|
||||
Do this:
|
||||
#define SOKOL_IMPL or
|
||||
#define SOKOL_TIME_IMPL
|
||||
before you include this file in *one* C or C++ file to create the
|
||||
implementation.
|
||||
|
||||
Optionally provide the following defines with your own implementations:
|
||||
SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
|
||||
SOKOL_TIME_API_DECL - public function declaration prefix (default: extern)
|
||||
SOKOL_API_DECL - same as SOKOL_TIME_API_DECL
|
||||
SOKOL_API_IMPL - public function implementation prefix (default: -)
|
||||
|
||||
If sokol_time.h is compiled as a DLL, define the following before
|
||||
including the declaration or implementation:
|
||||
|
||||
SOKOL_DLL
|
||||
|
||||
On Windows, SOKOL_DLL will define SOKOL_TIME_API_DECL as __declspec(dllexport)
|
||||
or __declspec(dllimport) as needed.
|
||||
|
||||
void stm_setup();
|
||||
Call once before any other functions to initialize sokol_time
|
||||
(this calls for instance QueryPerformanceFrequency on Windows)
|
||||
|
||||
uint64_t stm_now();
|
||||
Get current point in time in unspecified 'ticks'. The value that
|
||||
is returned has no relation to the 'wall-clock' time and is
|
||||
not in a specific time unit, it is only useful to compute
|
||||
time differences.
|
||||
|
||||
uint64_t stm_diff(uint64_t new, uint64_t old);
|
||||
Computes the time difference between new and old. This will always
|
||||
return a positive, non-zero value.
|
||||
|
||||
uint64_t stm_since(uint64_t start);
|
||||
Takes the current time, and returns the elapsed time since start
|
||||
(this is a shortcut for "stm_diff(stm_now(), start)")
|
||||
|
||||
uint64_t stm_laptime(uint64_t* last_time);
|
||||
This is useful for measuring frame time and other recurring
|
||||
events. It takes the current time, returns the time difference
|
||||
to the value in last_time, and stores the current time in
|
||||
last_time for the next call. If the value in last_time is 0,
|
||||
the return value will be zero (this usually happens on the
|
||||
very first call).
|
||||
|
||||
uint64_t stm_round_to_common_refresh_rate(uint64_t duration)
|
||||
This oddly named function takes a measured frame time and
|
||||
returns the closest "nearby" common display refresh rate frame duration
|
||||
in ticks. If the input duration isn't close to any common display
|
||||
refresh rate, the input duration will be returned unchanged as a fallback.
|
||||
The main purpose of this function is to remove jitter/inaccuracies from
|
||||
measured frame times, and instead use the display refresh rate as
|
||||
frame duration.
|
||||
|
||||
Use the following functions to convert a duration in ticks into
|
||||
useful time units:
|
||||
|
||||
double stm_sec(uint64_t ticks);
|
||||
double stm_ms(uint64_t ticks);
|
||||
double stm_us(uint64_t ticks);
|
||||
double stm_ns(uint64_t ticks);
|
||||
Converts a tick value into seconds, milliseconds, microseconds
|
||||
or nanoseconds. Note that not all platforms will have nanosecond
|
||||
or even microsecond precision.
|
||||
|
||||
Uses the following time measurement functions under the hood:
|
||||
|
||||
Windows: QueryPerformanceFrequency() / QueryPerformanceCounter()
|
||||
MacOS/iOS: mach_absolute_time()
|
||||
emscripten: performance.now()
|
||||
Linux+others: clock_gettime(CLOCK_MONOTONIC)
|
||||
|
||||
zlib/libpng license
|
||||
|
||||
Copyright (c) 2018 Andre Weissflog
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the
|
||||
use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software in a
|
||||
product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not
|
||||
be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
#define SOKOL_TIME_INCLUDED (1)
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(SOKOL_API_DECL) && !defined(SOKOL_TIME_API_DECL)
|
||||
#define SOKOL_TIME_API_DECL SOKOL_API_DECL
|
||||
#endif
|
||||
#ifndef SOKOL_TIME_API_DECL
|
||||
#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_TIME_IMPL)
|
||||
#define SOKOL_TIME_API_DECL __declspec(dllexport)
|
||||
#elif defined(_WIN32) && defined(SOKOL_DLL)
|
||||
#define SOKOL_TIME_API_DECL __declspec(dllimport)
|
||||
#else
|
||||
#define SOKOL_TIME_API_DECL extern
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
SOKOL_TIME_API_DECL void stm_setup(void);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_now(void);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_since(uint64_t start_ticks);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_laptime(uint64_t* last_time);
|
||||
SOKOL_TIME_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks);
|
||||
SOKOL_TIME_API_DECL double stm_sec(uint64_t ticks);
|
||||
SOKOL_TIME_API_DECL double stm_ms(uint64_t ticks);
|
||||
SOKOL_TIME_API_DECL double stm_us(uint64_t ticks);
|
||||
SOKOL_TIME_API_DECL double stm_ns(uint64_t ticks);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
#endif // SOKOL_TIME_INCLUDED
|
||||
|
||||
/*-- IMPLEMENTATION ----------------------------------------------------------*/
|
||||
#ifdef SOKOL_TIME_IMPL
|
||||
#define SOKOL_TIME_IMPL_INCLUDED (1)
|
||||
#include <string.h> /* memset */
|
||||
|
||||
#ifndef SOKOL_API_IMPL
|
||||
#define SOKOL_API_IMPL
|
||||
#endif
|
||||
#ifndef SOKOL_ASSERT
|
||||
#include <assert.h>
|
||||
#define SOKOL_ASSERT(c) assert(c)
|
||||
#endif
|
||||
#ifndef _SOKOL_PRIVATE
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define _SOKOL_PRIVATE __attribute__((unused)) static
|
||||
#else
|
||||
#define _SOKOL_PRIVATE static
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
LARGE_INTEGER freq;
|
||||
LARGE_INTEGER start;
|
||||
} _stm_state_t;
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
#include <mach/mach_time.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
mach_timebase_info_data_t timebase;
|
||||
uint64_t start;
|
||||
} _stm_state_t;
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
#include <emscripten/emscripten.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
double start;
|
||||
} _stm_state_t;
|
||||
#else /* anything else, this will need more care for non-Linux platforms */
|
||||
#ifdef ESP8266
|
||||
// On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined
|
||||
#define CLOCK_MONOTONIC 0
|
||||
#endif
|
||||
#include <time.h>
|
||||
typedef struct {
|
||||
uint32_t initialized;
|
||||
uint64_t start;
|
||||
} _stm_state_t;
|
||||
#endif
|
||||
static _stm_state_t _stm;
|
||||
|
||||
/* prevent 64-bit overflow when computing relative timestamp
|
||||
see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3
|
||||
*/
|
||||
#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__))
|
||||
_SOKOL_PRIVATE int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) {
|
||||
int64_t q = value / denom;
|
||||
int64_t r = value % denom;
|
||||
return q * numer + r * numer / denom;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
EM_JS(double, stm_js_perfnow, (void), {
|
||||
return performance.now();
|
||||
});
|
||||
#endif
|
||||
|
||||
SOKOL_API_IMPL void stm_setup(void) {
|
||||
memset(&_stm, 0, sizeof(_stm));
|
||||
_stm.initialized = 0xABCDABCD;
|
||||
#if defined(_WIN32)
|
||||
QueryPerformanceFrequency(&_stm.freq);
|
||||
QueryPerformanceCounter(&_stm.start);
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
mach_timebase_info(&_stm.timebase);
|
||||
_stm.start = mach_absolute_time();
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
_stm.start = stm_js_perfnow();
|
||||
#else
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
_stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec;
|
||||
#endif
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_now(void) {
|
||||
SOKOL_ASSERT(_stm.initialized == 0xABCDABCD);
|
||||
uint64_t now;
|
||||
#if defined(_WIN32)
|
||||
LARGE_INTEGER qpc_t;
|
||||
QueryPerformanceCounter(&qpc_t);
|
||||
now = (uint64_t) int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart);
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
const uint64_t mach_now = mach_absolute_time() - _stm.start;
|
||||
now = (uint64_t) int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom);
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
double js_now = stm_js_perfnow() - _stm.start;
|
||||
SOKOL_ASSERT(js_now >= 0.0);
|
||||
now = (uint64_t) (js_now * 1000000.0);
|
||||
#else
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start;
|
||||
#endif
|
||||
return now;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) {
|
||||
if (new_ticks > old_ticks) {
|
||||
return new_ticks - old_ticks;
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) {
|
||||
return stm_diff(stm_now(), start_ticks);
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) {
|
||||
SOKOL_ASSERT(last_time);
|
||||
uint64_t dt = 0;
|
||||
uint64_t now = stm_now();
|
||||
if (0 != *last_time) {
|
||||
dt = stm_diff(now, *last_time);
|
||||
}
|
||||
*last_time = now;
|
||||
return dt;
|
||||
}
|
||||
|
||||
// first number is frame duration in ns, second number is tolerance in ns,
|
||||
// the resulting min/max values must not overlap!
|
||||
static const uint64_t _stm_refresh_rates[][2] = {
|
||||
{ 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms
|
||||
{ 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms
|
||||
{ 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms
|
||||
{ 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25
|
||||
{ 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms
|
||||
{ 10000000, 500000 }, // 100 Hz: 10.0000 +- 0.5ms
|
||||
{ 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms
|
||||
{ 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms
|
||||
{ 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms
|
||||
{ 0, 0 }, // keep the last element always at zero
|
||||
};
|
||||
|
||||
SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) {
|
||||
uint64_t ns;
|
||||
int i = 0;
|
||||
while (0 != (ns = _stm_refresh_rates[i][0])) {
|
||||
uint64_t tol = _stm_refresh_rates[i][1];
|
||||
if ((ticks > (ns - tol)) && (ticks < (ns + tol))) {
|
||||
return ns;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// fallthough: didn't fit into any buckets
|
||||
return ticks;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_sec(uint64_t ticks) {
|
||||
return (double)ticks / 1000000000.0;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_ms(uint64_t ticks) {
|
||||
return (double)ticks / 1000000.0;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_us(uint64_t ticks) {
|
||||
return (double)ticks / 1000.0;
|
||||
}
|
||||
|
||||
SOKOL_API_IMPL double stm_ns(uint64_t ticks) {
|
||||
return (double)ticks;
|
||||
}
|
||||
#endif /* SOKOL_TIME_IMPL */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mem.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
static uint8_t hunk[MEM_HUNK_BYTES];
|
||||
static uint32_t bump_len = 0;
|
||||
static uint32_t temp_len = 0;
|
||||
|
||||
static uint32_t temp_objects[MEM_TEMP_OBJECTS_MAX] = {};
|
||||
static uint32_t temp_objects_len;
|
||||
|
||||
|
||||
// Bump allocator - returns bytes from the front of the hunk
|
||||
|
||||
// These allocations persist for many frames. The allocator level is reset
|
||||
// whenever we load a new race track or menu in game_set_scene()
|
||||
|
||||
void *mem_mark() {
|
||||
return &hunk[bump_len];
|
||||
}
|
||||
|
||||
void *mem_bump(uint32_t size) {
|
||||
error_if(bump_len + temp_len + size >= MEM_HUNK_BYTES, "Failed to allocate %d bytes in hunk mem", size);
|
||||
uint8_t *p = &hunk[bump_len];
|
||||
bump_len += size;
|
||||
memset(p, 0, size);
|
||||
return p;
|
||||
}
|
||||
|
||||
void mem_reset(void *p) {
|
||||
uint32_t offset = (uint8_t *)p - (uint8_t *)hunk;
|
||||
error_if(offset > bump_len || offset > MEM_HUNK_BYTES, "Invalid mem reset");
|
||||
bump_len = offset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Temp allocator - returns bytes from the back of the hunk
|
||||
|
||||
// Temporary allocated bytes are not allowed to persist for multiple frames. You
|
||||
// need to explicitly free them when you are done. Temp allocated bytes don't
|
||||
// have be freed in reverse allocation order. I.e. you can allocate A then B,
|
||||
// and aftewards free A then B.
|
||||
|
||||
void *mem_temp_alloc(uint32_t size) {
|
||||
size = ((size >> 3) + 7) << 3; // allign to 8 bytes
|
||||
|
||||
error_if(bump_len + temp_len + size >= MEM_HUNK_BYTES, "Failed to allocate %d bytes in temp mem", size);
|
||||
error_if(temp_objects_len >= MEM_TEMP_OBJECTS_MAX, "MEM_TEMP_OBJECTS_MAX reached");
|
||||
|
||||
temp_len += size;
|
||||
void *p = &hunk[MEM_HUNK_BYTES - temp_len];
|
||||
temp_objects[temp_objects_len++] = temp_len;
|
||||
return p;
|
||||
}
|
||||
|
||||
void mem_temp_free(void *p) {
|
||||
uint32_t offset = (uint8_t *)&hunk[MEM_HUNK_BYTES] - (uint8_t *)p;
|
||||
error_if(offset > MEM_HUNK_BYTES, "Object 0x%p not in temp hunk", p);
|
||||
|
||||
bool found = false;
|
||||
uint32_t remaining_max = 0;
|
||||
for (int i = 0; i < temp_objects_len; i++) {
|
||||
if (temp_objects[i] == offset) {
|
||||
temp_objects[i--] = temp_objects[--temp_objects_len];
|
||||
found = true;
|
||||
}
|
||||
else if (temp_objects[i] > remaining_max) {
|
||||
remaining_max = temp_objects[i];
|
||||
}
|
||||
}
|
||||
error_if(!found, "Object 0x%p not in temp hunk", p);
|
||||
temp_len = remaining_max;
|
||||
}
|
||||
|
||||
void mem_temp_check() {
|
||||
error_if(temp_len != 0, "Temp memory not free: %d object(s)", temp_objects_len);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef MEM_H
|
||||
#define MEM_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define MEM_TEMP_OBJECTS_MAX 8
|
||||
#define MEM_HUNK_BYTES (4 * 1024 * 1024)
|
||||
|
||||
void *mem_bump(uint32_t size);
|
||||
void *mem_mark();
|
||||
void mem_reset(void *p);
|
||||
|
||||
void *mem_temp_alloc(uint32_t size);
|
||||
void mem_temp_free(void *p);
|
||||
void mem_temp_check();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef PLATFORM_H
|
||||
#define PLATFORM_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
void platform_exit();
|
||||
vec2i_t platform_screen_size();
|
||||
double platform_now();
|
||||
void platform_set_fullscreen(bool fullscreen);
|
||||
void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len));
|
||||
|
||||
#if defined(RENDERER_SOFTWARE)
|
||||
rgba_t *platform_get_screenbuffer(int32_t *pitch);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Executable
+350
@@ -0,0 +1,350 @@
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "platform.h"
|
||||
#include "input.h"
|
||||
#include "system.h"
|
||||
|
||||
static uint64_t perf_freq = 0;
|
||||
static bool wants_to_exit = false;
|
||||
static SDL_Window *window;
|
||||
static SDL_AudioDeviceID audio_device;
|
||||
static SDL_GameController *gamepad;
|
||||
static void (*audio_callback)(float *buffer, uint32_t len) = NULL;
|
||||
|
||||
|
||||
uint8_t platform_sdl_gamepad_map[] = {
|
||||
[SDL_CONTROLLER_BUTTON_A] = INPUT_GAMEPAD_A,
|
||||
[SDL_CONTROLLER_BUTTON_B] = INPUT_GAMEPAD_B,
|
||||
[SDL_CONTROLLER_BUTTON_X] = INPUT_GAMEPAD_X,
|
||||
[SDL_CONTROLLER_BUTTON_Y] = INPUT_GAMEPAD_Y,
|
||||
[SDL_CONTROLLER_BUTTON_BACK] = INPUT_GAMEPAD_SELECT,
|
||||
[SDL_CONTROLLER_BUTTON_GUIDE] = INPUT_GAMEPAD_HOME,
|
||||
[SDL_CONTROLLER_BUTTON_START] = INPUT_GAMEPAD_START,
|
||||
[SDL_CONTROLLER_BUTTON_LEFTSTICK] = INPUT_GAMEPAD_L_STICK_PRESS,
|
||||
[SDL_CONTROLLER_BUTTON_RIGHTSTICK] = INPUT_GAMEPAD_R_STICK_PRESS,
|
||||
[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = INPUT_GAMEPAD_L_SHOULDER,
|
||||
[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = INPUT_GAMEPAD_R_SHOULDER,
|
||||
[SDL_CONTROLLER_BUTTON_DPAD_UP] = INPUT_GAMEPAD_DPAD_UP,
|
||||
[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = INPUT_GAMEPAD_DPAD_DOWN,
|
||||
[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = INPUT_GAMEPAD_DPAD_LEFT,
|
||||
[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = INPUT_GAMEPAD_DPAD_RIGHT,
|
||||
[SDL_CONTROLLER_BUTTON_MAX] = INPUT_INVALID
|
||||
};
|
||||
|
||||
|
||||
uint8_t platform_sdl_axis_map[] = {
|
||||
[SDL_CONTROLLER_AXIS_LEFTX] = INPUT_GAMEPAD_L_STICK_LEFT,
|
||||
[SDL_CONTROLLER_AXIS_LEFTY] = INPUT_GAMEPAD_L_STICK_UP,
|
||||
[SDL_CONTROLLER_AXIS_RIGHTX] = INPUT_GAMEPAD_R_STICK_LEFT,
|
||||
[SDL_CONTROLLER_AXIS_RIGHTY] = INPUT_GAMEPAD_R_STICK_UP,
|
||||
[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = INPUT_GAMEPAD_L_TRIGGER,
|
||||
[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = INPUT_GAMEPAD_R_TRIGGER,
|
||||
[SDL_CONTROLLER_AXIS_MAX] = INPUT_INVALID
|
||||
};
|
||||
|
||||
|
||||
void platform_exit() {
|
||||
wants_to_exit = true;
|
||||
}
|
||||
|
||||
SDL_GameController *platform_find_gamepad() {
|
||||
for (int i = 0; i < SDL_NumJoysticks(); i++) {
|
||||
if (SDL_IsGameController(i)) {
|
||||
return SDL_GameControllerOpen(i);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void platform_pump_events() {
|
||||
SDL_Event ev;
|
||||
while (SDL_PollEvent(&ev)) {
|
||||
// Input Keyboard
|
||||
if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) {
|
||||
int code = ev.key.keysym.scancode;
|
||||
float state = ev.type == SDL_KEYDOWN ? 1.0 : 0.0;
|
||||
if (code >= SDL_SCANCODE_LCTRL && code <= SDL_SCANCODE_RALT) {
|
||||
int code_internal = code - SDL_SCANCODE_LCTRL + INPUT_KEY_LCTRL;
|
||||
input_set_button_state(code_internal, state);
|
||||
}
|
||||
else if (code > 0 && code < INPUT_KEY_MAX) {
|
||||
input_set_button_state(code, state);
|
||||
}
|
||||
}
|
||||
|
||||
else if (ev.type == SDL_TEXTINPUT) {
|
||||
input_textinput(ev.text.text[0]);
|
||||
}
|
||||
|
||||
// Gamepads connect/disconnect
|
||||
else if (ev.type == SDL_CONTROLLERDEVICEADDED) {
|
||||
gamepad = SDL_GameControllerOpen(ev.cdevice.which);
|
||||
}
|
||||
else if (ev.type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
if (gamepad && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamepad))) {
|
||||
SDL_GameControllerClose(gamepad);
|
||||
gamepad = platform_find_gamepad();
|
||||
}
|
||||
}
|
||||
|
||||
// Input Gamepad Buttons
|
||||
else if (
|
||||
ev.type == SDL_CONTROLLERBUTTONDOWN ||
|
||||
ev.type == SDL_CONTROLLERBUTTONUP
|
||||
) {
|
||||
if (ev.cbutton.button < SDL_CONTROLLER_BUTTON_MAX) {
|
||||
button_t button = platform_sdl_gamepad_map[ev.cbutton.button];
|
||||
if (button != INPUT_INVALID) {
|
||||
float state = ev.type == SDL_CONTROLLERBUTTONDOWN ? 1.0 : 0.0;
|
||||
input_set_button_state(button, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input Gamepad Axis
|
||||
else if (ev.type == SDL_CONTROLLERAXISMOTION) {
|
||||
float state = (float)ev.caxis.value / 32767.0;
|
||||
|
||||
if (ev.caxis.axis < SDL_CONTROLLER_AXIS_MAX) {
|
||||
int code = platform_sdl_axis_map[ev.caxis.axis];
|
||||
if (
|
||||
code == INPUT_GAMEPAD_L_TRIGGER ||
|
||||
code == INPUT_GAMEPAD_R_TRIGGER
|
||||
) {
|
||||
input_set_button_state(code, state);
|
||||
}
|
||||
else if (state > 0) {
|
||||
input_set_button_state(code, 0.0);
|
||||
input_set_button_state(code+1, state);
|
||||
}
|
||||
else {
|
||||
input_set_button_state(code, -state);
|
||||
input_set_button_state(code+1, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse buttons
|
||||
else if (
|
||||
ev.type == SDL_MOUSEBUTTONDOWN ||
|
||||
ev.type == SDL_MOUSEBUTTONUP
|
||||
) {
|
||||
button_t button = INPUT_BUTTON_NONE;
|
||||
switch (ev.button.button) {
|
||||
case SDL_BUTTON_LEFT: button = INPUT_MOUSE_LEFT; break;
|
||||
case SDL_BUTTON_MIDDLE: button = INPUT_MOUSE_MIDDLE; break;
|
||||
case SDL_BUTTON_RIGHT: button = INPUT_MOUSE_RIGHT; break;
|
||||
default: break;
|
||||
}
|
||||
if (button != INPUT_BUTTON_NONE) {
|
||||
float state = ev.type == SDL_MOUSEBUTTONDOWN ? 1.0 : 0.0;
|
||||
input_set_button_state(button, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse wheel
|
||||
else if (ev.type == SDL_MOUSEWHEEL) {
|
||||
button_t button = ev.wheel.y > 0
|
||||
? INPUT_MOUSE_WHEEL_UP
|
||||
: INPUT_MOUSE_WHEEL_DOWN;
|
||||
input_set_button_state(button, 1.0);
|
||||
input_set_button_state(button, 0.0);
|
||||
}
|
||||
|
||||
// Mouse move
|
||||
else if (ev.type == SDL_MOUSEMOTION) {
|
||||
input_set_mouse_pos(ev.motion.x, ev.motion.y);
|
||||
}
|
||||
|
||||
// Window Events
|
||||
if (ev.type == SDL_QUIT) {
|
||||
wants_to_exit = true;
|
||||
}
|
||||
else if (
|
||||
ev.type == SDL_WINDOWEVENT &&
|
||||
(
|
||||
ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED ||
|
||||
ev.window.event == SDL_WINDOWEVENT_RESIZED
|
||||
)
|
||||
) {
|
||||
system_resize(platform_screen_size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double platform_now() {
|
||||
uint64_t perf_counter = SDL_GetPerformanceCounter();
|
||||
return (double)perf_counter / (double)perf_freq;
|
||||
}
|
||||
|
||||
void platform_set_fullscreen(bool fullscreen) {
|
||||
if (fullscreen) {
|
||||
int32_t display = SDL_GetWindowDisplayIndex(window);
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
SDL_GetDesktopDisplayMode(display, &mode);
|
||||
SDL_SetWindowDisplayMode(window, &mode);
|
||||
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
}
|
||||
else {
|
||||
SDL_SetWindowFullscreen(window, 0);
|
||||
SDL_ShowCursor(SDL_ENABLE);
|
||||
}
|
||||
}
|
||||
|
||||
void platform_audio_callback(void* userdata, uint8_t* stream, int len) {
|
||||
if (audio_callback) {
|
||||
audio_callback((float *)stream, len/sizeof(float));
|
||||
}
|
||||
else {
|
||||
memset(stream, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len)) {
|
||||
audio_callback = cb;
|
||||
SDL_PauseAudioDevice(audio_device, 0);
|
||||
}
|
||||
|
||||
|
||||
#if defined(RENDERER_GL) // ----------------------------------------------------
|
||||
#define PLATFORM_WINDOW_FLAGS SDL_WINDOW_OPENGL
|
||||
SDL_GLContext platform_gl;
|
||||
|
||||
void platform_video_init() {
|
||||
#if defined(USE_GLES2)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
|
||||
#endif
|
||||
|
||||
platform_gl = SDL_GL_CreateContext(window);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
}
|
||||
|
||||
void platform_prepare_frame() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
void platform_video_cleanup() {
|
||||
SDL_GL_DeleteContext(platform_gl);
|
||||
}
|
||||
|
||||
void platform_end_frame() {
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
vec2i_t platform_screen_size() {
|
||||
int width, height;
|
||||
SDL_GL_GetDrawableSize(window, &width, &height);
|
||||
return vec2i(width, height);
|
||||
}
|
||||
|
||||
|
||||
#elif defined(RENDERER_SOFTWARE) // ----------------------------------------------
|
||||
#define PLATFORM_WINDOW_FLAGS 0
|
||||
static SDL_Renderer *renderer;
|
||||
static SDL_Texture *screenbuffer = NULL;
|
||||
static void *screenbuffer_pixels = NULL;
|
||||
static int screenbuffer_pitch;
|
||||
static vec2i_t screenbuffer_size = vec2i(0, 0);
|
||||
static vec2i_t screen_size = vec2i(0, 0);
|
||||
|
||||
|
||||
void platform_video_init() {
|
||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
}
|
||||
|
||||
void platform_video_cleanup() {
|
||||
|
||||
}
|
||||
|
||||
void platform_prepare_frame() {
|
||||
if (screen_size.x != screenbuffer_size.x || screen_size.y != screenbuffer_size.y) {
|
||||
if (screenbuffer) {
|
||||
SDL_DestroyTexture(screenbuffer);
|
||||
}
|
||||
screenbuffer = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, screen_size.x, screen_size.y);
|
||||
screenbuffer_size = screen_size;
|
||||
}
|
||||
SDL_LockTexture(screenbuffer, NULL, &screenbuffer_pixels, &screenbuffer_pitch);
|
||||
}
|
||||
|
||||
void platform_end_frame() {
|
||||
screenbuffer_pixels = NULL;
|
||||
SDL_UnlockTexture(screenbuffer);
|
||||
SDL_RenderCopy(renderer, screenbuffer, NULL, NULL);
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
rgba_t *platform_get_screenbuffer(int32_t *pitch) {
|
||||
*pitch = screenbuffer_pitch;
|
||||
return screenbuffer_pixels;
|
||||
}
|
||||
|
||||
vec2i_t platform_screen_size() {
|
||||
int width, height;
|
||||
SDL_GetWindowSize(window, &width, &height);
|
||||
|
||||
// float aspect = (float)width / (float)height;
|
||||
// screen_size = vec2i(240 * aspect, 240);
|
||||
screen_size = vec2i(width, height);
|
||||
return screen_size;
|
||||
}
|
||||
|
||||
#else
|
||||
#error "Unsupported renderer for platform SDL"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
|
||||
|
||||
int gcdb_res = SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt");
|
||||
if (gcdb_res < 0) {
|
||||
printf("Failed to load gamecontrollerdb.txt\n");
|
||||
}
|
||||
else {
|
||||
printf("load gamecontrollerdb.txt\n");
|
||||
}
|
||||
|
||||
audio_device = SDL_OpenAudioDevice(NULL, 0, &(SDL_AudioSpec){
|
||||
.freq = 44100,
|
||||
.format = AUDIO_F32,
|
||||
.channels = 2,
|
||||
.samples = 1024,
|
||||
.callback = platform_audio_callback
|
||||
}, NULL, 0);
|
||||
|
||||
gamepad = platform_find_gamepad();
|
||||
|
||||
perf_freq = SDL_GetPerformanceFrequency();
|
||||
|
||||
window = SDL_CreateWindow(
|
||||
SYSTEM_WINDOW_NAME,
|
||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
SYSTEM_WINDOW_WIDTH, SYSTEM_WINDOW_HEIGHT,
|
||||
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | PLATFORM_WINDOW_FLAGS | SDL_WINDOW_ALLOW_HIGHDPI
|
||||
);
|
||||
|
||||
platform_video_init();
|
||||
system_init();
|
||||
|
||||
while (!wants_to_exit) {
|
||||
platform_pump_events();
|
||||
platform_prepare_frame();
|
||||
system_update();
|
||||
platform_end_frame();
|
||||
}
|
||||
|
||||
system_cleanup();
|
||||
platform_video_cleanup();
|
||||
|
||||
SDL_CloseAudioDevice(audio_device);
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
#include "platform.h"
|
||||
#include "system.h"
|
||||
|
||||
#if defined(RENDERER_GL)
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#define SOKOL_GLES2
|
||||
#else
|
||||
#define SOKOL_GLCORE33
|
||||
#endif
|
||||
#else
|
||||
#error "Unsupported renderer for platform SOKOL"
|
||||
#endif
|
||||
|
||||
#define SOKOL_IMPL
|
||||
#include "libs/sokol_audio.h"
|
||||
#include "libs/sokol_time.h"
|
||||
#include "libs/sokol_app.h"
|
||||
#include "input.h"
|
||||
|
||||
static const uint8_t keyboard_map[] = {
|
||||
[SAPP_KEYCODE_SPACE] = INPUT_KEY_SPACE,
|
||||
[SAPP_KEYCODE_APOSTROPHE] = INPUT_KEY_APOSTROPHE,
|
||||
[SAPP_KEYCODE_COMMA] = INPUT_KEY_COMMA,
|
||||
[SAPP_KEYCODE_MINUS] = INPUT_KEY_MINUS,
|
||||
[SAPP_KEYCODE_PERIOD] = INPUT_KEY_PERIOD,
|
||||
[SAPP_KEYCODE_SLASH] = INPUT_KEY_SLASH,
|
||||
[SAPP_KEYCODE_0] = INPUT_KEY_0,
|
||||
[SAPP_KEYCODE_1] = INPUT_KEY_1,
|
||||
[SAPP_KEYCODE_2] = INPUT_KEY_2,
|
||||
[SAPP_KEYCODE_3] = INPUT_KEY_3,
|
||||
[SAPP_KEYCODE_4] = INPUT_KEY_4,
|
||||
[SAPP_KEYCODE_5] = INPUT_KEY_5,
|
||||
[SAPP_KEYCODE_6] = INPUT_KEY_6,
|
||||
[SAPP_KEYCODE_7] = INPUT_KEY_7,
|
||||
[SAPP_KEYCODE_8] = INPUT_KEY_8,
|
||||
[SAPP_KEYCODE_9] = INPUT_KEY_9,
|
||||
[SAPP_KEYCODE_SEMICOLON] = INPUT_KEY_SEMICOLON,
|
||||
[SAPP_KEYCODE_EQUAL] = INPUT_KEY_EQUALS,
|
||||
[SAPP_KEYCODE_A] = INPUT_KEY_A,
|
||||
[SAPP_KEYCODE_B] = INPUT_KEY_B,
|
||||
[SAPP_KEYCODE_C] = INPUT_KEY_C,
|
||||
[SAPP_KEYCODE_D] = INPUT_KEY_D,
|
||||
[SAPP_KEYCODE_E] = INPUT_KEY_E,
|
||||
[SAPP_KEYCODE_F] = INPUT_KEY_F,
|
||||
[SAPP_KEYCODE_G] = INPUT_KEY_G,
|
||||
[SAPP_KEYCODE_H] = INPUT_KEY_H,
|
||||
[SAPP_KEYCODE_I] = INPUT_KEY_I,
|
||||
[SAPP_KEYCODE_J] = INPUT_KEY_J,
|
||||
[SAPP_KEYCODE_K] = INPUT_KEY_K,
|
||||
[SAPP_KEYCODE_L] = INPUT_KEY_L,
|
||||
[SAPP_KEYCODE_M] = INPUT_KEY_M,
|
||||
[SAPP_KEYCODE_N] = INPUT_KEY_N,
|
||||
[SAPP_KEYCODE_O] = INPUT_KEY_O,
|
||||
[SAPP_KEYCODE_P] = INPUT_KEY_P,
|
||||
[SAPP_KEYCODE_Q] = INPUT_KEY_Q,
|
||||
[SAPP_KEYCODE_R] = INPUT_KEY_R,
|
||||
[SAPP_KEYCODE_S] = INPUT_KEY_S,
|
||||
[SAPP_KEYCODE_T] = INPUT_KEY_T,
|
||||
[SAPP_KEYCODE_U] = INPUT_KEY_U,
|
||||
[SAPP_KEYCODE_V] = INPUT_KEY_V,
|
||||
[SAPP_KEYCODE_W] = INPUT_KEY_W,
|
||||
[SAPP_KEYCODE_X] = INPUT_KEY_X,
|
||||
[SAPP_KEYCODE_Y] = INPUT_KEY_Y,
|
||||
[SAPP_KEYCODE_Z] = INPUT_KEY_Z,
|
||||
[SAPP_KEYCODE_LEFT_BRACKET] = INPUT_KEY_LEFTBRACKET,
|
||||
[SAPP_KEYCODE_BACKSLASH] = INPUT_KEY_BACKSLASH,
|
||||
[SAPP_KEYCODE_RIGHT_BRACKET] = INPUT_KEY_RIGHTBRACKET,
|
||||
[SAPP_KEYCODE_GRAVE_ACCENT] = INPUT_KEY_TILDE,
|
||||
[SAPP_KEYCODE_WORLD_1] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_WORLD_2] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_ESCAPE] = INPUT_KEY_ESCAPE,
|
||||
[SAPP_KEYCODE_ENTER] = INPUT_KEY_RETURN,
|
||||
[SAPP_KEYCODE_TAB] = INPUT_KEY_TAB,
|
||||
[SAPP_KEYCODE_BACKSPACE] = INPUT_KEY_BACKSPACE,
|
||||
[SAPP_KEYCODE_INSERT] = INPUT_KEY_INSERT,
|
||||
[SAPP_KEYCODE_DELETE] = INPUT_KEY_DELETE,
|
||||
[SAPP_KEYCODE_RIGHT] = INPUT_KEY_RIGHT,
|
||||
[SAPP_KEYCODE_LEFT] = INPUT_KEY_LEFT,
|
||||
[SAPP_KEYCODE_DOWN] = INPUT_KEY_DOWN,
|
||||
[SAPP_KEYCODE_UP] = INPUT_KEY_UP,
|
||||
[SAPP_KEYCODE_PAGE_UP] = INPUT_KEY_PAGEUP,
|
||||
[SAPP_KEYCODE_PAGE_DOWN] = INPUT_KEY_PAGEDOWN,
|
||||
[SAPP_KEYCODE_HOME] = INPUT_KEY_HOME,
|
||||
[SAPP_KEYCODE_END] = INPUT_KEY_END,
|
||||
[SAPP_KEYCODE_CAPS_LOCK] = INPUT_KEY_CAPSLOCK,
|
||||
[SAPP_KEYCODE_SCROLL_LOCK] = INPUT_KEY_SCROLLLOCK,
|
||||
[SAPP_KEYCODE_NUM_LOCK] = INPUT_KEY_NUMLOCK,
|
||||
[SAPP_KEYCODE_PRINT_SCREEN] = INPUT_KEY_PRINTSCREEN,
|
||||
[SAPP_KEYCODE_PAUSE] = INPUT_KEY_PAUSE,
|
||||
[SAPP_KEYCODE_F1] = INPUT_KEY_F1,
|
||||
[SAPP_KEYCODE_F2] = INPUT_KEY_F2,
|
||||
[SAPP_KEYCODE_F3] = INPUT_KEY_F3,
|
||||
[SAPP_KEYCODE_F4] = INPUT_KEY_F4,
|
||||
[SAPP_KEYCODE_F5] = INPUT_KEY_F5,
|
||||
[SAPP_KEYCODE_F6] = INPUT_KEY_F6,
|
||||
[SAPP_KEYCODE_F7] = INPUT_KEY_F7,
|
||||
[SAPP_KEYCODE_F8] = INPUT_KEY_F8,
|
||||
[SAPP_KEYCODE_F9] = INPUT_KEY_F9,
|
||||
[SAPP_KEYCODE_F10] = INPUT_KEY_F10,
|
||||
[SAPP_KEYCODE_F11] = INPUT_KEY_F11,
|
||||
[SAPP_KEYCODE_F12] = INPUT_KEY_F12,
|
||||
[SAPP_KEYCODE_F13] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F14] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F15] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F16] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F17] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F18] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F19] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F20] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F21] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F22] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F23] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F24] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_F25] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_KP_0] = INPUT_KEY_KP_0,
|
||||
[SAPP_KEYCODE_KP_1] = INPUT_KEY_KP_1,
|
||||
[SAPP_KEYCODE_KP_2] = INPUT_KEY_KP_2,
|
||||
[SAPP_KEYCODE_KP_3] = INPUT_KEY_KP_3,
|
||||
[SAPP_KEYCODE_KP_4] = INPUT_KEY_KP_4,
|
||||
[SAPP_KEYCODE_KP_5] = INPUT_KEY_KP_5,
|
||||
[SAPP_KEYCODE_KP_6] = INPUT_KEY_KP_6,
|
||||
[SAPP_KEYCODE_KP_7] = INPUT_KEY_KP_7,
|
||||
[SAPP_KEYCODE_KP_8] = INPUT_KEY_KP_8,
|
||||
[SAPP_KEYCODE_KP_9] = INPUT_KEY_KP_9,
|
||||
[SAPP_KEYCODE_KP_DECIMAL] = INPUT_KEY_KP_PERIOD,
|
||||
[SAPP_KEYCODE_KP_DIVIDE] = INPUT_KEY_KP_DIVIDE,
|
||||
[SAPP_KEYCODE_KP_MULTIPLY] = INPUT_KEY_KP_MULTIPLY,
|
||||
[SAPP_KEYCODE_KP_SUBTRACT] = INPUT_KEY_KP_MINUS,
|
||||
[SAPP_KEYCODE_KP_ADD] = INPUT_KEY_KP_PLUS,
|
||||
[SAPP_KEYCODE_KP_ENTER] = INPUT_KEY_KP_ENTER,
|
||||
[SAPP_KEYCODE_KP_EQUAL] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_LEFT_SHIFT] = INPUT_KEY_LSHIFT,
|
||||
[SAPP_KEYCODE_LEFT_CONTROL] = INPUT_KEY_LCTRL,
|
||||
[SAPP_KEYCODE_LEFT_ALT] = INPUT_KEY_LALT,
|
||||
[SAPP_KEYCODE_LEFT_SUPER] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_RIGHT_SHIFT] = INPUT_KEY_RSHIFT,
|
||||
[SAPP_KEYCODE_RIGHT_CONTROL] = INPUT_KEY_RCTRL,
|
||||
[SAPP_KEYCODE_RIGHT_ALT] = INPUT_KEY_RALT,
|
||||
[SAPP_KEYCODE_RIGHT_SUPER] = INPUT_INVALID, // not implemented
|
||||
[SAPP_KEYCODE_MENU] = INPUT_INVALID, // not implemented
|
||||
};
|
||||
|
||||
|
||||
static void (*audio_callback)(float *buffer, uint32_t len) = NULL;
|
||||
|
||||
void platform_exit() {
|
||||
sapp_quit();
|
||||
}
|
||||
|
||||
vec2i_t platform_screen_size() {
|
||||
return vec2i(sapp_width(), sapp_height());
|
||||
}
|
||||
|
||||
double platform_now() {
|
||||
return stm_sec(stm_now());
|
||||
}
|
||||
|
||||
void platform_set_fullscreen(bool fullscreen) {
|
||||
if (fullscreen == sapp_is_fullscreen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
sapp_toggle_fullscreen();
|
||||
sapp_show_mouse(!fullscreen);
|
||||
}
|
||||
|
||||
void platform_handle_event(const sapp_event* ev) {
|
||||
// Input Keyboard
|
||||
if (ev->type == SAPP_EVENTTYPE_KEY_DOWN || ev->type == SAPP_EVENTTYPE_KEY_UP) {
|
||||
float state = ev->type == SAPP_EVENTTYPE_KEY_DOWN ? 1.0 : 0.0;
|
||||
if (ev->key_code > 0 && ev->key_code < sizeof(keyboard_map)) {
|
||||
int code = keyboard_map[ev->key_code];
|
||||
input_set_button_state(code, state);
|
||||
}
|
||||
}
|
||||
|
||||
else if (ev->type == SAPP_EVENTTYPE_CHAR) {
|
||||
input_textinput(ev->char_code);
|
||||
}
|
||||
|
||||
|
||||
// Input Gamepad Axis
|
||||
// TODO: not implemented by sokol_app itself
|
||||
|
||||
// Mouse buttons
|
||||
else if (
|
||||
ev->type == SAPP_EVENTTYPE_MOUSE_DOWN ||
|
||||
ev->type == SAPP_EVENTTYPE_MOUSE_UP
|
||||
) {
|
||||
button_t button = INPUT_BUTTON_NONE;
|
||||
switch (ev->mouse_button) {
|
||||
case SAPP_MOUSEBUTTON_LEFT: button = INPUT_MOUSE_LEFT; break;
|
||||
case SAPP_MOUSEBUTTON_MIDDLE: button = INPUT_MOUSE_MIDDLE; break;
|
||||
case SAPP_MOUSEBUTTON_RIGHT: button = INPUT_MOUSE_RIGHT; break;
|
||||
default: break;
|
||||
}
|
||||
if (button != INPUT_BUTTON_NONE) {
|
||||
float state = ev->type == SAPP_EVENTTYPE_MOUSE_DOWN ? 1.0 : 0.0;
|
||||
input_set_button_state(button, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse wheel
|
||||
else if (ev->type == SAPP_EVENTTYPE_MOUSE_SCROLL) {
|
||||
button_t button = ev->scroll_y > 0
|
||||
? INPUT_MOUSE_WHEEL_UP
|
||||
: INPUT_MOUSE_WHEEL_DOWN;
|
||||
input_set_button_state(button, 1.0);
|
||||
input_set_button_state(button, 0.0);
|
||||
}
|
||||
|
||||
// Mouse move
|
||||
else if (ev->type == SAPP_EVENTTYPE_MOUSE_MOVE) {
|
||||
input_set_mouse_pos(ev->mouse_x, ev->mouse_y);
|
||||
}
|
||||
|
||||
// Window Events
|
||||
if (ev->type == SAPP_EVENTTYPE_RESIZED) {
|
||||
system_resize(vec2i(ev->window_width, ev->window_height));
|
||||
}
|
||||
}
|
||||
|
||||
void platform_audio_callback(float* buffer, int num_frames, int num_channels) {
|
||||
if (audio_callback) {
|
||||
audio_callback(buffer, num_frames * num_channels);
|
||||
}
|
||||
else {
|
||||
memset(buffer, 0, num_frames * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
void platform_set_audio_mix_cb(void (*cb)(float *buffer, uint32_t len)) {
|
||||
audio_callback = cb;
|
||||
}
|
||||
|
||||
sapp_desc sokol_main(int argc, char* argv[]) {
|
||||
stm_setup();
|
||||
|
||||
saudio_setup(&(saudio_desc){
|
||||
.sample_rate = 44100,
|
||||
.buffer_frames = 1024,
|
||||
.num_packets = 256,
|
||||
.num_channels = 2,
|
||||
.stream_cb = platform_audio_callback,
|
||||
});
|
||||
|
||||
return (sapp_desc) {
|
||||
.width = SYSTEM_WINDOW_WIDTH,
|
||||
.height = SYSTEM_WINDOW_HEIGHT,
|
||||
.init_cb = system_init,
|
||||
.frame_cb = system_update,
|
||||
.cleanup_cb = system_cleanup,
|
||||
.event_cb = platform_handle_event,
|
||||
.win32_console_attach = true
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef RENDER_H
|
||||
#define RENDER_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
typedef enum {
|
||||
RENDER_BLEND_NORMAL,
|
||||
RENDER_BLEND_LIGHTER
|
||||
} render_blend_mode_t;
|
||||
|
||||
typedef enum {
|
||||
RENDER_RES_NATIVE,
|
||||
RENDER_RES_240P,
|
||||
RENDER_RES_480P,
|
||||
} render_resolution_t;
|
||||
|
||||
typedef enum {
|
||||
RENDER_POST_NONE,
|
||||
RENDER_POST_CRT,
|
||||
NUM_RENDER_POST_EFFCTS,
|
||||
} render_post_effect_t;
|
||||
|
||||
#define RENDER_USE_MIPMAPS 1
|
||||
|
||||
#define RENDER_FADEOUT_NEAR 48000.0
|
||||
#define RENDER_FADEOUT_FAR 64000.0
|
||||
|
||||
extern uint16_t RENDER_NO_TEXTURE;
|
||||
|
||||
void render_init(vec2i_t screen_size);
|
||||
void render_cleanup();
|
||||
|
||||
void render_set_screen_size(vec2i_t size);
|
||||
void render_set_resolution(render_resolution_t res);
|
||||
void render_set_post_effect(render_post_effect_t post);
|
||||
vec2i_t render_size();
|
||||
|
||||
void render_frame_prepare();
|
||||
void render_frame_end();
|
||||
|
||||
void render_set_view(vec3_t pos, vec3_t angles);
|
||||
void render_set_view_2d();
|
||||
void render_set_model_mat(mat4_t *m);
|
||||
void render_set_depth_write(bool enabled);
|
||||
void render_set_depth_test(bool enabled);
|
||||
void render_set_depth_offset(float offset);
|
||||
void render_set_screen_position(vec2_t pos);
|
||||
void render_set_blend_mode(render_blend_mode_t mode);
|
||||
void render_set_cull_backface(bool enabled);
|
||||
|
||||
vec3_t render_transform(vec3_t pos);
|
||||
void render_push_tris(tris_t tris, uint16_t texture);
|
||||
void render_push_sprite(vec3_t pos, vec2i_t size, rgba_t color, uint16_t texture);
|
||||
void render_push_2d(vec2i_t pos, vec2i_t size, rgba_t color, uint16_t texture);
|
||||
void render_push_2d_tile(vec2i_t pos, vec2i_t uv_offset, vec2i_t uv_size, vec2i_t size, rgba_t color, uint16_t texture_index);
|
||||
|
||||
uint16_t render_texture_create(uint32_t width, uint32_t height, rgba_t *pixels);
|
||||
vec2i_t render_texture_size(uint16_t texture_index);
|
||||
void render_texture_replace_pixels(int16_t texture_index, rgba_t *pixels);
|
||||
uint16_t render_textures_len();
|
||||
void render_textures_reset(uint16_t len);
|
||||
void render_textures_dump(const char *path);
|
||||
|
||||
#endif
|
||||
+1001
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,369 @@
|
||||
#include "system.h"
|
||||
#include "render.h"
|
||||
#include "mem.h"
|
||||
#include "utils.h"
|
||||
#include "platform.h"
|
||||
|
||||
#define NEAR_PLANE 16.0
|
||||
#define FAR_PLANE (RENDER_FADEOUT_FAR)
|
||||
#define TEXTURES_MAX 1024
|
||||
|
||||
|
||||
typedef struct {
|
||||
vec2i_t size;
|
||||
rgba_t *pixels;
|
||||
} render_texture_t;
|
||||
|
||||
static void line(vec2i_t p0, vec2i_t p1, rgba_t color);
|
||||
|
||||
static rgba_t *screen_buffer;
|
||||
static int32_t screen_pitch;
|
||||
static int32_t screen_ppr;
|
||||
static vec2i_t screen_size;
|
||||
|
||||
static mat4_t view_mat = mat4_identity();
|
||||
static mat4_t mvp_mat = mat4_identity();
|
||||
static mat4_t projection_mat = mat4_identity();
|
||||
static mat4_t sprite_mat = mat4_identity();
|
||||
|
||||
static render_texture_t textures[TEXTURES_MAX];
|
||||
static uint32_t textures_len;
|
||||
|
||||
uint16_t RENDER_NO_TEXTURE;
|
||||
|
||||
|
||||
void render_init(vec2i_t screen_size) {
|
||||
render_set_screen_size(screen_size);
|
||||
textures_len = 0;
|
||||
|
||||
rgba_t white_pixels[4] = {
|
||||
rgba(128,128,128,255), rgba(128,128,128,255),
|
||||
rgba(128,128,128,255), rgba(128,128,128,255)
|
||||
};
|
||||
RENDER_NO_TEXTURE = render_texture_create(2, 2, white_pixels);
|
||||
}
|
||||
|
||||
void render_cleanup() {}
|
||||
|
||||
void render_set_screen_size(vec2i_t size) {
|
||||
screen_size = size;
|
||||
|
||||
float aspect = (float)size.x / (float)size.y;
|
||||
float fov = (73.75 / 180.0) * 3.14159265358;
|
||||
float f = 1.0 / tan(fov / 2);
|
||||
float nf = 1.0 / (NEAR_PLANE - FAR_PLANE);
|
||||
projection_mat = mat4(
|
||||
f / aspect, 0, 0, 0,
|
||||
0, f, 0, 0,
|
||||
0, 0, (FAR_PLANE + NEAR_PLANE) * nf, -1,
|
||||
0, 0, 2 * FAR_PLANE * NEAR_PLANE * nf, 0
|
||||
);
|
||||
}
|
||||
|
||||
void render_set_resolution(render_resolution_t res) {}
|
||||
void render_set_post_effect(render_post_effect_t post) {}
|
||||
|
||||
vec2i_t render_size() {
|
||||
return screen_size;
|
||||
}
|
||||
|
||||
|
||||
void render_frame_prepare() {
|
||||
screen_buffer = platform_get_screenbuffer(&screen_pitch);
|
||||
screen_ppr = screen_pitch / sizeof(rgba_t);
|
||||
|
||||
rgba_t color = rgba(0, 0, 0, 255);
|
||||
for (uint32_t y = 0; y < screen_size.y; y++) {
|
||||
for (uint32_t x = 0; x < screen_size.x; x++) {
|
||||
screen_buffer[y * screen_ppr + x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void render_frame_end() {}
|
||||
|
||||
void render_set_view(vec3_t pos, vec3_t angles) {
|
||||
view_mat = mat4_identity();
|
||||
mat4_set_translation(&view_mat, vec3(0, 0, 0));
|
||||
mat4_set_roll_pitch_yaw(&view_mat, vec3(angles.x, -angles.y + M_PI, angles.z + M_PI));
|
||||
mat4_translate(&view_mat, vec3_inv(pos));
|
||||
mat4_set_yaw_pitch_roll(&sprite_mat, vec3(-angles.x, angles.y - M_PI, 0));
|
||||
|
||||
render_set_model_mat(&mat4_identity());
|
||||
}
|
||||
|
||||
void render_set_view_2d() {
|
||||
float near = -1;
|
||||
float far = 1;
|
||||
float left = 0;
|
||||
float right = screen_size.x;
|
||||
float bottom = screen_size.y;
|
||||
float top = 0;
|
||||
float lr = 1 / (left - right);
|
||||
float bt = 1 / (bottom - top);
|
||||
float nf = 1 / (near - far);
|
||||
mvp_mat = mat4(
|
||||
-2 * lr, 0, 0, 0,
|
||||
0, -2 * bt, 0, 0,
|
||||
0, 0, 2 * nf, 0,
|
||||
(left + right) * lr, (top + bottom) * bt, (far + near) * nf, 1
|
||||
);
|
||||
}
|
||||
|
||||
void render_set_model_mat(mat4_t *m) {
|
||||
mat4_t vm_mat;
|
||||
mat4_mul(&vm_mat, &view_mat, m);
|
||||
mat4_mul(&mvp_mat, &projection_mat, &vm_mat);
|
||||
}
|
||||
|
||||
void render_set_depth_write(bool enabled) {}
|
||||
void render_set_depth_test(bool enabled) {}
|
||||
void render_set_depth_offset(float offset) {}
|
||||
void render_set_screen_position(vec2_t pos) {}
|
||||
void render_set_blend_mode(render_blend_mode_t mode) {}
|
||||
void render_set_cull_backface(bool enabled) {}
|
||||
|
||||
vec3_t render_transform(vec3_t pos) {
|
||||
return vec3_transform(vec3_transform(pos, &view_mat), &projection_mat);
|
||||
}
|
||||
|
||||
void render_push_tris(tris_t tris, uint16_t texture_index) {
|
||||
float w2 = screen_size.x * 0.5;
|
||||
float h2 = screen_size.y * 0.5;
|
||||
|
||||
vec3_t p0 = vec3_transform(tris.vertices[0].pos, &mvp_mat);
|
||||
vec3_t p1 = vec3_transform(tris.vertices[1].pos, &mvp_mat);
|
||||
vec3_t p2 = vec3_transform(tris.vertices[2].pos, &mvp_mat);
|
||||
if (p0.z >= 1.0 || p1.z >= 1.0 || p2.z >= 1.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
vec2i_t sc0 = vec2i(p0.x * w2 + w2, h2 - p0.y * h2);
|
||||
vec2i_t sc1 = vec2i(p1.x * w2 + w2, h2 - p1.y * h2);
|
||||
vec2i_t sc2 = vec2i(p2.x * w2 + w2, h2 - p2.y * h2);
|
||||
|
||||
rgba_t color = tris.vertices[0].color;
|
||||
color.as_rgba.r = min(color.as_rgba.r * 2, 255);
|
||||
color.as_rgba.g = min(color.as_rgba.g * 2, 255);
|
||||
color.as_rgba.b = min(color.as_rgba.b * 2, 255);
|
||||
color.as_rgba.a = clamp(color.as_rgba.a * (1.0-p0.z) * FAR_PLANE * (2.0/255.0), 0, 255);
|
||||
|
||||
line(sc0, sc1, color);
|
||||
line(sc1, sc2, color);
|
||||
line(sc2, sc0, color);
|
||||
}
|
||||
|
||||
void render_push_sprite(vec3_t pos, vec2i_t size, rgba_t color, uint16_t texture_index) {
|
||||
error_if(texture_index >= textures_len, "Invalid texture %d", texture_index);
|
||||
|
||||
vec3_t p0 = vec3_add(pos, vec3_transform(vec3(-size.x * 0.5, -size.y * 0.5, 0), &sprite_mat));
|
||||
vec3_t p1 = vec3_add(pos, vec3_transform(vec3( size.x * 0.5, -size.y * 0.5, 0), &sprite_mat));
|
||||
vec3_t p2 = vec3_add(pos, vec3_transform(vec3(-size.x * 0.5, size.y * 0.5, 0), &sprite_mat));
|
||||
vec3_t p3 = vec3_add(pos, vec3_transform(vec3( size.x * 0.5, size.y * 0.5, 0), &sprite_mat));
|
||||
|
||||
render_texture_t *t = &textures[texture_index];
|
||||
render_push_tris((tris_t){
|
||||
.vertices = {
|
||||
{.pos = p0, .uv = {0, 0}, .color = color},
|
||||
{.pos = p1, .uv = {0 + t->size.x ,0}, .color = color},
|
||||
{.pos = p2, .uv = {0, 0 + t->size.y}, .color = color},
|
||||
}
|
||||
}, texture_index);
|
||||
render_push_tris((tris_t){
|
||||
.vertices = {
|
||||
{.pos = p2, .uv = {0, 0 + t->size.y}, .color = color},
|
||||
{.pos = p1, .uv = {0 + t->size.x, 0}, .color = color},
|
||||
{.pos = p3, .uv = {0 + t->size.x, 0 + t->size.y}, .color = color},
|
||||
}
|
||||
}, texture_index);
|
||||
}
|
||||
|
||||
void render_push_2d(vec2i_t pos, vec2i_t size, rgba_t color, uint16_t texture_index) {
|
||||
render_push_2d_tile(pos, vec2i(0, 0), render_texture_size(texture_index), size, color, texture_index);
|
||||
}
|
||||
|
||||
void render_push_2d_tile(vec2i_t pos, vec2i_t uv_offset, vec2i_t uv_size, vec2i_t size, rgba_t color, uint16_t texture_index) {
|
||||
error_if(texture_index >= textures_len, "Invalid texture %d", texture_index);
|
||||
render_push_tris((tris_t){
|
||||
.vertices = {
|
||||
{.pos = {pos.x, pos.y + size.y, 0}, .uv = {uv_offset.x , uv_offset.y + uv_size.y}, .color = color},
|
||||
{.pos = {pos.x + size.x, pos.y, 0}, .uv = {uv_offset.x + uv_size.x, uv_offset.y}, .color = color},
|
||||
{.pos = {pos.x, pos.y, 0}, .uv = {uv_offset.x , uv_offset.y}, .color = color},
|
||||
}
|
||||
}, texture_index);
|
||||
|
||||
render_push_tris((tris_t){
|
||||
.vertices = {
|
||||
{.pos = {pos.x + size.x, pos.y + size.y, 0}, .uv = {uv_offset.x + uv_size.x, uv_offset.y + uv_size.y}, .color = color},
|
||||
{.pos = {pos.x + size.x, pos.y, 0}, .uv = {uv_offset.x + uv_size.x, uv_offset.y}, .color = color},
|
||||
{.pos = {pos.x, pos.y + size.y, 0}, .uv = {uv_offset.x , uv_offset.y + uv_size.y}, .color = color},
|
||||
}
|
||||
}, texture_index);
|
||||
}
|
||||
|
||||
|
||||
uint16_t render_texture_create(uint32_t width, uint32_t height, rgba_t *pixels) {
|
||||
error_if(textures_len >= TEXTURES_MAX, "TEXTURES_MAX reached");
|
||||
|
||||
uint32_t byte_size = width * height * sizeof(rgba_t);
|
||||
uint16_t texture_index = textures_len;
|
||||
|
||||
textures[texture_index] = (render_texture_t){{width, height}, NULL};
|
||||
// textures[texture_index] = (render_texture_t){{width, height}, mem_bump(byte_size)};
|
||||
// memcpy(textures[texture_index].pixels, pixels, byte_size);
|
||||
|
||||
textures_len++;
|
||||
return texture_index;
|
||||
}
|
||||
|
||||
vec2i_t render_texture_size(uint16_t texture_index) {
|
||||
error_if(texture_index >= textures_len, "Invalid texture %d", texture_index);
|
||||
return textures[texture_index].size;
|
||||
}
|
||||
|
||||
void render_texture_replace_pixels(int16_t texture_index, rgba_t *pixels) {
|
||||
error_if(texture_index >= textures_len, "Invalid texture %d", texture_index);
|
||||
render_texture_t *t = &textures[texture_index];
|
||||
// memcpy(t->pixels, pixels, t->size.x * t->size.y * sizeof(rgba_t));
|
||||
}
|
||||
|
||||
uint16_t render_textures_len() {
|
||||
return textures_len;
|
||||
}
|
||||
|
||||
void render_textures_reset(uint16_t len) {
|
||||
error_if(len > textures_len, "Invalid texture reset len %d >= %d", len, textures_len);
|
||||
textures_len = len;
|
||||
}
|
||||
|
||||
void render_textures_dump(const char *path) {}
|
||||
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
static inline rgba_t color_mix(rgba_t in, rgba_t out) {
|
||||
return rgba(
|
||||
lerp(in.as_rgba.r, out.as_rgba.r, out.as_rgba.a/255.0),
|
||||
lerp(in.as_rgba.g, out.as_rgba.g, out.as_rgba.a/255.0),
|
||||
lerp(in.as_rgba.b, out.as_rgba.b, out.as_rgba.a/255.0),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
CLIP_INSIDE = 0,
|
||||
CLIP_LEFT = (1<<0),
|
||||
CLIP_RIGHT = (1<<1),
|
||||
CLIP_BOTTOM = (1<<2),
|
||||
CLIP_TOP = (1<<3),
|
||||
} clip_code_t;
|
||||
|
||||
static inline clip_code_t clip_code(vec2i_t p) {
|
||||
clip_code_t cc = CLIP_INSIDE;
|
||||
if (p.x < 0) {
|
||||
flags_add(cc, CLIP_LEFT);
|
||||
}
|
||||
else if (p.x >= screen_size.x) {
|
||||
flags_add(cc, CLIP_RIGHT);
|
||||
}
|
||||
if (p.y < 0) {
|
||||
flags_add(cc, CLIP_BOTTOM);
|
||||
}
|
||||
else if (p.y >= screen_size.y) {
|
||||
flags_add(cc, CLIP_TOP);
|
||||
}
|
||||
return cc;
|
||||
}
|
||||
|
||||
static void line(vec2i_t p0, vec2i_t p1, rgba_t color) {
|
||||
// Cohen Sutherland Line Clipping
|
||||
clip_code_t cc0 = clip_code(p0);
|
||||
clip_code_t cc1 = clip_code(p1);
|
||||
bool accept = false;
|
||||
|
||||
vec2i_t ss = vec2i(screen_size.x-1, screen_size.y-1);
|
||||
while (true) {
|
||||
if (!(cc0 | cc1)) {
|
||||
accept = true;
|
||||
break;
|
||||
}
|
||||
else if (cc0 & cc1) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
vec2i_t r = p0;
|
||||
clip_code_t cc_out = cc0 ? cc0 : cc1;
|
||||
|
||||
if (flags_is(cc_out, CLIP_TOP)) {
|
||||
r.x = p0.x + (p1.x - p0.x) * (ss.y - p0.y) / (p1.y - p0.y);
|
||||
r.y = ss.y;
|
||||
}
|
||||
else if (flags_is(cc_out, CLIP_BOTTOM)) {
|
||||
r.x = p0.x + (p1.x - p0.x) * (-p0.y) / (p1.y - p0.y);
|
||||
r.y = 0;
|
||||
}
|
||||
else if (flags_is(cc_out, CLIP_RIGHT)) {
|
||||
r.y = p0.y + (p1.y - p0.y) * (ss.x - p0.x) / (p1.x - p0.x);
|
||||
r.x = ss.x;
|
||||
}
|
||||
else if (flags_is(cc_out, CLIP_LEFT)) {
|
||||
r.y = p0.y + (p1.y - p0.y) * (-p0.x) / (p1.x - p0.x);
|
||||
r.x = 0;
|
||||
}
|
||||
|
||||
if (cc_out == cc0) {
|
||||
p0.x = r.x;
|
||||
p0.y = r.y;
|
||||
cc0 = clip_code(p0);
|
||||
}
|
||||
else {
|
||||
p1.x = r.x;
|
||||
p1.y = r.y;
|
||||
cc1 = clip_code(p1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!accept) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bresenham's line algorithm
|
||||
bool steep = false;
|
||||
if (abs(p0.x - p1.x) < abs(p0.y - p1.y)) {
|
||||
swap(p0.x, p0.y);
|
||||
swap(p1.x, p1.y);
|
||||
steep = true;
|
||||
}
|
||||
if (p0.x > p1.x) {
|
||||
swap(p0.x, p1.x);
|
||||
swap(p0.y, p1.y);
|
||||
}
|
||||
int32_t dx = p1.x - p0.x;
|
||||
int32_t dy = p1.y - p0.y;
|
||||
int32_t derror2 = abs(dy) * 2;
|
||||
int32_t error2 = 0;
|
||||
int32_t y = p0.y;
|
||||
int32_t ydir = (p1.y > p0.y ? 1 : -1);
|
||||
|
||||
if (steep) {
|
||||
for (int32_t x = p0.x; x <= p1.x; x++) {
|
||||
screen_buffer[x * screen_ppr + y] = color_mix(screen_buffer[x * screen_ppr + y], color);
|
||||
error2 += derror2;
|
||||
if (error2 > dx) {
|
||||
y += ydir;
|
||||
error2 -= dx * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int32_t x = p0.x; x <= p1.x; x++) {
|
||||
screen_buffer[y * screen_ppr + x] = color_mix(screen_buffer[y * screen_ppr + x], color);
|
||||
error2 += derror2;
|
||||
if (error2 > dx) {
|
||||
y += ydir;
|
||||
error2 -= dx * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "system.h"
|
||||
#include "input.h"
|
||||
#include "render.h"
|
||||
#include "platform.h"
|
||||
#include "mem.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "wipeout/game.h"
|
||||
|
||||
static double time_real;
|
||||
static double time_scaled;
|
||||
static double time_scale = 1.0;
|
||||
static double tick_last;
|
||||
static double cycle_time = 0;
|
||||
|
||||
void system_init() {
|
||||
time_real = platform_now();
|
||||
input_init();
|
||||
render_init(platform_screen_size());
|
||||
game_init();
|
||||
}
|
||||
|
||||
void system_cleanup() {
|
||||
render_cleanup();
|
||||
input_cleanup();
|
||||
}
|
||||
|
||||
void system_exit() {
|
||||
platform_exit();
|
||||
}
|
||||
|
||||
void system_update() {
|
||||
double time_real_now = platform_now();
|
||||
double real_delta = time_real_now - time_real;
|
||||
time_real = time_real_now;
|
||||
tick_last = min(real_delta, 0.1) * time_scale;
|
||||
time_scaled += tick_last;
|
||||
|
||||
// FIXME: come up with a better way to wrap the cycle_time, so that it
|
||||
// doesn't lose precission, but also doesn't jump upon reset.
|
||||
cycle_time = time_scaled;
|
||||
if (cycle_time > 3600 * M_PI) {
|
||||
cycle_time -= 3600 * M_PI;
|
||||
}
|
||||
|
||||
render_frame_prepare();
|
||||
|
||||
game_update();
|
||||
|
||||
render_frame_end();
|
||||
input_clear();
|
||||
mem_temp_check();
|
||||
}
|
||||
|
||||
void system_reset_cycle_time() {
|
||||
cycle_time = 0;
|
||||
}
|
||||
|
||||
void system_resize(vec2i_t size) {
|
||||
render_set_screen_size(size);
|
||||
}
|
||||
|
||||
double system_time_scale_get() {
|
||||
return time_scale;
|
||||
}
|
||||
|
||||
void system_time_scale_set(double scale) {
|
||||
time_scale = scale;
|
||||
}
|
||||
|
||||
double system_tick() {
|
||||
return tick_last;
|
||||
}
|
||||
|
||||
double system_time() {
|
||||
return time_scaled;
|
||||
}
|
||||
|
||||
double system_cycle_time() {
|
||||
return cycle_time;
|
||||
}
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#ifndef SYSTEM_H
|
||||
#define SYSTEM_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define SYSTEM_WINDOW_NAME "wipEout"
|
||||
#define SYSTEM_WINDOW_WIDTH 1280
|
||||
#define SYSTEM_WINDOW_HEIGHT 720
|
||||
|
||||
void system_init();
|
||||
void system_update();
|
||||
void system_cleanup();
|
||||
void system_exit();
|
||||
void system_resize(vec2i_t size);
|
||||
|
||||
double system_time();
|
||||
double system_tick();
|
||||
double system_cycle_time();
|
||||
void system_reset_cycle_time();
|
||||
double system_time_scale_get();
|
||||
void system_time_scale_set(double ts);
|
||||
|
||||
#endif
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
#include <math.h>
|
||||
#include "types.h"
|
||||
#include "utils.h"
|
||||
|
||||
vec3_t vec3_wrap_angle(vec3_t a) {
|
||||
return vec3(wrap_angle(a.x), wrap_angle(a.y), wrap_angle(a.z));
|
||||
}
|
||||
|
||||
float vec3_angle(vec3_t a, vec3_t b) {
|
||||
float magnitude = sqrt(
|
||||
(a.x * a.x + a.y * a.y + a.z * a.z) *
|
||||
(b.x * b.x + b.y * b.y + b.z * b.z)
|
||||
);
|
||||
float cosine = (magnitude == 0)
|
||||
? 1
|
||||
: vec3_dot(a, b) / magnitude;
|
||||
return acos(clamp(cosine, -1, 1));
|
||||
}
|
||||
|
||||
vec3_t vec3_transform(vec3_t a, mat4_t *mat) {
|
||||
float w = mat->m[3] * a.x + mat->m[7] * a.y + mat->m[11] * a.z + mat->m[15];
|
||||
if (w == 0) {
|
||||
w = 1;
|
||||
}
|
||||
return vec3(
|
||||
(mat->m[0] * a.x + mat->m[4] * a.y + mat->m[ 8] * a.z + mat->m[12]) / w,
|
||||
(mat->m[1] * a.x + mat->m[5] * a.y + mat->m[ 9] * a.z + mat->m[13]) / w,
|
||||
(mat->m[2] * a.x + mat->m[6] * a.y + mat->m[10] * a.z + mat->m[14]) / w
|
||||
);
|
||||
}
|
||||
|
||||
vec3_t vec3_project_to_ray(vec3_t p, vec3_t r0, vec3_t r1) {
|
||||
vec3_t ray = vec3_normalize(vec3_sub(r1, r0));
|
||||
float dp = vec3_dot(vec3_sub(p, r0), ray);
|
||||
return vec3_add(r0, vec3_mulf(ray, dp));
|
||||
}
|
||||
|
||||
float vec3_distance_to_plane(vec3_t p, vec3_t plane_pos, vec3_t plane_normal) {
|
||||
float dot_product = vec3_dot(vec3_sub(plane_pos, p), plane_normal);
|
||||
float norm_dot_product = vec3_dot(vec3_mulf(plane_normal, -1), plane_normal);
|
||||
return dot_product / norm_dot_product;
|
||||
}
|
||||
|
||||
vec3_t vec3_reflect(vec3_t incidence, vec3_t normal, float f) {
|
||||
return vec3_add(incidence, vec3_mulf(normal, vec3_dot(normal, vec3_mulf(incidence, -1)) * f));
|
||||
}
|
||||
|
||||
void mat4_set_translation(mat4_t *mat, vec3_t pos) {
|
||||
mat->cols[3][0] = pos.x;
|
||||
mat->cols[3][1] = pos.y;
|
||||
mat->cols[3][2] = pos.z;
|
||||
}
|
||||
|
||||
void mat4_set_yaw_pitch_roll(mat4_t *mat, vec3_t rot) {
|
||||
float sx = sin( rot.x);
|
||||
float sy = sin(-rot.y);
|
||||
float sz = sin(-rot.z);
|
||||
float cx = cos( rot.x);
|
||||
float cy = cos(-rot.y);
|
||||
float cz = cos(-rot.z);
|
||||
|
||||
mat->cols[0][0] = cy * cz + sx * sy * sz;
|
||||
mat->cols[1][0] = cz * sx * sy - cy * sz;
|
||||
mat->cols[2][0] = cx * sy;
|
||||
mat->cols[0][1] = cx * sz;
|
||||
mat->cols[1][1] = cx * cz;
|
||||
mat->cols[2][1] = -sx;
|
||||
mat->cols[0][2] = -cz * sy + cy * sx * sz;
|
||||
mat->cols[1][2] = cy * cz * sx + sy * sz;
|
||||
mat->cols[2][2] = cx * cy;
|
||||
}
|
||||
|
||||
void mat4_set_roll_pitch_yaw(mat4_t *mat, vec3_t rot) {
|
||||
float sx = sin( rot.x);
|
||||
float sy = sin(-rot.y);
|
||||
float sz = sin(-rot.z);
|
||||
float cx = cos( rot.x);
|
||||
float cy = cos(-rot.y);
|
||||
float cz = cos(-rot.z);
|
||||
|
||||
mat->cols[0][0] = cy * cz - sx * sy * sz;
|
||||
mat->cols[1][0] = -cx * sz;
|
||||
mat->cols[2][0] = cz * sy + cy * sx * sz;
|
||||
mat->cols[0][1] = cz * sx * sy + cy * sz;
|
||||
mat->cols[1][1] = cx *cz;
|
||||
mat->cols[2][1] = -cy * cz * sx + sy * sz;
|
||||
mat->cols[0][2] = -cx * sy;
|
||||
mat->cols[1][2] = sx;
|
||||
mat->cols[2][2] = cx * cy;
|
||||
}
|
||||
|
||||
void mat4_translate(mat4_t *mat, vec3_t translation) {
|
||||
mat->m[12] = mat->m[0] * translation.x + mat->m[4] * translation.y + mat->m[8] * translation.z + mat->m[12];
|
||||
mat->m[13] = mat->m[1] * translation.x + mat->m[5] * translation.y + mat->m[9] * translation.z + mat->m[13];
|
||||
mat->m[14] = mat->m[2] * translation.x + mat->m[6] * translation.y + mat->m[10] * translation.z + mat->m[14];
|
||||
mat->m[15] = mat->m[3] * translation.x + mat->m[7] * translation.y + mat->m[11] * translation.z + mat->m[15];
|
||||
}
|
||||
|
||||
void mat4_mul(mat4_t *res, mat4_t *a, mat4_t *b) {
|
||||
res->m[ 0] = b->m[ 0] * a->m[0] + b->m[ 1] * a->m[4] + b->m[ 2] * a->m[ 8] + b->m[ 3] * a->m[12];
|
||||
res->m[ 1] = b->m[ 0] * a->m[1] + b->m[ 1] * a->m[5] + b->m[ 2] * a->m[ 9] + b->m[ 3] * a->m[13];
|
||||
res->m[ 2] = b->m[ 0] * a->m[2] + b->m[ 1] * a->m[6] + b->m[ 2] * a->m[10] + b->m[ 3] * a->m[14];
|
||||
res->m[ 3] = b->m[ 0] * a->m[3] + b->m[ 1] * a->m[7] + b->m[ 2] * a->m[11] + b->m[ 3] * a->m[15];
|
||||
res->m[ 4] = b->m[ 4] * a->m[0] + b->m[ 5] * a->m[4] + b->m[ 6] * a->m[ 8] + b->m[ 7] * a->m[12];
|
||||
res->m[ 5] = b->m[ 4] * a->m[1] + b->m[ 5] * a->m[5] + b->m[ 6] * a->m[ 9] + b->m[ 7] * a->m[13];
|
||||
res->m[ 6] = b->m[ 4] * a->m[2] + b->m[ 5] * a->m[6] + b->m[ 6] * a->m[10] + b->m[ 7] * a->m[14];
|
||||
res->m[ 7] = b->m[ 4] * a->m[3] + b->m[ 5] * a->m[7] + b->m[ 6] * a->m[11] + b->m[ 7] * a->m[15];
|
||||
res->m[ 8] = b->m[ 8] * a->m[0] + b->m[ 9] * a->m[4] + b->m[10] * a->m[ 8] + b->m[11] * a->m[12];
|
||||
res->m[ 9] = b->m[ 8] * a->m[1] + b->m[ 9] * a->m[5] + b->m[10] * a->m[ 9] + b->m[11] * a->m[13];
|
||||
res->m[10] = b->m[ 8] * a->m[2] + b->m[ 9] * a->m[6] + b->m[10] * a->m[10] + b->m[11] * a->m[14];
|
||||
res->m[11] = b->m[ 8] * a->m[3] + b->m[ 9] * a->m[7] + b->m[10] * a->m[11] + b->m[11] * a->m[15];
|
||||
res->m[12] = b->m[12] * a->m[0] + b->m[13] * a->m[4] + b->m[14] * a->m[ 8] + b->m[15] * a->m[12];
|
||||
res->m[13] = b->m[12] * a->m[1] + b->m[13] * a->m[5] + b->m[14] * a->m[ 9] + b->m[15] * a->m[13];
|
||||
res->m[14] = b->m[12] * a->m[2] + b->m[13] * a->m[6] + b->m[14] * a->m[10] + b->m[15] * a->m[14];
|
||||
res->m[15] = b->m[12] * a->m[3] + b->m[13] * a->m[7] + b->m[14] * a->m[11] + b->m[15] * a->m[15];
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
#ifndef TYPES_H
|
||||
#define TYPES_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
typedef union rgba_t {
|
||||
struct {
|
||||
uint8_t r, g, b, a;
|
||||
} as_rgba;
|
||||
uint8_t as_components[4];
|
||||
uint32_t as_uint32;
|
||||
} rgba_t;
|
||||
|
||||
typedef struct {
|
||||
float x, y;
|
||||
} vec2_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
int32_t x, y;
|
||||
} vec2i_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
float x, y, z;
|
||||
} vec3_t;
|
||||
|
||||
typedef union {
|
||||
float m[16];
|
||||
float cols[4][4];
|
||||
} mat4_t;
|
||||
|
||||
typedef struct {
|
||||
vec3_t pos;
|
||||
vec2_t uv;
|
||||
rgba_t color;
|
||||
} vertex_t;
|
||||
|
||||
typedef struct {
|
||||
vertex_t vertices[3];
|
||||
} tris_t;
|
||||
|
||||
|
||||
#define rgba(R, G, B, A) ((rgba_t){.as_rgba = {.r = R, .g = G, .b = B, .a = A}})
|
||||
#define vec2(X, Y) ((vec2_t){.x = X, .y = Y})
|
||||
#define vec3(X, Y, Z) ((vec3_t){.x = X, .y = Y, .z = Z})
|
||||
#define vec2i(X, Y) ((vec2i_t){.x = X, .y = Y})
|
||||
|
||||
#define mat4(m0,m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12,m13,m14,m15) \
|
||||
(mat4_t){.m = { \
|
||||
m0, m1, m2, m3, \
|
||||
m4, m5, m6, m7, \
|
||||
m8, m9, m10, m11, \
|
||||
m12, m13, m14, m15 \
|
||||
}}
|
||||
|
||||
#define mat4_identity() mat4( \
|
||||
1, 0, 0, 0, \
|
||||
0, 1, 0, 0, \
|
||||
0, 0, 1, 0, \
|
||||
0, 0, 0, 1 \
|
||||
)
|
||||
|
||||
static inline vec2_t vec2_mulf(vec2_t a, float f) {
|
||||
return vec2(
|
||||
a.x * f,
|
||||
a.y * f
|
||||
);
|
||||
}
|
||||
|
||||
static inline vec2i_t vec2i_mulf(vec2i_t a, float f) {
|
||||
return vec2i(
|
||||
a.x * f,
|
||||
a.y * f
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
static inline vec3_t vec3_add(vec3_t a, vec3_t b) {
|
||||
return vec3(
|
||||
a.x + b.x,
|
||||
a.y + b.y,
|
||||
a.z + b.z
|
||||
);
|
||||
}
|
||||
|
||||
static inline vec3_t vec3_sub(vec3_t a, vec3_t b) {
|
||||
return vec3(
|
||||
a.x - b.x,
|
||||
a.y - b.y,
|
||||
a.z - b.z
|
||||
);
|
||||
}
|
||||
|
||||
static inline vec3_t vec3_mul(vec3_t a, vec3_t b) {
|
||||
return vec3(
|
||||
a.x * b.x,
|
||||
a.y * b.y,
|
||||
a.z * b.z
|
||||
);
|
||||
}
|
||||
|
||||
static inline vec3_t vec3_mulf(vec3_t a, float f) {
|
||||
return vec3(
|
||||
a.x * f,
|
||||
a.y * f,
|
||||
a.z * f
|
||||
);
|
||||
}
|
||||
|
||||
static inline vec3_t vec3_inv(vec3_t a) {
|
||||
return vec3(-a.x, -a.y, -a.z);
|
||||
}
|
||||
|
||||
static inline vec3_t vec3_divf(vec3_t a, float f) {
|
||||
return vec3(
|
||||
a.x / f,
|
||||
a.y / f,
|
||||
a.z / f
|
||||
);
|
||||
}
|
||||
|
||||
static inline float vec3_len(vec3_t a) {
|
||||
return sqrt(a.x * a.x + a.y * a.y + a.z * a.z);
|
||||
}
|
||||
|
||||
static inline vec3_t vec3_cross(vec3_t a, vec3_t b) {
|
||||
return vec3(
|
||||
a.y * b.z - a.z * b.y,
|
||||
a.z * b.x - a.x * b.z,
|
||||
a.x * b.y - a.y * b.x
|
||||
);
|
||||
}
|
||||
|
||||
static inline float vec3_dot(vec3_t a, vec3_t b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
}
|
||||
|
||||
static inline vec3_t vec3_lerp(vec3_t a, vec3_t b, float t) {
|
||||
return vec3(
|
||||
a.x + t * (b.x - a.x),
|
||||
a.y + t * (b.y - a.y),
|
||||
a.z + t * (b.z - a.z)
|
||||
);
|
||||
}
|
||||
|
||||
static inline vec3_t vec3_normalize(vec3_t a) {
|
||||
float length = vec3_len(a);
|
||||
return vec3(
|
||||
a.x / length,
|
||||
a.y / length,
|
||||
a.z / length
|
||||
);
|
||||
}
|
||||
|
||||
static inline float wrap_angle(float a) {
|
||||
a = fmod(a + M_PI, M_PI * 2);
|
||||
if (a < 0) {
|
||||
a += M_PI * 2;
|
||||
}
|
||||
return a - M_PI;
|
||||
}
|
||||
|
||||
float vec3_angle(vec3_t a, vec3_t b);
|
||||
vec3_t vec3_wrap_angle(vec3_t a);
|
||||
vec3_t vec3_normalize(vec3_t a);
|
||||
vec3_t vec3_project_to_ray(vec3_t p, vec3_t r0, vec3_t r1);
|
||||
float vec3_distance_to_plane(vec3_t p, vec3_t plane_pos, vec3_t plane_normal);
|
||||
vec3_t vec3_reflect(vec3_t incidence, vec3_t normal, float f);
|
||||
|
||||
float wrap_angle(float a);
|
||||
|
||||
vec3_t vec3_transform(vec3_t a, mat4_t *mat);
|
||||
void mat4_set_translation(mat4_t *mat, vec3_t pos);
|
||||
void mat4_set_yaw_pitch_roll(mat4_t *m, vec3_t rot);
|
||||
void mat4_set_roll_pitch_yaw(mat4_t *mat, vec3_t rot);
|
||||
void mat4_translate(mat4_t *mat, vec3_t translation);
|
||||
void mat4_mul(mat4_t *res, mat4_t *a, mat4_t *b);
|
||||
|
||||
#endif
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include "utils.h"
|
||||
#include "mem.h"
|
||||
|
||||
char temp_path[64];
|
||||
char *get_path(const char *dir, const char *file) {
|
||||
strcpy(temp_path, dir);
|
||||
strcpy(temp_path + strlen(dir), file);
|
||||
return temp_path;
|
||||
}
|
||||
|
||||
|
||||
bool file_exists(char *path) {
|
||||
struct stat s;
|
||||
return (stat(path, &s) == 0);
|
||||
}
|
||||
|
||||
uint8_t *file_load(char *path, uint32_t *bytes_read) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
error_if(!f, "Could not open file for reading: %s", path);
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
int32_t size = ftell(f);
|
||||
if (size <= 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
uint8_t *bytes = mem_temp_alloc(size);
|
||||
if (!bytes) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*bytes_read = fread(bytes, 1, size, f);
|
||||
fclose(f);
|
||||
|
||||
error_if(*bytes_read != size, "Could not read file: %s", path);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
uint32_t file_store(char *path, void *bytes, int32_t len) {
|
||||
FILE *f = fopen(path, "wb");
|
||||
error_if(!f, "Could not open file for writing: %s", path);
|
||||
|
||||
if (fwrite(bytes, 1, len, f) != len) {
|
||||
die("Could not write file file %s", path);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return len;
|
||||
}
|
||||
|
||||
bool str_starts_with(const char *haystack, const char *needle) {
|
||||
return (strncmp(haystack, needle, strlen(needle)) == 0);
|
||||
}
|
||||
|
||||
float rand_float(float min, float max) {
|
||||
return min + ((float)rand() / (float)RAND_MAX) * (max - min);
|
||||
}
|
||||
|
||||
int32_t rand_int(int32_t min, int32_t max) {
|
||||
return min + rand() % (max - min);
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <string.h>
|
||||
#include "types.h"
|
||||
|
||||
|
||||
#if !defined(offsetof)
|
||||
#define offsetof(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT))
|
||||
#endif
|
||||
#define member_size(type, member) sizeof(((type *)0)->member)
|
||||
|
||||
#define max(a,b) ({ \
|
||||
__typeof__ (a) _a = (a); \
|
||||
__typeof__ (b) _b = (b); \
|
||||
_a > _b ? _a : _b; \
|
||||
})
|
||||
|
||||
#define min(a,b) ({ \
|
||||
__typeof__ (a) _a = (a); \
|
||||
__typeof__ (b) _b = (b); \
|
||||
_a < _b ? _a : _b; \
|
||||
})
|
||||
|
||||
#define swap(a, b) ({ \
|
||||
__typeof__(a) tmp = a; a = b; b = tmp; \
|
||||
})
|
||||
|
||||
#define clamp(v, min, max) ({ \
|
||||
__typeof__(v) _v = v, _min = min, _max = max; \
|
||||
_v > _max ? _max : _v < _min ? _min : _v; \
|
||||
})
|
||||
#define scale(v, in_min, in_max, out_min, out_max) ({ \
|
||||
__typeof__(v) _in_min = in_min, _out_min = out_min; \
|
||||
_out_min + ((out_max) - _out_min) * (((v) - _in_min) / ((in_max) - _in_min)); \
|
||||
})
|
||||
#define lerp(a, b, t) ({ \
|
||||
__typeof__(a) _a = a; \
|
||||
_a + ((b) - _a) * (t); \
|
||||
})
|
||||
|
||||
#define len(A) (sizeof(A) / sizeof(A[0]))
|
||||
#define clear(A) memset(A, 0, sizeof(A))
|
||||
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
#define TOSTRING(x) STRINGIFY(x)
|
||||
#define die(...) \
|
||||
printf("Abort at " TOSTRING(__FILE__) " line " TOSTRING(__LINE__) ": " __VA_ARGS__); \
|
||||
printf("\n"); \
|
||||
exit(1)
|
||||
|
||||
#define error_if(TEST, ...) \
|
||||
if (TEST) { \
|
||||
die(__VA_ARGS__); \
|
||||
}
|
||||
|
||||
|
||||
#define flags_add(FLAGS, F) (FLAGS |= (F))
|
||||
#define flags_rm(FLAGS, F) (FLAGS &= ~(F))
|
||||
#define flags_is(FLAGS, F) ((FLAGS & (F)) == (F))
|
||||
#define flags_any(FLAGS, F) (FLAGS & (F))
|
||||
#define flags_not(FLAGS, F) ((FLAGS & (F)) != (F))
|
||||
#define flags_none(FLAGS, F) ((FLAGS & (F)) == 0)
|
||||
#define flags_set(FLAGS, F) (FLAGS = (F))
|
||||
|
||||
|
||||
|
||||
char *get_path(const char *dir, const char *file);
|
||||
bool str_starts_with(const char *haystack, const char *needle);
|
||||
float rand_float(float min, float max);
|
||||
int32_t rand_int(int32_t min, int32_t max);
|
||||
|
||||
bool file_exists(char *path);
|
||||
uint8_t *file_load(char *path, uint32_t *bytes_read);
|
||||
uint32_t file_store(char *path, void *bytes, int32_t len);
|
||||
|
||||
|
||||
#define sort(LIST, LEN, COMPARE_FUNC) \
|
||||
for (uint32_t sort_i = 1, sort_j; sort_i < (LEN); sort_i++) { \
|
||||
sort_j = sort_i; \
|
||||
__typeof__((LIST)[0]) sort_temp = (LIST)[sort_j]; \
|
||||
while (sort_j > 0 && COMPARE_FUNC(&(LIST)[sort_j-1], &sort_temp)) { \
|
||||
(LIST)[sort_j] = (LIST)[sort_j-1]; \
|
||||
sort_j--; \
|
||||
} \
|
||||
(LIST)[sort_j] = sort_temp; \
|
||||
}
|
||||
|
||||
#define shuffle(LIST, LEN) \
|
||||
for (int i = (LEN) - 1; i > 0; i--) { \
|
||||
int j = rand_int(0, i+1); \
|
||||
swap((LIST)[i], (LIST)[j]); \
|
||||
}
|
||||
|
||||
|
||||
static inline uint8_t get_u8(uint8_t *bytes, uint32_t *p) {
|
||||
return bytes[(*p)++];
|
||||
}
|
||||
|
||||
static inline uint16_t get_u16(uint8_t *bytes, uint32_t *p) {
|
||||
uint16_t v = 0;
|
||||
v |= bytes[(*p)++] << 8;
|
||||
v |= bytes[(*p)++] << 0;
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline uint32_t get_u32(uint8_t *bytes, uint32_t *p) {
|
||||
uint32_t v = 0;
|
||||
v |= bytes[(*p)++] << 24;
|
||||
v |= bytes[(*p)++] << 16;
|
||||
v |= bytes[(*p)++] << 8;
|
||||
v |= bytes[(*p)++] << 0;
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline uint16_t get_u16_le(uint8_t *bytes, uint32_t *p) {
|
||||
uint16_t v = 0;
|
||||
v |= bytes[(*p)++] << 0;
|
||||
v |= bytes[(*p)++] << 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
static inline uint32_t get_u32_le(uint8_t *bytes, uint32_t *p) {
|
||||
uint32_t v = 0;
|
||||
v |= bytes[(*p)++] << 0;
|
||||
v |= bytes[(*p)++] << 8;
|
||||
v |= bytes[(*p)++] << 16;
|
||||
v |= bytes[(*p)++] << 24;
|
||||
return v;
|
||||
}
|
||||
|
||||
#define get_i8(BYTES, P) ((int8_t)get_u8(BYTES, P))
|
||||
#define get_i16(BYTES, P) ((int16_t)get_u16(BYTES, P))
|
||||
#define get_i16_le(BYTES, P) ((int16_t)get_u16_le(BYTES, P))
|
||||
#define get_i32(BYTES, P) ((int32_t)get_u32(BYTES, P))
|
||||
#define get_i32_le(BYTES, P) ((int32_t)get_u32_le(BYTES, P))
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,238 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>wipEout</title>
|
||||
<style>
|
||||
html, body {
|
||||
color: #ccc;
|
||||
font-family: Monospace;
|
||||
font-size: 13px;
|
||||
background-color: #111;
|
||||
margin: 0px;
|
||||
}
|
||||
a {
|
||||
color: #ffc603;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
|
||||
canvas {
|
||||
border: 0px none;
|
||||
background-color: black;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
#head {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
padding: 4px 16px 6px 16px;
|
||||
xheight: 24px;
|
||||
background-color: #222;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@keyframes page-loader {
|
||||
0% {transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg);}
|
||||
}
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
.info-overlay {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
display: none;
|
||||
}
|
||||
.fullscreen {
|
||||
margin: 0 8px;
|
||||
}
|
||||
#spinner {
|
||||
content: "";
|
||||
border-radius: 50%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-top: 2px solid #222;
|
||||
border-right: 2px solid #222;
|
||||
border-bottom: 2px solid #222;
|
||||
border-left: 2px solid #ffc603;
|
||||
transform: translateZ(0);
|
||||
animation: page-loader 1.1s infinite linear;
|
||||
}
|
||||
h1 {
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
.key {
|
||||
margin-right: 16px;
|
||||
}
|
||||
kbd {
|
||||
background-color: #eee;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b4b4b4;
|
||||
box-shadow:
|
||||
0 1px 1px rgba(0, 0, 0, 0.2),
|
||||
0 2px 0 0 rgba(255, 255, 255, 0.7) inset;
|
||||
color: #333;
|
||||
display: inline-block;
|
||||
font-size: 0.85em;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
padding: 1px 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="head">
|
||||
<div>
|
||||
<h1>wipEout</h1>
|
||||
<a href="#" id="fullscreen">fullscreen</a>
|
||||
<span class="key"><kbd>◀</kbd><kbd>▲</kbd><kbd>▼</kbd><kbd>▶</kbd> steering</span>
|
||||
<span class="key"><kbd>X</kbd> thrust</span>
|
||||
<span class="key"><kbd>Z</kbd> shoot</span>
|
||||
<span class="key"><kbd>C</kbd> brake left</span>
|
||||
<span class="key"><kbd>V</kbd> brake right</span>
|
||||
<span class="key"><kbd>A</kbd> view</span>
|
||||
</div>
|
||||
<div>
|
||||
Read:
|
||||
<a href="https://phoboslab.org/log/2023/08/rewriting-wipeout">
|
||||
Rewriting wipEout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="game" class="container">
|
||||
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
|
||||
<div class="info-overlay" id="loading">
|
||||
<div id="spinner"></div>
|
||||
<div id="status">Downloading...</div>
|
||||
<div>
|
||||
<progress value="0" max="100" id="progress" hidden=1></progress>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-overlay" id="select-version">
|
||||
<p>
|
||||
<a href="#" id="load-full-version">FULL VERSION</a><br/>
|
||||
the complete game ~144mb
|
||||
</p>
|
||||
<p>
|
||||
<a href="#" id="load-minimal-version">MINIMAL VERSION</a><br/>
|
||||
no intro, no music ~11mb
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script type='text/javascript'>
|
||||
var loadScript = (ev, src) => {
|
||||
ev.preventDefault();
|
||||
|
||||
// Hide select-version, show loader
|
||||
document.getElementById('select-version').style.display = 'none';
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
|
||||
// Load the requested script
|
||||
var s = document.createElement('script');
|
||||
s.setAttribute('src', src);
|
||||
document.head.appendChild(s);
|
||||
|
||||
// Attemp to unlock Audio :(
|
||||
var audioCtx = new AudioContext();
|
||||
audioCtx.resume();
|
||||
return false;
|
||||
};
|
||||
var requestFullscreen = (ev) => {
|
||||
ev.preventDefault();
|
||||
document.getElementById('game').requestFullscreen();
|
||||
};
|
||||
document.getElementById('select-version').style.display = 'block';
|
||||
document.getElementById('fullscreen').addEventListener('click', (ev) => requestFullscreen(ev, 'wipeout.js'))
|
||||
document.getElementById('load-full-version').addEventListener('click', (ev) => loadScript(ev, 'wipeout.js'));
|
||||
document.getElementById('load-minimal-version').addEventListener('click', (ev) => loadScript(ev, 'wipeout-minimal.js'));
|
||||
|
||||
var statusElement = document.getElementById('status');
|
||||
var progressElement = document.getElementById('progress');
|
||||
var spinnerElement = document.getElementById('spinner');
|
||||
|
||||
var Module = {
|
||||
preRun: [],
|
||||
postRun: [],
|
||||
print: function(text) {
|
||||
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
|
||||
console.log(text);
|
||||
},
|
||||
canvas: document.getElementById('canvas'),
|
||||
setStatus: (text) => {
|
||||
if (!Module.setStatus.last) {
|
||||
Module.setStatus.last = { time: Date.now(), text: '' };
|
||||
}
|
||||
if (text === Module.setStatus.last.text) {
|
||||
return;
|
||||
}
|
||||
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
|
||||
var now = Date.now();
|
||||
if (m && now - Module.setStatus.last.time < 30) {
|
||||
return; // if this is a progress update, skip it if too soon
|
||||
}
|
||||
Module.setStatus.last.time = now;
|
||||
Module.setStatus.last.text = text;
|
||||
if (m) {
|
||||
text = m[1];
|
||||
progressElement.value = parseInt(m[2])*100;
|
||||
progressElement.max = parseInt(m[4])*100;
|
||||
progressElement.hidden = false;
|
||||
spinnerElement.hidden = false;
|
||||
} else {
|
||||
progressElement.value = null;
|
||||
progressElement.max = null;
|
||||
progressElement.hidden = true;
|
||||
if (!text) {
|
||||
spinnerElement.hidden = true;
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}
|
||||
}
|
||||
statusElement.innerHTML = text;
|
||||
},
|
||||
totalDependencies: 0,
|
||||
monitorRunDependencies: (left) => {
|
||||
Module.totalDependencies = Math.max(Module.totalDependencies, left);
|
||||
Module.setStatus(left ? 'preparing... (' + (Module.totalDependencies-left) + '/' + Module.totalDependencies + ')' : 'all downloads complete.');
|
||||
}
|
||||
};
|
||||
Module.setStatus('downloading...');
|
||||
window.onerror = () => {
|
||||
Module.setStatus('Exception thrown, see JavaScript console');
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
spinnerElement.style.display = 'none';
|
||||
Module.setStatus = (text) => {
|
||||
if (text) {
|
||||
console.error('[post-exception status] ' + text);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#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->mat = mat4_identity();
|
||||
camera->has_initial_section = false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Executable
+32
@@ -0,0 +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;
|
||||
mat4_t mat;
|
||||
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);
|
||||
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
|
||||
Executable
+260
@@ -0,0 +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() {
|
||||
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->colour[0].as_rgba.r = r;
|
||||
prm.gt3->colour[0].as_rgba.g = g;
|
||||
prm.gt3->colour[0].as_rgba.b = b;
|
||||
|
||||
prm.gt3->colour[1].as_rgba.r = r;
|
||||
prm.gt3->colour[1].as_rgba.g = g;
|
||||
prm.gt3->colour[1].as_rgba.b = b;
|
||||
|
||||
prm.gt3->colour[2].as_rgba.r = r;
|
||||
prm.gt3->colour[2].as_rgba.g = g;
|
||||
prm.gt3->colour[2].as_rgba.b = b;
|
||||
prm.gt3++;
|
||||
break;
|
||||
|
||||
case PRM_TYPE_GT4:
|
||||
prm.gt4->colour[0].as_rgba.r = r;
|
||||
prm.gt4->colour[0].as_rgba.g = g;
|
||||
prm.gt4->colour[0].as_rgba.b = b;
|
||||
|
||||
prm.gt4->colour[1].as_rgba.r = r;
|
||||
prm.gt4->colour[1].as_rgba.g = g;
|
||||
prm.gt4->colour[1].as_rgba.b = b;
|
||||
|
||||
prm.gt4->colour[2].as_rgba.r = r;
|
||||
prm.gt4->colour[2].as_rgba.g = g;
|
||||
prm.gt4->colour[2].as_rgba.b = b;
|
||||
|
||||
prm.gt4->colour[3].as_rgba.r = 40;
|
||||
prm.gt4->colour[3].as_rgba.g = 40;
|
||||
prm.gt4->colour[3].as_rgba.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+39
@@ -0,0 +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 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
|
||||
Executable
+637
@@ -0,0 +1,637 @@
|
||||
#include <string.h>
|
||||
|
||||
#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 (*update)();
|
||||
} 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() {
|
||||
if (file_exists("save.dat")) {
|
||||
uint32_t size;
|
||||
save_t *save_file = (save_t *)file_load("save.dat", &size);
|
||||
if (size == sizeof(save_t) && save_file->magic == SAVE_DATA_MAGIC) {
|
||||
printf("load save data success\n");
|
||||
memcpy(&save, save_file, sizeof(save_t));
|
||||
}
|
||||
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() {
|
||||
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() {
|
||||
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();
|
||||
}
|
||||
|
||||
if (save.is_dirty) {
|
||||
// FIXME: use a text based format?
|
||||
// FIXME: this should probably run async somewhere
|
||||
save.is_dirty = false;
|
||||
file_store("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;
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+270
@@ -0,0 +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 game_set_scene(game_scene_t scene);
|
||||
void game_reset_championship();
|
||||
void game_update();
|
||||
|
||||
#endif
|
||||
Executable
+244
@@ -0,0 +1,244 @@
|
||||
#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() {
|
||||
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.as_uint32 > 0) {
|
||||
left_color = color_override;
|
||||
right_color = color_override;
|
||||
}
|
||||
else {
|
||||
left_color = a->color;
|
||||
right_color = rgba(
|
||||
lerp(a->color.as_rgba.r, b->color.as_rgba.r, f),
|
||||
lerp(a->color.as_rgba.g, b->color.as_rgba.g, f),
|
||||
lerp(a->color.as_rgba.b, b->color.as_rgba.b, f),
|
||||
lerp(a->color.as_rgba.a, b->color.as_rgba.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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#ifndef HUD_H
|
||||
#define HUD_H
|
||||
|
||||
#include "ship.h"
|
||||
|
||||
void hud_load();
|
||||
void hud_draw(ship_t *ship);
|
||||
|
||||
#endif
|
||||
Executable
+320
@@ -0,0 +1,320 @@
|
||||
#include "../types.h"
|
||||
#include "../mem.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 "image.h"
|
||||
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "../libs/stb_image_write.h"
|
||||
|
||||
|
||||
#define TIM_TYPE_PALETTED_4_BPP 0x08
|
||||
#define TIM_TYPE_PALETTED_8_BPP 0x09
|
||||
#define TIM_TYPE_TRUE_COLOR_16_BPP 0x02
|
||||
|
||||
static inline rgba_t tim_16bit_to_rgba(uint16_t c, bool transparent_bit) {
|
||||
return rgba(
|
||||
((c >> 0) & 0x1f) << 3,
|
||||
((c >> 5) & 0x1f) << 3,
|
||||
((c >> 10) & 0x1f) << 3,
|
||||
(c == 0
|
||||
? 0x00
|
||||
: transparent_bit && (c & 0x7fff) == 0 ? 0x00 : 0xff
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
image_t *image_alloc(uint32_t width, uint32_t height) {
|
||||
image_t *image = mem_temp_alloc(sizeof(image_t) + width * height * sizeof(rgba_t));
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
image->pixels = (rgba_t *)(((uint8_t *)image) + sizeof(image_t));
|
||||
return image;
|
||||
}
|
||||
|
||||
image_t *image_load_from_bytes(uint8_t *bytes, bool transparent) {
|
||||
uint32_t p = 0;
|
||||
|
||||
uint32_t magic = get_i32_le(bytes, &p);
|
||||
uint32_t type = get_i32_le(bytes, &p);
|
||||
uint16_t *palette = NULL;
|
||||
|
||||
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);
|
||||
palette = (uint16_t *)(bytes + p);
|
||||
p += palette_colors * 2;
|
||||
}
|
||||
|
||||
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_i16_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++] = tim_16bit_to_rgba(palette[(palette_pos >> 0) & 0xff], transparent);
|
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(palette[(palette_pos >> 8) & 0xff], transparent);
|
||||
}
|
||||
}
|
||||
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++] = tim_16bit_to_rgba(palette[(palette_pos >> 0) & 0xf], transparent);
|
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(palette[(palette_pos >> 4) & 0xf], transparent);
|
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(palette[(palette_pos >> 8) & 0xf], transparent);
|
||||
image->pixels[pixel_pos++] = tim_16bit_to_rgba(palette[(palette_pos >> 12) & 0xf], transparent);
|
||||
}
|
||||
}
|
||||
|
||||
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 = file_load(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 = file_load(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 = file_load(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;
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+34
@@ -0,0 +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);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,486 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "../input.h"
|
||||
#include "../system.h"
|
||||
#include "../utils.h"
|
||||
#include "../mem.h"
|
||||
|
||||
#include "menu.h"
|
||||
#include "ingame_menus.h"
|
||||
#include "game.h"
|
||||
#include "image.h"
|
||||
#include "ui.h"
|
||||
#include "race.h"
|
||||
|
||||
static void page_race_points_init(menu_t * menu);
|
||||
static void page_championship_points_init(menu_t * menu);
|
||||
static void page_hall_of_fame_init(menu_t * menu);
|
||||
|
||||
static texture_list_t pilot_portraits;
|
||||
static menu_t *ingame_menu;
|
||||
|
||||
void ingame_menus_load() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef PAUSE_MENU_H
|
||||
#define PAUSE_MENU_H
|
||||
|
||||
#include "menu.h"
|
||||
|
||||
void ingame_menus_load();
|
||||
|
||||
menu_t *pause_menu_init();
|
||||
menu_t *game_over_menu_init();
|
||||
menu_t *race_stats_menu_init();
|
||||
menu_t *text_scroll_menu_init(char * const *lines, int len);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "../system.h"
|
||||
#include "../input.h"
|
||||
#include "../utils.h"
|
||||
#include "../types.h"
|
||||
#include "../mem.h"
|
||||
|
||||
#include "intro.h"
|
||||
#include "ui.h"
|
||||
#include "image.h"
|
||||
#include "game.h"
|
||||
|
||||
void free_dummmy(void *p) {}
|
||||
void *realloc_dummmy(void *p, size_t sz) {
|
||||
die("pl_mpeg needed to realloc. Not implemented. Maybe increase PLM_BUFFER_DEFAULT_SIZE");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define PL_MPEG_IMPLEMENTATION
|
||||
#define PLM_MALLOC mem_bump
|
||||
#define PLM_FREE free_dummmy
|
||||
#define PLM_REALLOC realloc_dummmy
|
||||
#include "../libs/pl_mpeg.h"
|
||||
|
||||
#define INTRO_AUDIO_BUFFER_LEN (64 * 1024)
|
||||
|
||||
static plm_t *plm;
|
||||
static rgba_t *frame_buffer;
|
||||
static int16_t texture;
|
||||
static float *audio_buffer;
|
||||
static int audio_buffer_read_pos;
|
||||
static int audio_buffer_write_pos;
|
||||
|
||||
static void video_cb(plm_t *plm, plm_frame_t *frame, void *user);
|
||||
static void audio_cb(plm_t *plm, plm_samples_t *samples, void *user);
|
||||
static void audio_mix(float *samples, uint32_t len);
|
||||
static void intro_end();
|
||||
|
||||
void intro_init() {
|
||||
plm = plm_create_with_filename("wipeout/intro.mpeg");
|
||||
if (!plm) {
|
||||
intro_end();
|
||||
return;
|
||||
}
|
||||
plm_set_video_decode_callback(plm, video_cb, NULL);
|
||||
plm_set_audio_decode_callback(plm, audio_cb, NULL);
|
||||
|
||||
plm_set_loop(plm, false);
|
||||
plm_set_audio_enabled(plm, true);
|
||||
plm_set_audio_stream(plm, 0);
|
||||
|
||||
int w = plm_get_width(plm);
|
||||
int h = plm_get_height(plm);
|
||||
frame_buffer = mem_bump(w * h * sizeof(rgba_t));
|
||||
for (int i = 0; i < w * h; i++) {
|
||||
frame_buffer[i] = rgba(0, 0, 0, 255);
|
||||
}
|
||||
texture = render_texture_create(w, h, frame_buffer);
|
||||
|
||||
sfx_set_external_mix_cb(audio_mix);
|
||||
audio_buffer = mem_bump(INTRO_AUDIO_BUFFER_LEN * sizeof(float) * 2);
|
||||
audio_buffer_read_pos = 0;
|
||||
audio_buffer_write_pos = 0;
|
||||
}
|
||||
|
||||
static void intro_end() {
|
||||
sfx_set_external_mix_cb(NULL);
|
||||
game_set_scene(GAME_SCENE_TITLE);
|
||||
}
|
||||
|
||||
void intro_update() {
|
||||
if (!plm) {
|
||||
return;
|
||||
}
|
||||
plm_decode(plm, system_tick());
|
||||
render_set_view_2d();
|
||||
render_push_2d(vec2i(0,0), render_size(), rgba(128, 128, 128, 255), texture);
|
||||
if (plm_has_ended(plm) || input_pressed(A_MENU_SELECT) || input_pressed(A_MENU_START)) {
|
||||
intro_end();
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_cb(plm_t *plm, plm_samples_t *samples, void *user) {
|
||||
int len = samples->count * 2;
|
||||
for (int i = 0; i < len; i++) {
|
||||
audio_buffer[audio_buffer_write_pos % INTRO_AUDIO_BUFFER_LEN] = samples->interleaved[i];
|
||||
audio_buffer_write_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_mix(float *samples, uint32_t len) {
|
||||
int i;
|
||||
for (i = 0; i < len && audio_buffer_read_pos < audio_buffer_write_pos; i++) {
|
||||
samples[i] = audio_buffer[audio_buffer_read_pos % INTRO_AUDIO_BUFFER_LEN];
|
||||
audio_buffer_read_pos++;
|
||||
}
|
||||
for (; i < len; i++) {
|
||||
samples[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void video_cb(plm_t *plm, plm_frame_t *frame, void *user) {
|
||||
plm_frame_to_rgba(frame, (uint8_t *)frame_buffer, plm_get_width(plm) * sizeof(rgba_t));
|
||||
render_texture_replace_pixels(texture, frame_buffer);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef INTRO_H
|
||||
#define INTRO_H
|
||||
|
||||
void intro_init();
|
||||
void intro_update();
|
||||
void intro_cleanup();
|
||||
|
||||
#endif
|
||||
Executable
+541
@@ -0,0 +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() {
|
||||
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() {
|
||||
render_set_view_2d();
|
||||
render_push_2d(vec2i(0, 0), render_size(), rgba(128, 128, 128, 255), background);
|
||||
|
||||
menu_update(main_menu);
|
||||
}
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#ifndef MAIN_MENU_H
|
||||
#define MAIN_MENU_H
|
||||
|
||||
void main_menu_init();
|
||||
void main_menu_update();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +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() {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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
|
||||
Executable
+778
@@ -0,0 +1,778 @@
|
||||
#include "../types.h"
|
||||
#include "../mem.h"
|
||||
#include "../render.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 "hud.h"
|
||||
#include "object.h"
|
||||
|
||||
|
||||
static rgba_t int32_to_rgba(uint32_t v) {
|
||||
return rgba(
|
||||
((v >> 24) & 0xff),
|
||||
((v >> 16) & 0xff),
|
||||
((v >> 8) & 0xff),
|
||||
255
|
||||
);
|
||||
}
|
||||
|
||||
Object *objects_load(char *name, texture_list_t tl) {
|
||||
uint32_t length = 0;
|
||||
uint8_t *bytes = file_load(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->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
|
||||
}
|
||||
|
||||
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->colour = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour[0] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.g3->colour[1] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.g3->colour[2] = int32_to_rgba(get_i32(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->colour[0] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.g4->colour[1] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.g4->colour[2] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.g4->colour[3] = int32_to_rgba(get_i32(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->colour[0] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.gt3->colour[1] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.gt3->colour[2] = int32_to_rgba(get_i32(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->colour[0] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.gt4->colour[1] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.gt4->colour[2] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.gt4->colour[3] = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour[0] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.lsg3->colour[1] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.lsg3->colour[2] = int32_to_rgba(get_i32(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->colour[0] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.lsg4->colour[1] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.lsg4->colour[2] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.lsg4->colour[3] = int32_to_rgba(get_i32(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->colour[0] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.lsgt3->colour[1] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.lsgt3->colour[2] = int32_to_rgba(get_i32(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->colour[0] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.lsgt4->colour[1] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.lsgt4->colour[2] = int32_to_rgba(get_i32(bytes, &p));
|
||||
prm.lsgt4->colour[3] = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour = int32_to_rgba(get_i32(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->colour[2]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.uv = {poly.gt3->u1, poly.gt3->v1},
|
||||
.color = poly.gt3->colour[1]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord0],
|
||||
.uv = {poly.gt3->u0, poly.gt3->v0},
|
||||
.color = poly.gt3->colour[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->colour[2]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.uv = {poly.gt4->u1, poly.gt4->v1},
|
||||
.color = poly.gt4->colour[1]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord0],
|
||||
.uv = {poly.gt4->u0, poly.gt4->v0},
|
||||
.color = poly.gt4->colour[0]
|
||||
},
|
||||
}
|
||||
}, poly.gt4->texture);
|
||||
render_push_tris((tris_t) {
|
||||
.vertices = {
|
||||
{
|
||||
.pos = vertex[coord2],
|
||||
.uv = {poly.gt4->u2, poly.gt4->v2},
|
||||
.color = poly.gt4->colour[2]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord3],
|
||||
.uv = {poly.gt4->u3, poly.gt4->v3},
|
||||
.color = poly.gt4->colour[3]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.uv = {poly.gt4->u1, poly.gt4->v1},
|
||||
.color = poly.gt4->colour[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->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.uv = {poly.ft3->u1, poly.ft3->v1},
|
||||
.color = poly.ft3->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord0],
|
||||
.uv = {poly.ft3->u0, poly.ft3->v0},
|
||||
.color = poly.ft3->colour
|
||||
},
|
||||
}
|
||||
}, 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->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.uv = {poly.ft4->u1, poly.ft4->v1},
|
||||
.color = poly.ft4->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord0],
|
||||
.uv = {poly.ft4->u0, poly.ft4->v0},
|
||||
.color = poly.ft4->colour
|
||||
},
|
||||
}
|
||||
}, poly.ft4->texture);
|
||||
render_push_tris((tris_t) {
|
||||
.vertices = {
|
||||
{
|
||||
.pos = vertex[coord2],
|
||||
.uv = {poly.ft4->u2, poly.ft4->v2},
|
||||
.color = poly.ft4->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord3],
|
||||
.uv = {poly.ft4->u3, poly.ft4->v3},
|
||||
.color = poly.ft4->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.uv = {poly.ft4->u1, poly.ft4->v1},
|
||||
.color = poly.ft4->colour
|
||||
},
|
||||
}
|
||||
}, 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->colour[2]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.color = poly.g3->colour[1]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord0],
|
||||
.color = poly.g3->colour[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->colour[2]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.color = poly.g4->colour[1]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord0],
|
||||
.color = poly.g4->colour[0]
|
||||
},
|
||||
}
|
||||
}, RENDER_NO_TEXTURE);
|
||||
render_push_tris((tris_t) {
|
||||
.vertices = {
|
||||
{
|
||||
.pos = vertex[coord2],
|
||||
.color = poly.g4->colour[2]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord3],
|
||||
.color = poly.g4->colour[3]
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.color = poly.g4->colour[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->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.color = poly.f3->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord0],
|
||||
.color = poly.f3->colour
|
||||
},
|
||||
}
|
||||
}, 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->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.color = poly.f4->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord0],
|
||||
.color = poly.f4->colour
|
||||
},
|
||||
}
|
||||
}, RENDER_NO_TEXTURE);
|
||||
render_push_tris((tris_t) {
|
||||
.vertices = {
|
||||
{
|
||||
.pos = vertex[coord2],
|
||||
.color = poly.f4->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord3],
|
||||
.color = poly.f4->colour
|
||||
},
|
||||
{
|
||||
.pos = vertex[coord1],
|
||||
.color = poly.f4->colour
|
||||
},
|
||||
}
|
||||
}, 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->colour,
|
||||
poly.spr->texture
|
||||
);
|
||||
|
||||
poly.spr += 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+383
@@ -0,0 +1,383 @@
|
||||
#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 colour;
|
||||
} 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 colour;
|
||||
} FT3;
|
||||
|
||||
typedef struct F4 {
|
||||
int16_t type; // Type of primitive
|
||||
int16_t flag;
|
||||
int16_t coords[4]; // Indices of the coords
|
||||
rgba_t colour;
|
||||
} 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 colour;
|
||||
} 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 colour[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 colour[3];
|
||||
} GT3;
|
||||
|
||||
typedef struct G4 {
|
||||
int16_t type; // Type of primitive
|
||||
int16_t flag;
|
||||
int16_t coords[4]; // Indices of the coords
|
||||
rgba_t colour[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 colour[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 colour;
|
||||
} 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 colour;
|
||||
} 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 colour;
|
||||
} 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 colour;
|
||||
} 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 colour[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 colour[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 colour[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 colour[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 colour;
|
||||
} SPR;
|
||||
|
||||
|
||||
typedef struct Spline {
|
||||
int16_t type; // Type of primitive
|
||||
int16_t flag;
|
||||
vec3_t control1;
|
||||
vec3_t position;
|
||||
vec3_t control2;
|
||||
rgba_t colour;
|
||||
} Spline;
|
||||
|
||||
|
||||
typedef struct PointLight {
|
||||
int16_t type;
|
||||
int16_t flag;
|
||||
vec3_t position;
|
||||
rgba_t colour;
|
||||
int16_t startFalloff;
|
||||
int16_t endFalloff;
|
||||
} PointLight;
|
||||
|
||||
|
||||
typedef struct SpotLight {
|
||||
int16_t type;
|
||||
int16_t flag;
|
||||
vec3_t position;
|
||||
vec3_t direction;
|
||||
rgba_t colour;
|
||||
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 colour;
|
||||
} 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
|
||||
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
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "../mem.h"
|
||||
#include "../utils.h"
|
||||
#include "../system.h"
|
||||
#include "../render.h"
|
||||
|
||||
#include "particle.h"
|
||||
#include "image.h"
|
||||
|
||||
static particle_t *particles;
|
||||
static int particles_active = 0;
|
||||
static texture_list_t particle_textures;
|
||||
|
||||
void particles_load() {
|
||||
particles = mem_bump(sizeof(particle_t) * PARTICLES_MAX);
|
||||
particle_textures = image_get_compressed_textures("wipeout/common/effects.cmp");
|
||||
particles_init();
|
||||
}
|
||||
|
||||
void particles_init() {
|
||||
particles_active = 0;
|
||||
}
|
||||
|
||||
void particles_update() {
|
||||
for (int i = 0; i < particles_active; i++) {
|
||||
particle_t *p = &particles[i];
|
||||
|
||||
p->timer -= system_tick();
|
||||
p->position = vec3_add(p->position, vec3_mulf(p->velocity, system_tick()));
|
||||
if (p->timer < 0) {
|
||||
particles[i--] = particles[--particles_active];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void particles_draw() {
|
||||
if (particles_active == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
render_set_model_mat(&mat4_identity());
|
||||
render_set_depth_write(false);
|
||||
render_set_blend_mode(RENDER_BLEND_LIGHTER);
|
||||
render_set_depth_offset(-32.0);
|
||||
|
||||
for (int i = 0; i < particles_active; i++) {
|
||||
particle_t *p = &particles[i];
|
||||
render_push_sprite(p->position, p->size, p->color, p->texture);
|
||||
}
|
||||
|
||||
render_set_depth_offset(0.0);
|
||||
render_set_depth_write(true);
|
||||
render_set_blend_mode(RENDER_BLEND_NORMAL);
|
||||
}
|
||||
|
||||
void particles_spawn(vec3_t position, uint16_t type, vec3_t velocity, int size) {
|
||||
if (particles_active == PARTICLES_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
particle_t *p = &particles[particles_active++];
|
||||
p->color = rgba(128, 128, 128, 128);
|
||||
p->texture = texture_from_list(particle_textures, type);
|
||||
p->position = position;
|
||||
p->velocity = velocity;
|
||||
p->timer = rand_float(0.75, 1.0);
|
||||
p->size.x = size;
|
||||
p->size.y = size;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef PARTICLE_H
|
||||
#define PARTICLE_H
|
||||
|
||||
#include "../types.h"
|
||||
|
||||
#define PARTICLES_MAX 1024
|
||||
|
||||
#define PARTICLE_TYPE_NONE -1
|
||||
#define PARTICLE_TYPE_FIRE 0
|
||||
#define PARTICLE_TYPE_FIRE_WHITE 1
|
||||
#define PARTICLE_TYPE_SMOKE 2
|
||||
#define PARTICLE_TYPE_EBOLT 3
|
||||
#define PARTICLE_TYPE_HALO 4
|
||||
#define PARTICLE_TYPE_GREENY 5
|
||||
|
||||
typedef struct particle_t {
|
||||
vec3_t position;
|
||||
vec3_t velocity;
|
||||
vec2i_t size;
|
||||
rgba_t color;
|
||||
float timer;
|
||||
uint16_t type;
|
||||
uint16_t texture;
|
||||
} particle_t;
|
||||
|
||||
void particles_load();
|
||||
void particles_init();
|
||||
void particles_spawn(vec3_t position, uint16_t type, vec3_t velocity, int size);
|
||||
void particles_draw();
|
||||
void particles_update();
|
||||
|
||||
#endif
|
||||
Executable
+279
@@ -0,0 +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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
sfx_pause();
|
||||
is_paused = true;
|
||||
}
|
||||
|
||||
void race_unpause() {
|
||||
sfx_unpause();
|
||||
is_paused = false;
|
||||
active_menu = NULL;
|
||||
}
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#ifndef RACE_H
|
||||
#define RACE_H
|
||||
|
||||
void race_init();
|
||||
void race_update();
|
||||
void race_start();
|
||||
void race_restart();
|
||||
void race_pause();
|
||||
void race_unpause();
|
||||
void race_end();
|
||||
void race_next();
|
||||
void race_release_control();
|
||||
|
||||
#endif
|
||||
Executable
+261
@@ -0,0 +1,261 @@
|
||||
#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 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() {
|
||||
scene_set_start_booms(0);
|
||||
for (int i = 0; i < stands_len; i++) {
|
||||
stands[i].sfx = sfx_reserve_loop(SFX_CROWD);
|
||||
}
|
||||
}
|
||||
|
||||
void scene_update() {
|
||||
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);
|
||||
|
||||
// Nearby objects
|
||||
vec3_t cam_pos = camera->position;
|
||||
Object *object = scene_objects;
|
||||
float max_dist_sq = RENDER_FADEOUT_FAR * RENDER_FADEOUT_FAR;
|
||||
while (object) {
|
||||
vec3_t d = vec3_sub(cam_pos, object->origin);
|
||||
float dist_sq = d.x * d.x + d.y * d.y + d.z * d.z;
|
||||
|
||||
if (dist_sq < max_dist_sq) {
|
||||
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->colour[v].as_rgba.r = color.as_rgba.r;
|
||||
libPoly.gt4->colour[v].as_rgba.g = color.as_rgba.g;
|
||||
libPoly.gt4->colour[v].as_rgba.b = color.as_rgba.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->colour[v].as_rgba.r = r;
|
||||
libPoly.gt4->colour[v].as_rgba.g = 0x00;
|
||||
libPoly.gt4->colour[v].as_rgba.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() {
|
||||
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() {
|
||||
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]->colour[0].as_rgba.r = (sin(coords[0] * phase) * 64.0) + 190;
|
||||
aurora_borealis.primitives[i]->colour[0].as_rgba.g = (sin(coords[0] * (phase + 0.054)) * 64.0) + 190;
|
||||
aurora_borealis.primitives[i]->colour[0].as_rgba.b = (sin(coords[0] * (phase + 0.039)) * 64.0) + 190;
|
||||
}
|
||||
if (aurora_borealis.grey_coords[i] != -2) {
|
||||
aurora_borealis.primitives[i]->colour[1].as_rgba.r = (sin(coords[1] * phase) * 64.0) + 190;
|
||||
aurora_borealis.primitives[i]->colour[1].as_rgba.g = (sin(coords[1] * (phase + 0.054)) * 64.0) + 190;
|
||||
aurora_borealis.primitives[i]->colour[1].as_rgba.b = (sin(coords[1] * (phase + 0.039)) * 64.0) + 190;
|
||||
}
|
||||
if (aurora_borealis.grey_coords[i] != -1) {
|
||||
aurora_borealis.primitives[i]->colour[2].as_rgba.r = (sin(coords[2] * phase) * 64.0) + 190;
|
||||
aurora_borealis.primitives[i]->colour[2].as_rgba.g = (sin(coords[2] * (phase + 0.054)) * 64.0) + 190;
|
||||
aurora_borealis.primitives[i]->colour[2].as_rgba.b = (sin(coords[2] * (phase + 0.039)) * 64.0) + 190;
|
||||
}
|
||||
|
||||
if (aurora_borealis.grey_coords[i] != -1) {
|
||||
aurora_borealis.primitives[i]->colour[3].as_rgba.r = (sin(coords[3] * phase) * 64.0) + 190;
|
||||
aurora_borealis.primitives[i]->colour[3].as_rgba.g = (sin(coords[3] * (phase + 0.054)) * 64.0) + 190;
|
||||
aurora_borealis.primitives[i]->colour[3].as_rgba.b = (sin(coords[3] * (phase + 0.039)) * 64.0) + 190;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#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 scene_set_start_booms(int num_lights);
|
||||
void scene_init_aurora_borealis();
|
||||
void scene_update();
|
||||
void scene_draw();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,393 @@
|
||||
#include "../utils.h"
|
||||
#include "../mem.h"
|
||||
#include "../platform.h"
|
||||
|
||||
#include "sfx.h"
|
||||
#include "game.h"
|
||||
|
||||
#define QOA_IMPLEMENTATION
|
||||
#define QOA_NO_STDIO
|
||||
#include "../libs/qoa.h"
|
||||
|
||||
typedef struct {
|
||||
int16_t *samples;
|
||||
uint32_t len;
|
||||
} sfx_data_t;
|
||||
|
||||
typedef struct {
|
||||
qoa_desc qoa;
|
||||
FILE *file;
|
||||
|
||||
uint32_t track_index;
|
||||
uint32_t first_frame_pos;
|
||||
|
||||
uint32_t buffer_len;
|
||||
uint8_t *buffer;
|
||||
|
||||
uint32_t sample_data_pos;
|
||||
uint32_t sample_data_len;
|
||||
short *sample_data;
|
||||
sfx_music_mode_t mode;
|
||||
} music_decoder_t;
|
||||
|
||||
enum {
|
||||
VAG_REGION_START = 1,
|
||||
VAG_REGION = 2,
|
||||
VAG_REGION_END = 4
|
||||
};
|
||||
|
||||
static const int32_t vag_tab[5][2] = {
|
||||
{ 0, 0}, // { 0.0, 0.0}, << 14
|
||||
{15360, 0}, // { 60.0 / 64.0, 0.0}, << 14
|
||||
{29440, -13312}, // {115.0 / 64.0, -52.0 / 64.0}, << 14
|
||||
{25088, -14080}, // { 98.0 / 64.0, -55.0 / 64.0}, << 14
|
||||
{31232, -15360}, // {122.0 / 64.0, -60.0 / 64.0}, << 14
|
||||
};
|
||||
|
||||
static sfx_data_t *sources;
|
||||
static uint32_t num_sources;
|
||||
static sfx_t *nodes;
|
||||
static music_decoder_t *music;
|
||||
static void (*external_mix_cb)(float *, uint32_t len) = NULL;
|
||||
|
||||
void sfx_load() {
|
||||
// Init decode buffer for music
|
||||
uint32_t channels = 2;
|
||||
music = mem_bump(sizeof(music_decoder_t));
|
||||
music->buffer = mem_bump(QOA_FRAME_SIZE(channels, QOA_SLICES_PER_FRAME));
|
||||
music->sample_data = mem_bump(channels * QOA_FRAME_LEN * sizeof(short) * 2);
|
||||
music->qoa.channels = channels;
|
||||
music->mode = SFX_MUSIC_RANDOM;
|
||||
music->file = NULL;
|
||||
music->track_index = -1;
|
||||
|
||||
|
||||
// Load SFX samples
|
||||
nodes = mem_bump(SFX_MAX * sizeof(sfx_t));
|
||||
|
||||
// 16 byte blocks: 2 byte header, 14 bytes with 2x4bit samples each
|
||||
uint32_t vb_size;
|
||||
uint8_t *vb = file_load("wipeout/sound/wipeout.vb", &vb_size);
|
||||
uint32_t num_samples = (vb_size / 16) * 28;
|
||||
|
||||
int16_t *sample_buffer = mem_bump(num_samples * sizeof(int16_t));
|
||||
sources = mem_mark();
|
||||
num_sources = 0;
|
||||
|
||||
uint32_t sample_index = 0;
|
||||
int32_t history[2] = {0, 0};
|
||||
for (int p = 0; p < vb_size;) {
|
||||
uint8_t header = vb[p++];
|
||||
uint8_t flags = vb[p++];
|
||||
uint8_t shift = header & 0x0f;
|
||||
uint8_t predictor = clamp(header >> 4, 0, 4);
|
||||
|
||||
if (flags_is(flags, VAG_REGION_END)) {
|
||||
mem_bump(sizeof(sfx_data_t));
|
||||
sources[num_sources].samples = &sample_buffer[sample_index];
|
||||
}
|
||||
|
||||
for (uint32_t bs = 0; bs < 14; bs++) {
|
||||
int32_t nibbles[2] = {
|
||||
(vb[p] & 0x0f) << 12,
|
||||
(vb[p] & 0xf0) << 8
|
||||
};
|
||||
p++;
|
||||
|
||||
for (int ni = 0; ni < 2; ni++) {
|
||||
int32_t sample = nibbles[ni];
|
||||
if (sample & 0x8000) {
|
||||
sample |= 0xffff0000;
|
||||
}
|
||||
sample >>= shift;
|
||||
sample += (history[0] * vag_tab[predictor][0] + history[1] * vag_tab[predictor][1]) >> 14;
|
||||
history[1] = history[0];
|
||||
history[0] = sample;
|
||||
sample_buffer[sample_index++] = clamp(sample, -32768, 32767);
|
||||
}
|
||||
}
|
||||
|
||||
if (flags_is(flags, VAG_REGION_START)) {
|
||||
error_if(sources[num_sources].samples == NULL, "VAG_REGION_START without VAG_REGION_END");
|
||||
sources[num_sources].len = &sample_buffer[sample_index] - sources[num_sources].samples;
|
||||
num_sources++;
|
||||
}
|
||||
}
|
||||
|
||||
mem_temp_free(vb);
|
||||
platform_set_audio_mix_cb(sfx_stero_mix);
|
||||
}
|
||||
|
||||
void sfx_reset() {
|
||||
for (int i = 0; i < SFX_MAX; i++) {
|
||||
if (flags_is(nodes[i].flags, SFX_LOOP)) {
|
||||
flags_set(nodes[i].flags, SFX_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sfx_unpause() {
|
||||
for (int i = 0; i < SFX_MAX; i++) {
|
||||
if (flags_is(nodes[i].flags, SFX_LOOP_PAUSE)) {
|
||||
flags_rm(nodes[i].flags, SFX_LOOP_PAUSE);
|
||||
flags_add(nodes[i].flags, SFX_PLAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sfx_pause() {
|
||||
for (int i = 0; i < SFX_MAX; i++) {
|
||||
if (flags_is(nodes[i].flags, SFX_PLAY | SFX_LOOP)) {
|
||||
flags_rm(nodes[i].flags, SFX_PLAY);
|
||||
flags_add(nodes[i].flags, SFX_LOOP_PAUSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Sound effects
|
||||
|
||||
sfx_t *sfx_get_node(sfx_source_t source_index) {
|
||||
error_if(source_index < 0 || source_index > num_sources, "Invalid audio source");
|
||||
|
||||
sfx_t *sfx = NULL;
|
||||
for (int i = 0; i < SFX_MAX; i++) {
|
||||
if (flags_none(nodes[i].flags, SFX_PLAY | SFX_RESERVE)){
|
||||
sfx = &nodes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!sfx) {
|
||||
for (int i = 0; i < SFX_MAX; i++) {
|
||||
if (flags_not(nodes[i].flags, SFX_RESERVE)) {
|
||||
sfx = &nodes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error_if(!sfx, "All audio nodes reserved");
|
||||
|
||||
flags_set(sfx->flags, SFX_NONE);
|
||||
sfx->source = source_index;
|
||||
sfx->volume = 1;
|
||||
sfx->current_volume = 1;
|
||||
sfx->pan = 0;
|
||||
sfx->current_pan = 0;
|
||||
sfx->position = 0;
|
||||
|
||||
// Set default pitch. All voice samples are 44khz,
|
||||
// other effects 22khz
|
||||
sfx->pitch = source_index >= SFX_VOICE_MINES ? 1.0 : 0.5;
|
||||
|
||||
return sfx;
|
||||
}
|
||||
|
||||
sfx_t *sfx_play(sfx_source_t source_index) {
|
||||
sfx_t *sfx = sfx_get_node(source_index);
|
||||
flags_set(sfx->flags, SFX_PLAY);
|
||||
return sfx;
|
||||
}
|
||||
|
||||
sfx_t *sfx_play_at(sfx_source_t source_index, vec3_t pos, vec3_t vel, float volume) {
|
||||
sfx_t *sfx = sfx_get_node(source_index);
|
||||
sfx_set_position(sfx, pos, vel, volume);
|
||||
if (sfx->volume > 0) {
|
||||
flags_set(sfx->flags, SFX_PLAY);
|
||||
}
|
||||
return sfx;
|
||||
}
|
||||
|
||||
sfx_t *sfx_reserve_loop(sfx_source_t source_index) {
|
||||
sfx_t *sfx = sfx_get_node(source_index);
|
||||
flags_set(sfx->flags, SFX_RESERVE | SFX_LOOP | SFX_PLAY);
|
||||
sfx->volume = 0;
|
||||
sfx->current_volume = 0;
|
||||
sfx->current_pan = 0;
|
||||
sfx->position = rand_float(0, sources[source_index].len);
|
||||
return sfx;
|
||||
}
|
||||
|
||||
void sfx_set_position(sfx_t *sfx, vec3_t pos, vec3_t vel, float volume) {
|
||||
vec3_t relative_position = vec3_sub(g.camera.position, pos);
|
||||
vec3_t relative_velocity = vec3_sub(g.camera.real_velocity, vel);
|
||||
float distance = vec3_len(relative_position);
|
||||
|
||||
sfx->volume = clamp(scale(distance, 512, 32768, 1, 0), 0, 1) * volume;
|
||||
sfx->pan = -sin(atan2(g.camera.position.x - pos.x, g.camera.position.z - pos.z)+g.camera.angle.y);
|
||||
|
||||
// Doppler effect
|
||||
float away = vec3_dot(relative_velocity, relative_position) / distance;
|
||||
sfx->pitch = (262144.0 - away) / 524288.0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Music
|
||||
|
||||
uint32_t sfx_music_decode_frame() {
|
||||
if (!music->file) {
|
||||
return 0;
|
||||
}
|
||||
music->buffer_len = fread(music->buffer, 1, qoa_max_frame_size(&music->qoa), music->file);
|
||||
|
||||
uint32_t frame_len;
|
||||
qoa_decode_frame(music->buffer, music->buffer_len, &music->qoa, music->sample_data, &frame_len);
|
||||
music->sample_data_pos = 0;
|
||||
music->sample_data_len = frame_len;
|
||||
return frame_len;
|
||||
}
|
||||
|
||||
void sfx_music_rewind() {
|
||||
fseek(music->file, music->first_frame_pos, SEEK_SET);
|
||||
music->sample_data_len = 0;
|
||||
music->sample_data_pos = 0;
|
||||
}
|
||||
|
||||
void sfx_music_open(char *path) {
|
||||
if (music->file) {
|
||||
fclose(music->file);
|
||||
music->file = NULL;
|
||||
}
|
||||
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t header[QOA_MIN_FILESIZE];
|
||||
int read = fread(header, QOA_MIN_FILESIZE, 1, file);
|
||||
if (!read) {
|
||||
fclose(file);
|
||||
return;
|
||||
}
|
||||
|
||||
qoa_desc qoa;
|
||||
uint32_t first_frame_pos = qoa_decode_header(header, QOA_MIN_FILESIZE, &qoa);
|
||||
if (!first_frame_pos) {
|
||||
fclose(file);
|
||||
return;
|
||||
}
|
||||
|
||||
fseek(file, first_frame_pos, SEEK_SET);
|
||||
|
||||
if (qoa.channels != music->qoa.channels) {
|
||||
fclose(file);
|
||||
return;
|
||||
}
|
||||
music->qoa = qoa;
|
||||
music->first_frame_pos = first_frame_pos;
|
||||
music->file = file;
|
||||
music->sample_data_len = 0;
|
||||
music->sample_data_pos = 0;
|
||||
}
|
||||
|
||||
void sfx_music_play(uint32_t index) {
|
||||
error_if(index >= len(def.music), "Invalid music index");
|
||||
if (index == music->track_index) {
|
||||
sfx_music_rewind();
|
||||
return;
|
||||
}
|
||||
|
||||
printf("open music track %d\n", index);
|
||||
|
||||
music->track_index = index;
|
||||
sfx_music_open(def.music[index].path);
|
||||
}
|
||||
|
||||
void sfx_music_mode(sfx_music_mode_t mode) {
|
||||
music->mode = mode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Mixing
|
||||
|
||||
void sfx_set_external_mix_cb(void (*cb)(float *, uint32_t len)) {
|
||||
external_mix_cb = cb;
|
||||
}
|
||||
|
||||
void sfx_stero_mix(float *buffer, uint32_t len) {
|
||||
if (external_mix_cb) {
|
||||
external_mix_cb(buffer, len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find currently active nodes: those that play and have volume > 0
|
||||
sfx_t *active_nodes[SFX_MAX_ACTIVE];
|
||||
int active_nodes_len = 0;
|
||||
for (int n = 0; n < SFX_MAX; n++) {
|
||||
sfx_t *sfx = &nodes[n];
|
||||
if (flags_is(sfx->flags, SFX_PLAY) && (sfx->volume > 0 || sfx->current_volume > 0.01)) {
|
||||
active_nodes[active_nodes_len++] = sfx;
|
||||
if (active_nodes_len >= SFX_MAX_ACTIVE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t music_src_index = music->sample_data_pos * music->qoa.channels;
|
||||
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
float left = 0;
|
||||
float right = 0;
|
||||
|
||||
// Fill buffer with all active nodes
|
||||
for (int n = 0; n < active_nodes_len; n++) {
|
||||
sfx_t *sfx = active_nodes[n];
|
||||
if (flags_not(sfx->flags, SFX_PLAY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sfx->current_volume = sfx->current_volume * 0.999 + sfx->volume * 0.001;
|
||||
sfx->current_pan = sfx->current_pan * 0.999 + sfx->pan * 0.001;
|
||||
|
||||
sfx_data_t *source = &sources[sfx->source];
|
||||
float sample = (float)source->samples[(int)sfx->position] / 32768.0;
|
||||
left += sample * sfx->current_volume * clamp(1.0 - sfx->current_pan, 0, 1);
|
||||
right += sample * sfx->current_volume * clamp(1.0 + sfx->current_pan, 0, 1);
|
||||
|
||||
sfx->position += sfx->pitch;
|
||||
if (sfx->position >= source->len) {
|
||||
if (flags_is(sfx->flags, SFX_LOOP)) {
|
||||
sfx->position = fmod(sfx->position, source->len);
|
||||
}
|
||||
else {
|
||||
flags_rm(sfx->flags, SFX_PLAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
left *= save.sfx_volume;
|
||||
right *= save.sfx_volume;
|
||||
|
||||
// Mix in music
|
||||
if (music->mode != SFX_MUSIC_PAUSED && music->file) {
|
||||
if (music->sample_data_len - music->sample_data_pos == 0) {
|
||||
if (!sfx_music_decode_frame()) {
|
||||
if (music->mode == SFX_MUSIC_RANDOM) {
|
||||
sfx_music_play(rand_int(0, len(def.music)));
|
||||
}
|
||||
else if (music->mode == SFX_MUSIC_SEQUENTIAL) {
|
||||
sfx_music_play((music->track_index + 1) % len(def.music));
|
||||
}
|
||||
else if (music->mode == SFX_MUSIC_LOOP) {
|
||||
sfx_music_rewind();
|
||||
}
|
||||
sfx_music_decode_frame();
|
||||
}
|
||||
music_src_index = 0;
|
||||
}
|
||||
left += (music->sample_data[music_src_index++] / 32768.0) * save.music_volume;
|
||||
right += (music->sample_data[music_src_index++] / 32768.0) * save.music_volume;
|
||||
music->sample_data_pos++;
|
||||
}
|
||||
|
||||
buffer[i+0] = left;
|
||||
buffer[i+1] = right;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#ifndef SFX_H
|
||||
#define SFX_H
|
||||
|
||||
#include "../types.h"
|
||||
|
||||
typedef enum {
|
||||
SFX_CRUNCH,
|
||||
SFX_EBOLT,
|
||||
SFX_ENGINE_INTAKE,
|
||||
SFX_ENGINE_RUMBLE,
|
||||
SFX_ENGINE_THRUST,
|
||||
SFX_EXPLOSION_1,
|
||||
SFX_EXPLOSION_2,
|
||||
SFX_IMPACT,
|
||||
SFX_MENU_MOVE,
|
||||
SFX_MENU_SELECT,
|
||||
SFX_MENU_TRANSITION,
|
||||
SFX_MINE_DROP,
|
||||
SFX_MISSILE_FIRE,
|
||||
SFX_ENGINE_REMOTE,
|
||||
SFX_POWERUP,
|
||||
SFX_SHIELD,
|
||||
SFX_SIREN,
|
||||
SFX_TRACTOR,
|
||||
SFX_TURBULENCE,
|
||||
SFX_CROWD,
|
||||
SFX_VOICE_MINES,
|
||||
SFX_VOICE_MISSILE,
|
||||
SFX_VOICE_ROCKETS,
|
||||
SFX_VOICE_REVCON,
|
||||
SFX_VOICE_SHOCKWAVE,
|
||||
SFX_VOICE_SPECIAL,
|
||||
SFX_VOICE_COUNT_3,
|
||||
SFX_VOICE_COUNT_2,
|
||||
SFX_VOICE_COUNT_1,
|
||||
SFX_VOICE_COUNT_GO,
|
||||
} sfx_source_t;
|
||||
|
||||
typedef enum {
|
||||
SFX_NONE = 0,
|
||||
SFX_PLAY = (1<<0),
|
||||
SFX_RESERVE = (1<<1),
|
||||
SFX_LOOP = (1<<2),
|
||||
SFX_LOOP_PAUSE = (1<<3),
|
||||
} sfx_flags_t;
|
||||
|
||||
typedef struct {
|
||||
sfx_source_t source;
|
||||
sfx_flags_t flags;
|
||||
float pan;
|
||||
float current_pan;
|
||||
float volume;
|
||||
float current_volume;
|
||||
float pitch;
|
||||
float position;
|
||||
} sfx_t;
|
||||
|
||||
#define SFX_MAX 64
|
||||
#define SFX_MAX_ACTIVE 16
|
||||
|
||||
void sfx_load();
|
||||
void sfx_stero_mix(float *buffer, uint32_t len);
|
||||
void sfx_set_external_mix_cb(void (*cb)(float *, uint32_t len));
|
||||
void sfx_reset();
|
||||
void sfx_pause();
|
||||
void sfx_unpause();
|
||||
|
||||
sfx_t *sfx_play(sfx_source_t source_index);
|
||||
sfx_t *sfx_play_at(sfx_source_t source_index, vec3_t pos, vec3_t vel, float volume);
|
||||
sfx_t *sfx_reserve_loop(sfx_source_t source_index);
|
||||
void sfx_set_position(sfx_t *sfx, vec3_t pos, vec3_t vel, float volume);
|
||||
|
||||
typedef enum {
|
||||
SFX_MUSIC_PAUSED,
|
||||
SFX_MUSIC_RANDOM,
|
||||
SFX_MUSIC_SEQUENTIAL,
|
||||
SFX_MUSIC_LOOP
|
||||
} sfx_music_mode_t;
|
||||
|
||||
void sfx_music_next();
|
||||
void sfx_music_play(uint32_t index);
|
||||
void sfx_music_mode(sfx_music_mode_t);
|
||||
void sfx_music_pause();
|
||||
|
||||
#endif
|
||||
+1006
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
#ifndef SHIP_H
|
||||
#define SHIP_H
|
||||
|
||||
#include "../types.h"
|
||||
#include "track.h"
|
||||
#include "sfx.h"
|
||||
|
||||
#define SHIP_IN_TOW (1<< 0)
|
||||
#define SHIP_VIEW_REMOTE (1<< 1)
|
||||
#define SHIP_VIEW_INTERNAL (1<< 2)
|
||||
#define SHIP_DIRECTION_FORWARD (1<< 3)
|
||||
#define SHIP_FLYING (1<< 4)
|
||||
#define SHIP_LEFT_SIDE (1<< 5)
|
||||
#define SHIP_RACING (1<< 6)
|
||||
#define SHIP_COLL (1<< 7)
|
||||
#define SHIP_ON_JUNCTION (1<< 8)
|
||||
#define SHIP_VISIBLE (1<< 9)
|
||||
#define SHIP_IN_RESCUE (1<<10)
|
||||
#define SHIP_OVERTAKEN (1<<11)
|
||||
#define SHIP_JUST_IN_FRONT (1<<12)
|
||||
#define SHIP_JUNCTION_LEFT (1<<13)
|
||||
#define SHIP_SHIELDED (1<<14)
|
||||
#define SHIP_ELECTROED (1<<15)
|
||||
#define SHIP_REVCONNED (1<<16)
|
||||
#define SHIP_SPECIALED (1<<17)
|
||||
|
||||
|
||||
// Timings
|
||||
|
||||
#define UPDATE_TIME_INITIAL (200.0 * (1.0/30.0))
|
||||
#define UPDATE_TIME_THREE (150.0 * (1.0/30.0))
|
||||
#define UPDATE_TIME_RACE_VIEW (100.0 * (1.0/30.0))
|
||||
#define UPDATE_TIME_TWO (100.0 * (1.0/30.0))
|
||||
#define UPDATE_TIME_ONE ( 50.0 * (1.0/30.0))
|
||||
#define UPDATE_TIME_GO ( 0.0 * (1.0/30.0))
|
||||
|
||||
|
||||
// Physics conversion
|
||||
|
||||
#define FIXED_TO_FLOAT(V) ((V) * (1.0/4096.0))
|
||||
#define ANGLE_NORM_TO_RADIAN(V) ((V) * M_PI * 2.0)
|
||||
#define NTSC_STEP_TO_RATE_PER_SECOND(V) ((V) * 30.0)
|
||||
#define NTSC_ACCELERATION(V) NTSC_STEP_TO_RATE_PER_SECOND(NTSC_STEP_TO_RATE_PER_SECOND(V))
|
||||
#define NTSC_VELOCITY(V) NTSC_STEP_TO_RATE_PER_SECOND(V)
|
||||
|
||||
#define PITCH_VELOCITY(V) ((V) * (1.0/16.0))
|
||||
#define YAW_VELOCITY(V) ((V) * (1.0/64.0))
|
||||
#define ROLL_VELOCITY(V) ((V) * (1.0))
|
||||
|
||||
#define SHIP_FLYING_GRAVITY 80000.0
|
||||
#define SHIP_ON_TRACK_GRAVITY 30000.0
|
||||
#define SHIP_MIN_RESISTANCE 20 // 12
|
||||
#define SHIP_MAX_RESISTANCE 74
|
||||
#define SHIP_VELOCITY_SHIFT 6
|
||||
#define SHIP_TRACK_MAGNET 64 // 64
|
||||
#define SHIP_TRACK_FLOAT 256
|
||||
|
||||
#define SHIP_PITCH_ACCEL NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(PITCH_VELOCITY(30))))
|
||||
#define SHIP_THRUST_RATE NTSC_VELOCITY(16)
|
||||
#define SHIP_THRUST_FALLOFF NTSC_VELOCITY(8)
|
||||
#define SHIP_BRAKE_RATE NTSC_VELOCITY(32)
|
||||
|
||||
typedef struct ship_t {
|
||||
int16_t pilot;
|
||||
int flags;
|
||||
|
||||
section_t *section, *prev_section;
|
||||
|
||||
vec3_t dir_forward;
|
||||
vec3_t dir_right;
|
||||
vec3_t dir_up;
|
||||
|
||||
vec3_t position;
|
||||
vec3_t velocity;
|
||||
vec3_t acceleration;
|
||||
vec3_t thrust;
|
||||
|
||||
vec3_t angle;
|
||||
vec3_t angular_velocity;
|
||||
vec3_t angular_acceleration;
|
||||
|
||||
vec3_t temp_target; // used for start position and rescue target
|
||||
|
||||
float turn_rate;
|
||||
float turn_rate_max;
|
||||
float turn_rate_from_hit;
|
||||
|
||||
float mass;
|
||||
float thrust_mag;
|
||||
float thrust_max;
|
||||
float current_thrust_max;
|
||||
float speed;
|
||||
float brake_left;
|
||||
float brake_right;
|
||||
|
||||
float resistance;
|
||||
float skid;
|
||||
|
||||
float remote_thrust_mag;
|
||||
float remote_thrust_max;
|
||||
|
||||
// Remote Ship Attributes
|
||||
int16_t fight_back;
|
||||
float start_accelerate_timer;
|
||||
|
||||
// Weapon Attributes
|
||||
uint8_t weapon_type;
|
||||
struct ship_t *weapon_target;
|
||||
|
||||
float ebolt_timer;
|
||||
float ebolt_effect_timer;
|
||||
float revcon_timer;
|
||||
float special_timer;
|
||||
|
||||
// Race Control Attributes
|
||||
int position_rank;
|
||||
int lap;
|
||||
int max_lap;
|
||||
float lap_time;
|
||||
|
||||
int16_t section_num;
|
||||
int16_t prev_section_num;
|
||||
int16_t total_section_num;
|
||||
|
||||
float update_timer;
|
||||
float last_impact_time;
|
||||
|
||||
mat4_t mat;
|
||||
Object *model;
|
||||
Object *collision_model;
|
||||
uint16_t shadow_texture;
|
||||
|
||||
struct {
|
||||
vec3_t *v;
|
||||
vec3_t initial;
|
||||
} exhaust_plume[3];
|
||||
|
||||
// Control Routines
|
||||
vec3_t (*update_strat_func)(struct ship_t *, track_face_t *);
|
||||
void (*update_func)(struct ship_t *);
|
||||
|
||||
// Audio
|
||||
sfx_t *sfx_engine_thrust;
|
||||
sfx_t *sfx_engine_intake;
|
||||
sfx_t *sfx_turbulence;
|
||||
sfx_t *sfx_shield;
|
||||
} ship_t;
|
||||
|
||||
void ships_load();
|
||||
void ships_init(section_t *section);
|
||||
void ships_draw();
|
||||
void ships_update();
|
||||
|
||||
void ship_init(ship_t *self, section_t *section, int pilot, int position);
|
||||
void ship_init_exhaust_plume(ship_t *self);
|
||||
void ship_draw(ship_t *self);
|
||||
void ship_draw_shadow(ship_t *self);
|
||||
void ship_update(ship_t *self);
|
||||
void ship_collide_with_track(ship_t *self, track_face_t *face);
|
||||
void ship_collide_with_ship(ship_t *self, ship_t *other);
|
||||
|
||||
vec3_t ship_cockpit(ship_t *self);
|
||||
vec3_t ship_nose(ship_t *self);
|
||||
vec3_t ship_wing_left(ship_t *self);
|
||||
vec3_t ship_wing_right(ship_t *self);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,536 @@
|
||||
#include "../mem.h"
|
||||
#include "../input.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 "ship_ai.h"
|
||||
#include "game.h"
|
||||
|
||||
vec3_t ship_ai_strat_hold_center(ship_t *self, track_face_t *face);
|
||||
vec3_t ship_ai_strat_hold_right(ship_t *self, track_face_t *face);
|
||||
vec3_t ship_ai_strat_hold_left(ship_t *self, track_face_t *face);
|
||||
vec3_t ship_ai_strat_block(ship_t *self, track_face_t *face);
|
||||
vec3_t ship_ai_strat_avoid(ship_t *self, track_face_t *face);
|
||||
vec3_t ship_ai_strat_avoid_other(ship_t *self, track_face_t *face);
|
||||
vec3_t ship_ai_strat_zig_zag(ship_t *self, track_face_t *face);
|
||||
|
||||
void ship_ai_update_intro(ship_t *self) {
|
||||
self->temp_target = self->position;
|
||||
self->update_func = ship_ai_update_intro_await_go;
|
||||
|
||||
self->sfx_engine_thrust = sfx_reserve_loop(SFX_ENGINE_REMOTE);
|
||||
sfx_set_position(self->sfx_engine_thrust, self->position, self->velocity, 0.1);
|
||||
}
|
||||
|
||||
void ship_ai_update_intro_await_go(ship_t *self) {
|
||||
self->position.y = self->temp_target.y + sin(self->update_timer * (80.0 + self->pilot * 3.0) * 30.0 * M_PI * 2.0 / 4096.0) * 32;
|
||||
|
||||
self->update_timer -= system_tick();
|
||||
if (self->update_timer <= UPDATE_TIME_GO) {
|
||||
self->update_func = ship_ai_update_race;
|
||||
}
|
||||
}
|
||||
|
||||
vec3_t ship_ai_strat_hold_left(ship_t *self, track_face_t *face) {
|
||||
vec3_t fv1 = face->tris[0].vertices[1].pos;
|
||||
vec3_t fv2 = face->tris[0].vertices[0].pos;
|
||||
|
||||
return vec3_mulf(vec3_sub(fv1, fv2), 0.5);
|
||||
}
|
||||
|
||||
vec3_t ship_ai_strat_hold_right(ship_t *self, track_face_t *face) {
|
||||
vec3_t fv1 = face->tris[0].vertices[0].pos;
|
||||
vec3_t fv2 = face->tris[0].vertices[1].pos;
|
||||
|
||||
return vec3_mulf(vec3_sub(fv1, fv2), 0.5);
|
||||
}
|
||||
|
||||
|
||||
vec3_t ship_ai_strat_hold_center(ship_t *self, track_face_t *face) {
|
||||
return vec3(0, 0, 0);
|
||||
}
|
||||
|
||||
vec3_t ship_ai_strat_block(ship_t *self, track_face_t *face) {
|
||||
if (flags_is(g.ships[g.pilot].flags, SHIP_LEFT_SIDE)) {
|
||||
return ship_ai_strat_hold_left(self, face);
|
||||
}
|
||||
else {
|
||||
return ship_ai_strat_hold_right(self, face);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
vec3_t ship_ai_strat_avoid(ship_t *self, track_face_t *face) {
|
||||
if (flags_is(g.ships[g.pilot].flags, SHIP_LEFT_SIDE)) {
|
||||
return ship_ai_strat_hold_right(self, face);
|
||||
}
|
||||
else {
|
||||
return ship_ai_strat_hold_left(self, face);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
vec3_t ship_ai_strat_avoid_other(ship_t *self, track_face_t *face) {
|
||||
int min_section_num = 100;
|
||||
ship_t *avoid_ship;
|
||||
|
||||
for (int i = 0; i < NUM_PILOTS; i++) {
|
||||
if (i != self->pilot) {
|
||||
int section_diff = g.ships[i].total_section_num - self->total_section_num;
|
||||
if (min_section_num < section_diff) {
|
||||
min_section_num = section_diff;
|
||||
avoid_ship = &g.ships[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (avoid_ship && min_section_num < 10 && min_section_num > -2) {
|
||||
if (flags_is(avoid_ship->flags, SHIP_LEFT_SIDE)) {
|
||||
return ship_ai_strat_hold_right(self, face);
|
||||
}
|
||||
else {
|
||||
return ship_ai_strat_hold_left(self, face);
|
||||
}
|
||||
}
|
||||
return vec3(0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
vec3_t ship_ai_strat_zig_zag(ship_t *self, track_face_t *face) {
|
||||
int update_count = (self->update_timer * 30)/50;
|
||||
if (update_count % 2) {
|
||||
return ship_ai_strat_hold_right(self, face);
|
||||
}
|
||||
else {
|
||||
return ship_ai_strat_hold_left(self, face);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ship_ai_update_race(ship_t *self) {
|
||||
vec3_t offset_vector = vec3(0, 0, 0);
|
||||
|
||||
ship_t *player = &(g.ships[g.pilot]);
|
||||
|
||||
if (self->ebolt_timer > 0) {
|
||||
self->ebolt_timer -= system_tick();
|
||||
}
|
||||
if (self->ebolt_timer <= 0) {
|
||||
flags_rm(self->flags, SHIP_ELECTROED);
|
||||
}
|
||||
|
||||
int behind_speed = def.circuts[g.circut].settings[g.race_class].behind_speed;
|
||||
|
||||
|
||||
if (flags_not(self->flags, SHIP_FLYING)) {
|
||||
// Find First track base section
|
||||
track_face_t *face = track_section_get_base_face(self->section);
|
||||
|
||||
int section_diff = self->total_section_num - player->total_section_num;
|
||||
|
||||
flags_rm(self->flags, SHIP_JUST_IN_FRONT);
|
||||
|
||||
if (self == player) {
|
||||
self->update_strat_func = ship_ai_strat_avoid_other;
|
||||
if (self->remote_thrust_max > self->speed) {
|
||||
self->speed += self->remote_thrust_mag * 30 * system_tick();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Make global DPA decisions , these will effect the craft in
|
||||
// relation to your race position
|
||||
|
||||
// Accelerate remote ships away at start, start_accelerate_count set in
|
||||
// InitShipData and is an exponential progression
|
||||
|
||||
if (self->start_accelerate_timer > 0) {
|
||||
self->start_accelerate_timer -= system_tick();
|
||||
self->update_timer = 0;
|
||||
self->update_strat_func = ship_ai_strat_avoid;
|
||||
if ((self->remote_thrust_max + 1200) > self->speed) {
|
||||
self->speed += (self->remote_thrust_mag + 150) * 30 * system_tick();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Ship has been left WELL BEHIND; set it to avoid
|
||||
// other ships and update its speed as normal
|
||||
|
||||
else if (section_diff < -10) { // Ship behind, AVOID
|
||||
self->update_timer = 0;
|
||||
self->update_strat_func = ship_ai_strat_avoid;
|
||||
|
||||
// If ship has been well passed, increase its speed to allow
|
||||
// it to make a challenge when the player fouls up
|
||||
|
||||
if (((self->remote_thrust_max + behind_speed) > self->speed)) {
|
||||
self->speed += self->remote_thrust_mag * 30 * system_tick();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Ship is JUST AHEAD
|
||||
|
||||
else if ((section_diff <= 4) && (section_diff > 0)) { // Ship close by, beware does not account for lapped opponents yet
|
||||
flags_add(self->flags, SHIP_JUST_IN_FRONT);
|
||||
|
||||
if (self->update_timer <= 0) { // Make New Decision
|
||||
int chance = rand_int(0, 64); // 12
|
||||
|
||||
self->update_timer = UPDATE_TIME_JUST_FRONT;
|
||||
if (self->fight_back) { // Ship wants to make life difficult
|
||||
if ((chance < 40) || (self->weapon_type == WEAPON_TYPE_NONE)) { // Ship will try to block you
|
||||
self->update_strat_func = ship_ai_strat_block;
|
||||
}
|
||||
else if ((chance >= 40) && (chance < 52)) { // Ship will attempt to drop mines in your path
|
||||
self->update_strat_func = ship_ai_strat_block;
|
||||
if (flags_not(self->flags, SHIP_SHIELDED) && flags_is(self->flags, SHIP_RACING)) {
|
||||
sfx_play(SFX_VOICE_MINES);
|
||||
self->weapon_type = WEAPON_TYPE_MINE;
|
||||
weapons_fire_delayed(self, self->weapon_type);
|
||||
}
|
||||
}
|
||||
else if ((chance >= 52) && (chance < 64)) { // Ship will raise its shield
|
||||
self->update_strat_func = ship_ai_strat_block;
|
||||
if (flags_not(self->flags, SHIP_SHIELDED)) {
|
||||
self->weapon_type = WEAPON_TYPE_SHIELD;
|
||||
weapons_fire(self, self->weapon_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Let the first ships be easy to pass
|
||||
self->update_strat_func = ship_ai_strat_avoid;
|
||||
}
|
||||
}
|
||||
|
||||
self->update_timer -= system_tick();
|
||||
|
||||
if (flags_is(self->flags, SHIP_OVERTAKEN)) {
|
||||
// If ship has just overtaken, slow it down to a reasonable speed
|
||||
if ((self->remote_thrust_max + behind_speed) > self->speed) {
|
||||
self->speed += self->remote_thrust_mag * 30 * system_tick();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Increase the speed of any craft just in front slightly
|
||||
if (((self->remote_thrust_max + (behind_speed >> 1)) > self->speed)) {
|
||||
self->speed += self->remote_thrust_mag * 30 * system_tick();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Ship is JUST BEHIND; we must decided if and how many times it 'should have a go back'
|
||||
|
||||
else if ((section_diff >= -10) && (section_diff <= 0)) { // Ship just behind, MAKE DECISION
|
||||
if (self->update_timer <= 0) { // Make New Decision
|
||||
self->update_timer = UPDATE_TIME_JUST_BEHIND;
|
||||
|
||||
if (self->fight_back) { // Ship wants you to say "Outside Now!"
|
||||
if (self->weapon_type == WEAPON_TYPE_NONE) {
|
||||
self->update_strat_func = ship_ai_strat_avoid;
|
||||
flags_add(self->flags, SHIP_OVERTAKEN);
|
||||
}
|
||||
else {
|
||||
int chance = rand_int(0, 64);
|
||||
|
||||
if (chance < 48) {
|
||||
self->update_strat_func = ship_ai_strat_block;
|
||||
}
|
||||
else if ((chance >= 40) && (chance < 54)) {
|
||||
self->update_strat_func = ship_ai_strat_avoid;
|
||||
flags_rm(self->flags, SHIP_OVERTAKEN);
|
||||
if (flags_not(self->flags, SHIP_SHIELDED) && flags_is(self->flags, SHIP_RACING)) {
|
||||
sfx_play(SFX_VOICE_ROCKETS);
|
||||
self->weapon_type = WEAPON_TYPE_ROCKET;
|
||||
weapons_fire_delayed(self, self->weapon_type);
|
||||
}
|
||||
}
|
||||
else if ((chance >= 54) && (chance < 60)) {
|
||||
self->update_strat_func = ship_ai_strat_avoid;
|
||||
flags_rm(self->flags, SHIP_OVERTAKEN);
|
||||
if (flags_not(self->flags, SHIP_SHIELDED) && flags_is(self->flags, SHIP_RACING)) {
|
||||
sfx_play(SFX_VOICE_MISSILE);
|
||||
self->weapon_type = WEAPON_TYPE_MISSILE;
|
||||
self->weapon_target = &g.ships[g.pilot];
|
||||
weapons_fire_delayed(self, self->weapon_type);
|
||||
}
|
||||
}
|
||||
else if ((chance >= 60) && (chance < 64)) {
|
||||
self->update_strat_func = ship_ai_strat_avoid;
|
||||
flags_rm(self->flags, SHIP_OVERTAKEN);
|
||||
if (flags_not(self->flags, SHIP_SHIELDED) && flags_is(self->flags, SHIP_RACING)) {
|
||||
sfx_play(SFX_VOICE_SHOCKWAVE);
|
||||
self->weapon_type = WEAPON_TYPE_EBOLT;
|
||||
self->weapon_target = &g.ships[g.pilot];
|
||||
weapons_fire_delayed(self, self->weapon_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // If ship destined to be tail-ender then slow down
|
||||
self->remote_thrust_max = 2100 ;
|
||||
self->remote_thrust_mag = 25;
|
||||
self->speed = 2100 ;
|
||||
self->update_strat_func = ship_ai_strat_avoid;
|
||||
flags_rm(self->flags, SHIP_OVERTAKEN);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_PILOTS; i++) { // If another ship is just in front pass fight on
|
||||
if (flags_is(g.ships[i].flags, SHIP_JUST_IN_FRONT)) {
|
||||
self->update_strat_func = ship_ai_strat_avoid;
|
||||
flags_rm(self->flags, SHIP_OVERTAKEN);
|
||||
}
|
||||
}
|
||||
|
||||
self->update_timer -= system_tick();
|
||||
|
||||
|
||||
if (flags_is(self->flags, SHIP_OVERTAKEN)) {
|
||||
if ((self->remote_thrust_max + 700) > self->speed) {
|
||||
self->speed += self->remote_thrust_mag * 2 * 30 * system_tick();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (((self->remote_thrust_max + behind_speed) > self->speed)) {
|
||||
self->speed += self->remote_thrust_mag * 30 * system_tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Ship is WELL AHEAD; we must slow the opponent to
|
||||
// give the weaker player a chance to catch up
|
||||
|
||||
else if (section_diff > (NUM_PILOTS - self->position_rank) * 15 && section_diff < 150) {
|
||||
self->speed += self->remote_thrust_mag * 0.5 * 30 * system_tick();
|
||||
if (self->speed > self->remote_thrust_max * 0.5) {
|
||||
self->speed = self->remote_thrust_max * 0.5;
|
||||
}
|
||||
|
||||
self->update_timer = 0;
|
||||
self->update_strat_func = ship_ai_strat_hold_center;
|
||||
}
|
||||
|
||||
|
||||
// Ship is TOO FAR AHEAD
|
||||
|
||||
else if (section_diff >= 150) { // Ship too far ahead, let it continue
|
||||
self->update_timer = 0;
|
||||
self->update_strat_func = ship_ai_strat_avoid;
|
||||
|
||||
if ((self->remote_thrust_max > self->speed)) {
|
||||
self->speed += self->remote_thrust_mag * 30 * system_tick();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Ship is IN SIGHT
|
||||
|
||||
else if ((section_diff <= 10) && (section_diff > 4)) { // Ship close by, beware does not account for lapped opponents yet
|
||||
if (self->update_timer <= 0) { // Make New Decision
|
||||
int chance = rand_int(0, 5);
|
||||
|
||||
self->update_timer = UPDATE_TIME_IN_SIGHT;
|
||||
switch (chance) {
|
||||
case 0: self->update_strat_func = ship_ai_strat_hold_center; break;
|
||||
case 1: self->update_strat_func = ship_ai_strat_hold_left; break;
|
||||
case 2: self->update_strat_func = ship_ai_strat_hold_right; break;
|
||||
case 3: self->update_strat_func = ship_ai_strat_block; break;
|
||||
case 4: self->update_strat_func = ship_ai_strat_zig_zag; break;
|
||||
}
|
||||
}
|
||||
|
||||
self->update_timer -= system_tick();
|
||||
|
||||
if ((self->remote_thrust_max > self->speed)) {
|
||||
self->speed += self->remote_thrust_mag * 30 * system_tick();
|
||||
}
|
||||
} // End of DPA control options
|
||||
|
||||
|
||||
// Ship is JUST OUT OF SIGHT
|
||||
|
||||
else {
|
||||
self->update_timer = 0;
|
||||
self->update_strat_func = ship_ai_strat_hold_center;
|
||||
if ((self->remote_thrust_max > self->speed)) {
|
||||
self->speed += self->remote_thrust_mag * 30 * system_tick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!self->update_strat_func) {
|
||||
self->update_strat_func = ship_ai_strat_hold_center;
|
||||
}
|
||||
offset_vector = (self->update_strat_func)(self, face);
|
||||
|
||||
|
||||
// Make decision as to which path the craft will take at a junction
|
||||
|
||||
section_t *section = self->section->prev;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
section = section->next;
|
||||
}
|
||||
|
||||
if (section->junction) {
|
||||
if (flags_is(section->junction->flags, SECTION_JUNCTION_START)) {
|
||||
int chance = rand_int(0, 2);
|
||||
if (chance == 0) {
|
||||
flags_add(self->flags, SHIP_JUNCTION_LEFT);
|
||||
}
|
||||
else {
|
||||
flags_rm(self->flags, SHIP_JUNCTION_LEFT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section = self->section->prev;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (section->junction) {
|
||||
if (flags_is(section->junction->flags, SECTION_JUNCTION_START)) {
|
||||
if (flags_is(self->flags, SHIP_JUNCTION_LEFT)) {
|
||||
section = section->junction;
|
||||
}
|
||||
else {
|
||||
section = section->next;
|
||||
}
|
||||
}
|
||||
else {
|
||||
section = section->next;
|
||||
}
|
||||
}
|
||||
else {
|
||||
section = section->next;
|
||||
}
|
||||
}
|
||||
section_t *next = section->next;
|
||||
section = self->section;
|
||||
|
||||
|
||||
|
||||
|
||||
// General routines - Non decision based
|
||||
|
||||
|
||||
// Bleed off speed as orientation changes
|
||||
|
||||
self->speed -= fabsf(self->speed * self->angular_velocity.y) * 4 / (M_PI * 2) * system_tick(); // >> 14
|
||||
self->speed -= fabsf(self->speed * self->angular_velocity.x) * 4 / (M_PI * 2) * system_tick(); // >> 14
|
||||
|
||||
// If remote has gone over boost
|
||||
if (flags_is(face->flags, FACE_BOOST) && (self->update_strat_func == ship_ai_strat_hold_left || self->update_strat_func == ship_ai_strat_hold_center)) {
|
||||
self->speed += 200 * 30 * system_tick();
|
||||
}
|
||||
face++;
|
||||
if (flags_is(face->flags, FACE_BOOST) && (self->update_strat_func == ship_ai_strat_hold_right || self->update_strat_func == ship_ai_strat_hold_center)) {
|
||||
self->speed += 200 * 30 * system_tick();
|
||||
}
|
||||
|
||||
vec3_t track_target;
|
||||
if (flags_is(self->section->flags, SECTION_JUMP)) { // Cure for ships getting stuck on hump lip
|
||||
track_target = vec3_sub(self->section->center, self->section->prev->center);
|
||||
}
|
||||
else {
|
||||
track_target = vec3_sub(next->center, section->center);
|
||||
}
|
||||
|
||||
float gap_length = vec3_len(track_target);
|
||||
track_target = vec3_mulf(track_target, self->speed / gap_length);
|
||||
|
||||
vec3_t path1 = vec3_add(section->center, offset_vector);
|
||||
vec3_t path2 = vec3_add(next->center, offset_vector);
|
||||
|
||||
vec3_t best_path = vec3_project_to_ray(self->position, path2, path1);
|
||||
self->acceleration = vec3_add(track_target, vec3_mulf(vec3_sub(best_path, self->position), 0.5));
|
||||
|
||||
|
||||
vec3_t face_point = face->tris[0].vertices[0].pos;
|
||||
float height = vec3_distance_to_plane(self->position, face_point, face->normal);
|
||||
|
||||
if (height < 50) {
|
||||
height = 50;
|
||||
}
|
||||
|
||||
self->acceleration = vec3_add(self->acceleration, vec3_mulf(vec3_sub(
|
||||
vec3_mulf(face->normal, (SHIP_TRACK_FLOAT * SHIP_TRACK_MAGNET) / height),
|
||||
vec3_mulf(face->normal, SHIP_TRACK_MAGNET)
|
||||
), 16.0));
|
||||
self->velocity = vec3_add(self->velocity, vec3_mulf(self->acceleration, 30 * system_tick()));
|
||||
|
||||
|
||||
float xy_dist = sqrt(track_target.x * track_target.x + track_target.z * track_target.z);
|
||||
|
||||
self->angular_velocity.x = wrap_angle(-atan2(track_target.y, xy_dist) - self->angle.x) * (1.0/16.0) * 30;
|
||||
self->angular_velocity.y = (wrap_angle(-atan2(track_target.x, track_target.z) - self->angle.y) * (1.0/16.0)) * 30 + self->turn_rate_from_hit;
|
||||
}
|
||||
|
||||
|
||||
// Ship is SHIP_FLYING
|
||||
|
||||
else {
|
||||
section_t *section = self->section->next->next;
|
||||
section_t *next = section->next;
|
||||
|
||||
self->update_strat_func = ship_ai_strat_hold_center;
|
||||
offset_vector = (self->update_strat_func)(self, NULL);
|
||||
|
||||
if (self->remote_thrust_max > self->speed) {
|
||||
self->speed += self->remote_thrust_mag ;
|
||||
}
|
||||
|
||||
self->speed -= fabsf(self->speed * self->angular_velocity.y) * (4 * M_PI * 2) * system_tick();
|
||||
vec3_t track_target = vec3_sub(next->center, section->center);
|
||||
float gap_length = vec3_len(track_target);
|
||||
|
||||
track_target.x = (track_target.x * self->speed) / gap_length;
|
||||
track_target.z = (track_target.z * self->speed) / gap_length;
|
||||
|
||||
track_target.y = 500;
|
||||
|
||||
vec3_t best_path = vec3_project_to_ray(self->position, next->center, self->section->center);
|
||||
self->acceleration = vec3(
|
||||
(track_target.x + ((best_path.x - self->position.x) * 0.5)),
|
||||
track_target.y,
|
||||
(track_target.z + ((best_path.z - self->position.z) * 0.5))
|
||||
);
|
||||
self->velocity = vec3_add(self->velocity, vec3_mulf(self->acceleration, 30 * system_tick()));
|
||||
|
||||
self->angular_velocity.x = -0.3 - self->angle.x * 30;
|
||||
self->angular_velocity.y = wrap_angle(-atan2(track_target.x, track_target.z) - self->angle.y) * (1.0/16.0) * 30;
|
||||
}
|
||||
|
||||
|
||||
self->angular_velocity.z += (self->angular_velocity.y * 2.0 - self->angular_velocity.z * 0.5) * 30 * system_tick();
|
||||
self->turn_rate_from_hit -= self->turn_rate_from_hit * 0.125 * 30 * system_tick();
|
||||
|
||||
self->angle = vec3_add(self->angle, vec3_mulf(self->angular_velocity, system_tick()));
|
||||
self->angle.z -= self->angle.z * 0.125 * 30 * system_tick();
|
||||
self->angle = vec3_wrap_angle(self->angle);
|
||||
|
||||
self->velocity = vec3_sub(self->velocity, vec3_mulf(self->velocity, 0.125 * 30 * system_tick()));
|
||||
self->position = vec3_add(self->position, vec3_mulf(self->velocity, 0.015625 * 30 * system_tick()));
|
||||
|
||||
if (flags_is(self->flags, SHIP_ELECTROED)) {
|
||||
self->position = vec3_add(self->position, vec3(rand_float(-20, 20), rand_float(-20, 20), rand_float(-20, 20)));
|
||||
|
||||
if (rand_int(0, 50) == 0) {
|
||||
self->speed -= self->speed * 0.5 * 30 * system_tick();
|
||||
}
|
||||
}
|
||||
|
||||
sfx_set_position(self->sfx_engine_thrust, self->position, self->velocity, 0.5);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef SHIP_AI_H
|
||||
#define SHIP_AI_H
|
||||
|
||||
#include "ship.h"
|
||||
|
||||
#define UPDATE_TIME_JUST_FRONT (150.0 * (1.0/30.0))
|
||||
#define UPDATE_TIME_JUST_BEHIND (200.0 * (1.0/30.0))
|
||||
#define UPDATE_TIME_IN_SIGHT (200.0 * (1.0/30.0))
|
||||
|
||||
void ship_ai_update_race(ship_t *self);
|
||||
void ship_ai_update_intro(ship_t *self);
|
||||
void ship_ai_update_intro_await_go(ship_t *self);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,582 @@
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../mem.h"
|
||||
#include "object.h"
|
||||
#include "track.h"
|
||||
#include "ship.h"
|
||||
#include "weapon.h"
|
||||
#include "hud.h"
|
||||
#include "droid.h"
|
||||
#include "camera.h"
|
||||
#include "../utils.h"
|
||||
#include "scene.h"
|
||||
|
||||
|
||||
#include "../input.h"
|
||||
#include "../system.h"
|
||||
|
||||
#include "sfx.h"
|
||||
#include "ship_player.h"
|
||||
#include "ship_ai.h"
|
||||
#include "game.h"
|
||||
#include "particle.h"
|
||||
|
||||
void ship_player_update_sfx(ship_t *self) {
|
||||
float speedf = self->speed * 0.000015;
|
||||
self->sfx_engine_intake->volume = clamp(speedf, 0, 0.5);
|
||||
self->sfx_engine_intake->pitch = 0.5 + speedf * 1.25;
|
||||
|
||||
self->sfx_engine_thrust->volume = 0.05 + 0.025 * (self->thrust_mag / self->thrust_max);
|
||||
self->sfx_engine_thrust->pitch = 0.2 + 0.5 * (self->thrust_mag / self->thrust_max) + speedf;
|
||||
|
||||
float brake_left = self->brake_left * 0.0035;
|
||||
float brake_right = self->brake_right * 0.0035;
|
||||
self->sfx_turbulence->volume = (speedf * brake_left + speedf * brake_right) * 0.5;
|
||||
self->sfx_turbulence->pan = (brake_right - brake_left);
|
||||
|
||||
self->sfx_shield->volume = flags_is(self->flags, SHIP_SHIELDED) ? 0.5 : 0;
|
||||
}
|
||||
|
||||
void ship_player_update_intro(ship_t *self) {
|
||||
self->temp_target = self->position;
|
||||
|
||||
self->sfx_engine_thrust = sfx_reserve_loop(SFX_ENGINE_THRUST);
|
||||
self->sfx_engine_intake = sfx_reserve_loop(SFX_ENGINE_INTAKE);
|
||||
self->sfx_shield = sfx_reserve_loop(SFX_SHIELD);
|
||||
self->sfx_turbulence = sfx_reserve_loop(SFX_TURBULENCE);
|
||||
|
||||
ship_player_update_intro_general(self);
|
||||
self->update_func = ship_player_update_intro_await_three;
|
||||
}
|
||||
|
||||
void ship_player_update_intro_await_three(ship_t *self) {
|
||||
ship_player_update_intro_general(self);
|
||||
|
||||
if (self->update_timer <= UPDATE_TIME_THREE) {
|
||||
sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_3);
|
||||
self->update_func = ship_player_update_intro_await_two;
|
||||
}
|
||||
}
|
||||
|
||||
void ship_player_update_intro_await_two(ship_t *self) {
|
||||
ship_player_update_intro_general(self);
|
||||
|
||||
if (self->update_timer <= UPDATE_TIME_TWO) {
|
||||
scene_set_start_booms(1);
|
||||
sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_2);
|
||||
self->update_func = ship_player_update_intro_await_one;
|
||||
}
|
||||
}
|
||||
|
||||
void ship_player_update_intro_await_one(ship_t *self) {
|
||||
ship_player_update_intro_general(self);
|
||||
|
||||
if (self->update_timer <= UPDATE_TIME_ONE) {
|
||||
scene_set_start_booms(2);
|
||||
sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_1);
|
||||
self->update_func = ship_player_update_intro_await_go;
|
||||
}
|
||||
}
|
||||
|
||||
void ship_player_update_intro_await_go(ship_t *self) {
|
||||
ship_player_update_intro_general(self);
|
||||
|
||||
if (self->update_timer <= UPDATE_TIME_GO) {
|
||||
scene_set_start_booms(3);
|
||||
sfx_t *sfx = sfx_play(SFX_VOICE_COUNT_GO);
|
||||
|
||||
if (flags_is(self->flags, SHIP_RACING)) {
|
||||
// Check for stall
|
||||
if (self->thrust_mag >= 680 && self->thrust_mag <= 700) {
|
||||
self->thrust_mag = 1800;
|
||||
self->current_thrust_max = 1800;
|
||||
}
|
||||
else if (self->thrust_mag < 680) {
|
||||
self->current_thrust_max = self->thrust_max;
|
||||
}
|
||||
else {
|
||||
self->current_thrust_max = 200;
|
||||
}
|
||||
|
||||
self->update_timer = UPDATE_TIME_STALL;
|
||||
self->update_func = ship_player_update_race;
|
||||
}
|
||||
else {
|
||||
self->update_func = ship_ai_update_race;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ship_player_update_intro_general(ship_t *self) {
|
||||
self->update_timer -= system_tick();
|
||||
self->position.y = self->temp_target.y + sin(self->update_timer * 80.0 * 30.0 * M_PI * 2.0 / 4096.0) * 32;
|
||||
|
||||
// Thrust
|
||||
if (input_state(A_THRUST)) {
|
||||
self->thrust_mag += input_state(A_THRUST) * SHIP_THRUST_RATE * system_tick();
|
||||
}
|
||||
else {
|
||||
self->thrust_mag -= SHIP_THRUST_RATE * system_tick();
|
||||
}
|
||||
|
||||
self->thrust_mag = clamp(self->thrust_mag, 0, self->thrust_max);
|
||||
|
||||
// View
|
||||
if (input_pressed(A_CHANGE_VIEW)) {
|
||||
if (flags_not(self->flags, SHIP_VIEW_INTERNAL)) {
|
||||
g.camera.update_func = camera_update_race_internal;
|
||||
flags_add(self->flags, SHIP_VIEW_INTERNAL);
|
||||
}
|
||||
else {
|
||||
g.camera.update_func = camera_update_race_external;
|
||||
flags_rm(self->flags, SHIP_VIEW_INTERNAL);
|
||||
}
|
||||
}
|
||||
|
||||
ship_player_update_sfx(self);
|
||||
}
|
||||
|
||||
|
||||
void ship_player_update_race(ship_t *self) {
|
||||
if (flags_not(self->flags, SHIP_RACING)) {
|
||||
self->update_func = ship_ai_update_race;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->ebolt_timer > 0) {
|
||||
self->ebolt_timer -= system_tick();
|
||||
}
|
||||
|
||||
if (self->ebolt_timer <= 0) {
|
||||
flags_rm(self->flags, SHIP_ELECTROED);
|
||||
}
|
||||
|
||||
if (self->revcon_timer > 0) {
|
||||
self->revcon_timer -= system_tick();
|
||||
}
|
||||
|
||||
if (self->revcon_timer <= 0) {
|
||||
flags_rm(self->flags, SHIP_REVCONNED);
|
||||
}
|
||||
|
||||
if (self->special_timer > 0) {
|
||||
self->special_timer -= system_tick();
|
||||
}
|
||||
|
||||
if (self->special_timer <= 0) {
|
||||
flags_rm(self->flags, SHIP_SPECIALED);
|
||||
}
|
||||
|
||||
if (flags_is(self->flags, SHIP_REVCONNED)) {
|
||||
// FIXME_PL: make sure revconned is honored
|
||||
}
|
||||
|
||||
self->angular_acceleration = vec3(0, 0, 0);
|
||||
|
||||
if (input_state(A_LEFT)) {
|
||||
if (self->angular_velocity.y >= 0) {
|
||||
self->angular_acceleration.y += input_state(A_LEFT) * self->turn_rate;
|
||||
}
|
||||
else if (self->angular_velocity.y < 0) {
|
||||
self->angular_acceleration.y += input_state(A_LEFT) * self->turn_rate * 2;
|
||||
}
|
||||
}
|
||||
else if (input_state(A_RIGHT)) {
|
||||
if (self->angular_velocity.y <= 0) {
|
||||
self->angular_acceleration.y -= input_state(A_RIGHT) * self->turn_rate;
|
||||
}
|
||||
else if (self->angular_velocity.y > 0) {
|
||||
self->angular_acceleration.y -= input_state(A_RIGHT) * self->turn_rate * 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags_is(self->flags, SHIP_ELECTROED)) {
|
||||
self->ebolt_effect_timer += system_tick();
|
||||
// Yank the ship every 0.1 seconds
|
||||
if (self->ebolt_effect_timer > 0.1) {
|
||||
if (flags_is(self->flags, SHIP_VIEW_INTERNAL)) {
|
||||
// SetShake(2); // FIXME
|
||||
}
|
||||
self->angular_velocity.y += rand_float(-0.5, 0.5); // FIXME: 60fps
|
||||
self->ebolt_effect_timer -= 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
self->angular_acceleration.x += input_state(A_DOWN) * SHIP_PITCH_ACCEL;
|
||||
self->angular_acceleration.x -= input_state(A_UP) * SHIP_PITCH_ACCEL;
|
||||
|
||||
// Handle Stall
|
||||
if (self->update_timer > 0) {
|
||||
if (self->current_thrust_max < 500) {
|
||||
self->current_thrust_max += rand_float(0, 165) * system_tick();
|
||||
}
|
||||
self->update_timer -= system_tick();
|
||||
}
|
||||
else {
|
||||
// End stall / boost
|
||||
self->current_thrust_max = self->thrust_max;
|
||||
}
|
||||
|
||||
// Thrust
|
||||
if (input_state(A_THRUST)) {
|
||||
self->thrust_mag += input_state(A_THRUST) * SHIP_THRUST_RATE * system_tick();
|
||||
}
|
||||
else {
|
||||
self->thrust_mag -= SHIP_THRUST_FALLOFF * system_tick();
|
||||
}
|
||||
self->thrust_mag = clamp(self->thrust_mag, 0, self->current_thrust_max);
|
||||
|
||||
if (flags_is(self->flags, SHIP_ELECTROED) && rand_int(0, 80) == 0) {
|
||||
self->thrust_mag -= self->thrust_mag * 0.25; // FIXME: 60fps
|
||||
}
|
||||
|
||||
// Brake
|
||||
if (input_state(A_BRAKE_RIGHT)) {
|
||||
self->brake_right += SHIP_BRAKE_RATE * system_tick();
|
||||
}
|
||||
else if (self->brake_right > 0) {
|
||||
self->brake_right -= SHIP_BRAKE_RATE * system_tick();
|
||||
}
|
||||
self->brake_right = clamp(self->brake_right, 0, 256);
|
||||
|
||||
if (input_state(A_BRAKE_LEFT)) {
|
||||
self->brake_left += SHIP_BRAKE_RATE * system_tick();
|
||||
}
|
||||
else if (self->brake_left > 0) {
|
||||
self->brake_left -= SHIP_BRAKE_RATE * system_tick();
|
||||
}
|
||||
self->brake_left = clamp(self->brake_left, 0, 256);
|
||||
|
||||
// View
|
||||
if (input_pressed(A_CHANGE_VIEW)) {
|
||||
if (flags_not(self->flags, SHIP_VIEW_INTERNAL)) {
|
||||
g.camera.update_func = camera_update_race_internal;
|
||||
flags_add(self->flags, SHIP_VIEW_INTERNAL);
|
||||
}
|
||||
else {
|
||||
g.camera.update_func = camera_update_race_external;
|
||||
flags_rm(self->flags, SHIP_VIEW_INTERNAL);
|
||||
}
|
||||
}
|
||||
|
||||
if (self->weapon_type == WEAPON_TYPE_MISSILE || self->weapon_type == WEAPON_TYPE_EBOLT) {
|
||||
self->weapon_target = ship_player_find_target(self);
|
||||
}
|
||||
else {
|
||||
self->weapon_target = NULL;
|
||||
}
|
||||
|
||||
// Fire
|
||||
// self->weapon_type = WEAPON_TYPE_MISSILE; // Test weapon
|
||||
|
||||
if (input_pressed(A_FIRE) && self->weapon_type != WEAPON_TYPE_NONE) {
|
||||
if (flags_not(self->flags, SHIP_SHIELDED)) {
|
||||
weapons_fire(self, self->weapon_type);
|
||||
}
|
||||
else {
|
||||
sfx_play(SFX_MENU_MOVE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Physics
|
||||
|
||||
// Calculate thrust vector along principle axis of ship
|
||||
self->thrust = vec3_mulf(self->dir_forward, self->thrust_mag * 64);
|
||||
self->speed = vec3_len(self->velocity);
|
||||
vec3_t forward_velocity = vec3_mulf(self->dir_forward, self->speed);
|
||||
|
||||
// SECTION_JUMP
|
||||
if (flags_is(self->section->flags, SECTION_JUMP)) {
|
||||
track_face_t *face = track_section_get_base_face(self->section);
|
||||
|
||||
// Project the ship's position to the track section using the face normal.
|
||||
// If the point lands on the track, the sum of the angles between the
|
||||
// point and the track vertices will be M_PI*2.
|
||||
// If it's less then M_PI*2 (minus a safety margin) we are flying!
|
||||
|
||||
vec3_t face_point = face->tris[0].vertices[0].pos;
|
||||
float height = vec3_distance_to_plane(self->position, face_point, face->normal);
|
||||
vec3_t plane_point = vec3_sub(self->position, vec3_mulf(face->normal, height));
|
||||
|
||||
vec3_t vec0 = vec3_sub(plane_point, face->tris[0].vertices[1].pos);
|
||||
vec3_t vec1 = vec3_sub(plane_point, face->tris[0].vertices[2].pos);
|
||||
face++;
|
||||
vec3_t vec2 = vec3_sub(plane_point, face->tris[0].vertices[0].pos);
|
||||
vec3_t vec3 = vec3_sub(plane_point, face->tris[1].vertices[0].pos);
|
||||
|
||||
float angle =
|
||||
vec3_angle(vec0, vec2) +
|
||||
vec3_angle(vec2, vec3) +
|
||||
vec3_angle(vec3, vec1) +
|
||||
vec3_angle(vec1, vec0);
|
||||
if (angle < M_PI * 2 - 0.01) {
|
||||
flags_add(self->flags, SHIP_FLYING);
|
||||
}
|
||||
}
|
||||
|
||||
// Held by track
|
||||
if (flags_not(self->flags, SHIP_FLYING)) {
|
||||
track_face_t *face = track_section_get_base_face(self->section);
|
||||
ship_collide_with_track(self, face);
|
||||
|
||||
if (flags_not(self->flags, SHIP_LEFT_SIDE)) {
|
||||
face++;
|
||||
}
|
||||
|
||||
// Boost
|
||||
if (flags_not(self->flags, SHIP_SPECIALED) && flags_is(face->flags, FACE_BOOST)) {
|
||||
vec3_t track_direction = vec3_sub(self->section->next->center, self->section->center);
|
||||
self->velocity = vec3_add(self->velocity, vec3_mulf(track_direction, 30 * system_tick()));
|
||||
}
|
||||
|
||||
vec3_t face_point = face->tris[0].vertices[0].pos;
|
||||
float height = vec3_distance_to_plane(self->position, face_point, face->normal);
|
||||
|
||||
// Collision with floor
|
||||
if (height <= 0) {
|
||||
if (self->last_impact_time > 0.2) {
|
||||
self->last_impact_time = 0;
|
||||
sfx_play_at(SFX_IMPACT, self->position, vec3(0,0,0), 1);
|
||||
}
|
||||
self->velocity = vec3_reflect(self->velocity, face->normal, 2);
|
||||
self->velocity = vec3_sub(self->velocity, vec3_mulf(self->velocity, 0.125));
|
||||
self->velocity = vec3_sub(self->velocity, face->normal);
|
||||
}
|
||||
else if (height < 30) {
|
||||
self->velocity = vec3_add(self->velocity, face->normal);
|
||||
}
|
||||
|
||||
if (height < 50) {
|
||||
height = 50;
|
||||
}
|
||||
|
||||
// Calculate acceleration
|
||||
float brake = (self->brake_left + self->brake_right);
|
||||
float resistance = (self->resistance * (SHIP_MAX_RESISTANCE - (brake * 0.125))) * 0.0078125;
|
||||
|
||||
vec3_t force = vec3(0, SHIP_ON_TRACK_GRAVITY, 0);
|
||||
force = vec3_add(force, vec3_mulf(vec3_mulf(face->normal, 4096), (SHIP_TRACK_MAGNET * SHIP_TRACK_FLOAT) / height));
|
||||
force = vec3_sub(force, vec3_mulf(vec3_mulf(face->normal, 4096), SHIP_TRACK_MAGNET));
|
||||
force = vec3_add(force, self->thrust);
|
||||
|
||||
self->acceleration = vec3_divf(vec3_sub(forward_velocity, self->velocity), self->skid + brake * 0.25);
|
||||
self->acceleration = vec3_add(self->acceleration, vec3_divf(force, self->mass));
|
||||
self->acceleration = vec3_sub(self->acceleration, vec3_divf(self->velocity, resistance));
|
||||
|
||||
// Burying the nose in the track? Move it out!
|
||||
vec3_t nose_pos = vec3_add(self->position, vec3_mulf(self->dir_forward, 128));
|
||||
float nose_height = vec3_distance_to_plane(nose_pos,face_point, face->normal);
|
||||
if (nose_height < 600) {
|
||||
self->angular_acceleration.x += NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT((height - nose_height + 5) * (1.0/16.0))));
|
||||
}
|
||||
else {
|
||||
self->angular_acceleration.x += NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(-50.0/16.0)));
|
||||
}
|
||||
}
|
||||
|
||||
// Flying
|
||||
else {
|
||||
// Detect the need for a rescue droid
|
||||
section_t *next = self->section->next;
|
||||
|
||||
vec3_t best_path = vec3_project_to_ray(self->position, next->center, self->section->center);
|
||||
vec3_t distance = vec3_sub(best_path, self->position);
|
||||
|
||||
if (distance.y > 0) {
|
||||
distance.y = distance.y * 0.0001;
|
||||
}
|
||||
else {
|
||||
distance = vec3_mulf(distance, 8);
|
||||
}
|
||||
|
||||
// Do we need to be rescued?
|
||||
if (vec3_len(distance) > 8000) {
|
||||
self->update_func = ship_player_update_rescue;
|
||||
self->update_timer = UPDATE_TIME_RESCUE;
|
||||
flags_add(self->flags, SHIP_IN_RESCUE | SHIP_FLYING);
|
||||
|
||||
section_t *landing = self->section->prev;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
landing = landing->prev;
|
||||
}
|
||||
for (int i = 0; i < 10 && flags_not(landing->flags, SECTION_JUMP); i++) {
|
||||
landing = landing->next;
|
||||
}
|
||||
self->section = landing;
|
||||
self->temp_target = vec3_mulf(vec3_add(landing->center, landing->next->center), 0.5);
|
||||
self->temp_target.y -= 2000;
|
||||
self->velocity = vec3(0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
float brake = (self->brake_left + self->brake_right);
|
||||
float resistance = (self->resistance * (SHIP_MAX_RESISTANCE - (brake * 0.125))) * 0.0078125;
|
||||
|
||||
vec3_t force = vec3(0, SHIP_FLYING_GRAVITY, 0);
|
||||
force = vec3_add(force, self->thrust);
|
||||
|
||||
self->acceleration = vec3_divf(vec3_sub(forward_velocity, self->velocity), SHIP_MIN_RESISTANCE + brake * 4);
|
||||
self->acceleration = vec3_add(self->acceleration, vec3_divf(force, self->mass));
|
||||
self->acceleration = vec3_sub(self->acceleration, vec3_divf(self->velocity, resistance));
|
||||
|
||||
self->angular_acceleration.x += NTSC_ACCELERATION(ANGLE_NORM_TO_RADIAN(FIXED_TO_FLOAT(-50.0/16.0)));
|
||||
}
|
||||
|
||||
// Position
|
||||
self->velocity = vec3_add(self->velocity, vec3_mulf(self->acceleration, 30 * system_tick()));
|
||||
self->position = vec3_add(self->position, vec3_mulf(self->velocity, 0.015625 * 30 * system_tick()));
|
||||
|
||||
self->angular_acceleration.x -= self->angular_velocity.x * 0.25 * 30;
|
||||
self->angular_acceleration.z += (self->angular_velocity.y - 0.5 * self->angular_velocity.z) * 30;
|
||||
|
||||
|
||||
// Orientation
|
||||
if (self->angular_acceleration.y == 0) {
|
||||
if (self->angular_velocity.y > 0) {
|
||||
self->angular_acceleration.y -= min(self->turn_rate, self->angular_velocity.y / system_tick());
|
||||
}
|
||||
else if (self->angular_velocity.y < 0) {
|
||||
self->angular_acceleration.y += min(self->turn_rate, -self->angular_velocity.y / system_tick());
|
||||
}
|
||||
}
|
||||
|
||||
self->angular_velocity = vec3_add(self->angular_velocity, vec3_mulf(self->angular_acceleration, system_tick()));
|
||||
self->angular_velocity.y = clamp(self->angular_velocity.y, -self->turn_rate_max, self->turn_rate_max);
|
||||
|
||||
float brake_dir = (self->brake_left - self->brake_right) * (0.125 / 4096.0);
|
||||
self->angle.y += brake_dir * self->speed * 0.000030517578125 * M_PI * 2 * 30 * system_tick();
|
||||
|
||||
self->angle = vec3_add(self->angle, vec3_mulf(self->angular_velocity, system_tick()));
|
||||
self->angle.z -= self->angle.z * 0.125 * 30 * system_tick();
|
||||
self->angle = vec3_wrap_angle(self->angle);
|
||||
|
||||
// Prevent ship from going past the landing position of a SECTION_JUMP if going backwards.
|
||||
if (flags_not(self->flags, SHIP_DIRECTION_FORWARD) && flags_is(self->section->prev->flags, SECTION_JUMP)) {
|
||||
vec3_t repulse = vec3_sub(self->section->next->center, self->section->center);
|
||||
self->velocity = vec3_add(self->velocity, vec3_mulf(repulse, 2));
|
||||
}
|
||||
|
||||
ship_player_update_sfx(self);
|
||||
}
|
||||
|
||||
|
||||
void ship_player_update_rescue(ship_t *self) {
|
||||
|
||||
section_t *next = self->section->next;
|
||||
|
||||
if (flags_is(self->flags, SHIP_IN_TOW)) {
|
||||
self->temp_target = vec3_add(self->temp_target, vec3_mulf(vec3_sub(next->center, self->temp_target), 0.0078125));
|
||||
self->velocity = vec3_sub(self->temp_target, self->position);
|
||||
vec3_t target_dir = vec3_sub(next->center, self->section->center);
|
||||
|
||||
self->angular_velocity.y = wrap_angle(-atan2(target_dir.x, target_dir.z) - self->angle.y) * (1.0/16.0) * 30;
|
||||
self->angle.y = wrap_angle(self->angle.y + self->angular_velocity.y * system_tick());
|
||||
}
|
||||
|
||||
self->angle.x -= self->angle.x * 0.125 * 30 * system_tick();
|
||||
self->angle.z -= self->angle.z * 0.03125 * 30 * system_tick();
|
||||
|
||||
self->velocity = vec3_sub(self->velocity, vec3_mulf(self->velocity, 0.0625 * 30 * system_tick()));
|
||||
self->position = vec3_add(self->position, vec3_mulf(self->velocity, 0.03125 * 30 * system_tick()));
|
||||
|
||||
|
||||
// Are we done being rescued?
|
||||
float distance = vec3_len(vec3_sub(self->position, self->temp_target));
|
||||
if (flags_is(self->flags, SHIP_IN_TOW) && distance < 800) {
|
||||
self->update_func = ship_player_update_race;
|
||||
self->update_timer = 0;
|
||||
flags_rm(self->flags, SHIP_IN_RESCUE);
|
||||
flags_rm(self->flags, SHIP_VIEW_REMOTE);
|
||||
|
||||
if (flags_is(self->flags, SHIP_VIEW_INTERNAL)) {
|
||||
g.camera.update_func = camera_update_race_internal;
|
||||
}
|
||||
else {
|
||||
g.camera.update_func = camera_update_race_external;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ship_t *ship_player_find_target(ship_t *self) {
|
||||
int shortest_distance = 256;
|
||||
ship_t *nearest_ship = NULL;
|
||||
|
||||
for (int i = 0; i < len(g.ships); i++) {
|
||||
ship_t *other = &g.ships[i];
|
||||
if (self == other) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We are on a branch
|
||||
if (flags_is(self->section->flags, SECTION_JUNCTION)) {
|
||||
// Other ship is on same branch
|
||||
if (other->section->flags & SECTION_JUNCTION) {
|
||||
int distance = other->section->num - self->section->num;
|
||||
|
||||
if (distance < shortest_distance && distance > 0) {
|
||||
shortest_distance = distance;
|
||||
nearest_ship = other;
|
||||
}
|
||||
}
|
||||
|
||||
// Other ship is not on branch
|
||||
else {
|
||||
section_t *section = self->section;
|
||||
for (int distance = 0; distance < 10; distance++) {
|
||||
section = section->next;
|
||||
if (other->section == section && distance < shortest_distance && distance > 0) {
|
||||
shortest_distance = distance;
|
||||
nearest_ship = other;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We are not on a branch
|
||||
else {
|
||||
// Other ship is on a branch - check if we can reach the other ship's section
|
||||
if (flags_is(other->section->flags, SECTION_JUNCTION)) {
|
||||
section_t *section = self->section;
|
||||
for (int distance = 0; distance < 10; distance++) {
|
||||
if (section->junction) {
|
||||
section = section->junction;
|
||||
}
|
||||
else {
|
||||
section = section->next;
|
||||
}
|
||||
if (other->section == section && distance < shortest_distance && distance > 0) {
|
||||
shortest_distance = distance;
|
||||
nearest_ship = other;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Other ship is not on a branch
|
||||
else {
|
||||
int distance = other->section->num - self->section->num;
|
||||
|
||||
if (distance < shortest_distance && distance > 0) {
|
||||
shortest_distance = distance;
|
||||
nearest_ship = other;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shortest_distance < 10) {
|
||||
return nearest_ship;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef SHIP_PLAYER_H
|
||||
#define SHIP_PLAYER_H
|
||||
|
||||
#include "ship.h"
|
||||
|
||||
#define UPDATE_TIME_RESCUE (500.0 * (1.0/30.0))
|
||||
#define UPDATE_TIME_STALL (90.0 * (1.0/30.0))
|
||||
|
||||
void ship_player_update_intro(ship_t *self);
|
||||
void ship_player_update_intro_await_three(ship_t *self);
|
||||
void ship_player_update_intro_await_two(ship_t *self);
|
||||
void ship_player_update_intro_await_one(ship_t *self);
|
||||
void ship_player_update_intro_await_go(ship_t *self);
|
||||
void ship_player_update_intro_general(ship_t *self);
|
||||
void ship_player_update_race(ship_t *self);
|
||||
void ship_player_update_rescue(ship_t *self);
|
||||
|
||||
ship_t *ship_player_find_target(ship_t *self);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "../system.h"
|
||||
#include "../input.h"
|
||||
#include "../utils.h"
|
||||
|
||||
#include "title.h"
|
||||
#include "ui.h"
|
||||
#include "image.h"
|
||||
#include "game.h"
|
||||
|
||||
static uint16_t title_image;
|
||||
static float start_time;
|
||||
static bool has_shown_attract = false;
|
||||
|
||||
void title_init() {
|
||||
title_image = image_get_texture("wipeout/textures/wiptitle.tim");
|
||||
start_time = system_time();
|
||||
sfx_music_mode(SFX_MUSIC_RANDOM);
|
||||
}
|
||||
|
||||
void title_update() {
|
||||
render_set_view_2d();
|
||||
render_push_2d(vec2i(0, 0), render_size(), rgba(128, 128, 128, 255), title_image);
|
||||
ui_draw_text_centered("PRESS ENTER", ui_scaled_pos(UI_POS_BOTTOM | UI_POS_CENTER, vec2i(0, -40)), UI_SIZE_8, UI_COLOR_DEFAULT);
|
||||
|
||||
|
||||
if (input_pressed(A_MENU_SELECT) || input_pressed(A_MENU_START)) {
|
||||
sfx_play(SFX_MENU_SELECT);
|
||||
game_set_scene(GAME_SCENE_MAIN_MENU);
|
||||
}
|
||||
|
||||
float duration = system_time() - start_time;
|
||||
if (
|
||||
(has_shown_attract && duration > 5) ||
|
||||
(duration > 10)
|
||||
) {
|
||||
sfx_music_mode(SFX_MUSIC_RANDOM);
|
||||
has_shown_attract = true;
|
||||
g.is_attract_mode = true;
|
||||
g.pilot = rand_int(0, len(def.pilots));
|
||||
g.circut = rand_int(0, NUM_NON_BONUS_CIRCUTS);
|
||||
g.race_class = rand_int(0, NUM_RACE_CLASSES);
|
||||
g.race_type = RACE_TYPE_SINGLE;
|
||||
game_set_scene(GAME_SCENE_RACE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef TITLE_H
|
||||
#define TITLE_H
|
||||
|
||||
void title_init();
|
||||
void title_update();
|
||||
void title_cleanup();
|
||||
|
||||
#endif
|
||||
Executable
+387
@@ -0,0 +1,387 @@
|
||||
#include "../mem.h"
|
||||
#include "../utils.h"
|
||||
#include "../render.h"
|
||||
#include "../system.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++;
|
||||
}
|
||||
|
||||
error_if(g.track.pickups_len > TRACK_PICKUPS_MAX-1, "Track %s exceeds TRACK_PICKUPS_MAX", base_path);
|
||||
}
|
||||
}
|
||||
|
||||
ttf_t *track_load_tile_format(char *ttf_name) {
|
||||
uint32_t ttf_size;
|
||||
uint8_t *ttf_bytes = file_load(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 = file_load(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 = file_load(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 = {.as_uint32 = get_i32_le(bytes, &p) | 0xff000000};
|
||||
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 = file_load(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
|
||||
|
||||
for (int j = 0; j < 4; j++) {
|
||||
ts->high[j] = get_i16(bytes, &p);
|
||||
}
|
||||
for (int j = 0; j < 4; j++) {
|
||||
ts->med[j] = get_i16(bytes, &p);
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
float max_dist_sq = RENDER_FADEOUT_FAR * RENDER_FADEOUT_FAR;
|
||||
vec3_t cam_pos = camera->position;
|
||||
|
||||
section_t *s = g.track.sections;
|
||||
section_t *j = NULL;
|
||||
do {
|
||||
vec3_t d = vec3_sub(cam_pos, s->center);
|
||||
float dist_sq = d.x * d.x + d.y * d.y + d.z * d.z;
|
||||
if (dist_sq < max_dist_sq) {
|
||||
track_draw_section(s);
|
||||
}
|
||||
|
||||
if (s->junction) { // start junction
|
||||
j = s->junction;
|
||||
do {
|
||||
vec3_t d = vec3_sub(cam_pos, j->center);
|
||||
float dist_sq = d.x * d.x + d.y * d.y + d.z * d.z;
|
||||
if (dist_sq < max_dist_sq) {
|
||||
track_draw_section(j);
|
||||
}
|
||||
j = j->next;
|
||||
} while (!j->junction); // end junction
|
||||
}
|
||||
s = s->next;
|
||||
} while (s != g.track.sections);
|
||||
|
||||
}
|
||||
|
||||
void track_cycle_pickups() {
|
||||
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;
|
||||
}
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#ifndef TRACK_H
|
||||
#define TRACK_H
|
||||
|
||||
|
||||
#include "../types.h"
|
||||
#include "object.h"
|
||||
#include "image.h"
|
||||
|
||||
#define TRACK_VERSION 8
|
||||
|
||||
#define TRACK_VERTS_MAX 4096
|
||||
#define TRACK_FACES_MAX 3072
|
||||
#define TRACK_SECTIONS_MAX 1024
|
||||
#define TRACK_PICKUPS_MAX 64
|
||||
|
||||
#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 high[4];
|
||||
int16_t med[4];
|
||||
|
||||
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();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,213 @@
|
||||
#include "../render.h"
|
||||
#include "../utils.h"
|
||||
|
||||
#include "ui.h"
|
||||
#include "image.h"
|
||||
|
||||
typedef struct {
|
||||
vec2i_t offset;
|
||||
uint16_t width;
|
||||
} glyph_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t texture;
|
||||
uint16_t height;
|
||||
glyph_t glyphs[40];
|
||||
} char_set_t;
|
||||
|
||||
int ui_scale = 2;
|
||||
|
||||
char_set_t char_set[UI_SIZE_MAX] = {
|
||||
[UI_SIZE_16] = {
|
||||
.texture = 0,
|
||||
.height = 16,
|
||||
.glyphs = {
|
||||
{{ 0, 0}, 25}, {{ 25, 0}, 24}, {{ 49, 0}, 17}, {{ 66, 0}, 24}, {{ 90, 0}, 24}, {{114, 0}, 17}, {{131, 0}, 25}, {{156, 0}, 18},
|
||||
{{174, 0}, 7}, {{181, 0}, 17}, {{ 0, 16}, 17}, {{ 17, 16}, 17}, {{ 34, 16}, 28}, {{ 62, 16}, 17}, {{ 79, 16}, 24}, {{103, 16}, 24},
|
||||
{{127, 16}, 26}, {{153, 16}, 24}, {{177, 16}, 18}, {{195, 16}, 17}, {{ 0, 32}, 17}, {{ 17, 32}, 17}, {{ 34, 32}, 29}, {{ 63, 32}, 24},
|
||||
{{ 87, 32}, 17}, {{104, 32}, 18}, {{122, 32}, 24}, {{146, 32}, 10}, {{156, 32}, 18}, {{174, 32}, 17}, {{191, 32}, 18}, {{ 0, 48}, 18},
|
||||
{{ 18, 48}, 18}, {{ 36, 48}, 18}, {{ 54, 48}, 22}, {{ 76, 48}, 25}, {{101, 48}, 7}, {{108, 48}, 7}, {{198, 0}, 0}, {{198, 0}, 0}
|
||||
}
|
||||
},
|
||||
[UI_SIZE_12] = {
|
||||
.texture = 0,
|
||||
.height = 12,
|
||||
.glyphs = {
|
||||
{{ 0, 0}, 19}, {{ 19, 0}, 19}, {{ 38, 0}, 14}, {{ 52, 0}, 19}, {{ 71, 0}, 19}, {{ 90, 0}, 13}, {{103, 0}, 19}, {{122, 0}, 14},
|
||||
{{136, 0}, 6}, {{142, 0}, 13}, {{155, 0}, 14}, {{169, 0}, 14}, {{ 0, 12}, 22}, {{ 22, 12}, 14}, {{ 36, 12}, 19}, {{ 55, 12}, 18},
|
||||
{{ 73, 12}, 20}, {{ 93, 12}, 19}, {{112, 12}, 15}, {{127, 12}, 14}, {{141, 12}, 13}, {{154, 12}, 13}, {{167, 12}, 22}, {{ 0, 24}, 19},
|
||||
{{ 19, 24}, 13}, {{ 32, 24}, 14}, {{ 46, 24}, 19}, {{ 65, 24}, 8}, {{ 73, 24}, 15}, {{ 88, 24}, 13}, {{101, 24}, 14}, {{115, 24}, 15},
|
||||
{{130, 24}, 14}, {{144, 24}, 15}, {{159, 24}, 18}, {{177, 24}, 19}, {{196, 24}, 5}, {{201, 24}, 5}, {{183, 0}, 0}, {{183, 0}, 0}
|
||||
}
|
||||
},
|
||||
[UI_SIZE_8] = {
|
||||
.texture = 0,
|
||||
.height = 8,
|
||||
.glyphs = {
|
||||
{{ 0, 0}, 13}, {{ 13, 0}, 13}, {{ 26, 0}, 10}, {{ 36, 0}, 13}, {{ 49, 0}, 13}, {{ 62, 0}, 9}, {{ 71, 0}, 13}, {{ 84, 0}, 10},
|
||||
{{ 94, 0}, 4}, {{ 98, 0}, 9}, {{107, 0}, 10}, {{117, 0}, 10}, {{127, 0}, 16}, {{143, 0}, 10}, {{153, 0}, 13}, {{166, 0}, 13},
|
||||
{{179, 0}, 14}, {{ 0, 8}, 13}, {{ 13, 8}, 10}, {{ 23, 8}, 9}, {{ 32, 8}, 9}, {{ 41, 8}, 9}, {{ 50, 8}, 16}, {{ 66, 8}, 14},
|
||||
{{ 80, 8}, 9}, {{ 89, 8}, 10}, {{ 99, 8}, 13}, {{112, 8}, 6}, {{118, 8}, 11}, {{129, 8}, 10}, {{139, 8}, 10}, {{149, 8}, 11},
|
||||
{{160, 8}, 10}, {{170, 8}, 10}, {{180, 8}, 12}, {{192, 8}, 14}, {{206, 8}, 4}, {{210, 8}, 4}, {{193, 0}, 0}, {{193, 0}, 0}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
uint16_t icon_textures[UI_ICON_MAX];
|
||||
|
||||
void ui_load() {
|
||||
texture_list_t tl = image_get_compressed_textures("wipeout/textures/drfonts.cmp");
|
||||
char_set[UI_SIZE_16].texture = texture_from_list(tl, 0);
|
||||
char_set[UI_SIZE_12].texture = texture_from_list(tl, 1);
|
||||
char_set[UI_SIZE_8 ].texture = texture_from_list(tl, 2);
|
||||
icon_textures[UI_ICON_HAND] = texture_from_list(tl, 3);
|
||||
icon_textures[UI_ICON_CONFIRM] = texture_from_list(tl, 5);
|
||||
icon_textures[UI_ICON_CANCEL] = texture_from_list(tl, 6);
|
||||
icon_textures[UI_ICON_END] = texture_from_list(tl, 7);
|
||||
icon_textures[UI_ICON_DEL] = texture_from_list(tl, 8);
|
||||
icon_textures[UI_ICON_STAR] = texture_from_list(tl, 9);
|
||||
}
|
||||
|
||||
int ui_get_scale() {
|
||||
return ui_scale;
|
||||
}
|
||||
|
||||
void ui_set_scale(int scale) {
|
||||
ui_scale = scale;
|
||||
}
|
||||
|
||||
|
||||
vec2i_t ui_scaled(vec2i_t v) {
|
||||
return vec2i(v.x * ui_scale, v.y * ui_scale);
|
||||
}
|
||||
|
||||
vec2i_t ui_scaled_screen() {
|
||||
return vec2i_mulf(render_size(), ui_scale);
|
||||
}
|
||||
|
||||
vec2i_t ui_scaled_pos(ui_pos_t anchor, vec2i_t offset) {
|
||||
vec2i_t pos;
|
||||
vec2i_t screen_size = render_size();
|
||||
|
||||
if (flags_is(anchor, UI_POS_LEFT)) {
|
||||
pos.x = offset.x * ui_scale;
|
||||
}
|
||||
else if (flags_is(anchor, UI_POS_CENTER)) {
|
||||
pos.x = (screen_size.x >> 1) + offset.x * ui_scale;
|
||||
}
|
||||
else if (flags_is(anchor, UI_POS_RIGHT)) {
|
||||
pos.x = screen_size.x + offset.x * ui_scale;
|
||||
}
|
||||
|
||||
if (flags_is(anchor, UI_POS_TOP)) {
|
||||
pos.y = offset.y * ui_scale;
|
||||
}
|
||||
else if (flags_is(anchor, UI_POS_MIDDLE)) {
|
||||
pos.y = (screen_size.y >> 1) + offset.y * ui_scale;
|
||||
}
|
||||
else if (flags_is(anchor, UI_POS_BOTTOM)) {
|
||||
pos.y = screen_size.y + offset.y * ui_scale;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
#define char_to_glyph_index(C) (C >= '0' && C <= '9' ? (C - '0' + 26) : C - 'A')
|
||||
|
||||
int ui_char_width(char c, ui_text_size_t size) {
|
||||
if (c == ' ') {
|
||||
return 8;
|
||||
}
|
||||
return char_set[size].glyphs[char_to_glyph_index(c)].width;
|
||||
}
|
||||
|
||||
int ui_text_width(const char *text, ui_text_size_t size) {
|
||||
int width = 0;
|
||||
char_set_t *cs = &char_set[size];
|
||||
|
||||
for (int i = 0; text[i] != 0; i++) {
|
||||
width += text[i] != ' '
|
||||
? cs->glyphs[char_to_glyph_index(text[i])].width
|
||||
: 8;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
int ui_number_width(int num, ui_text_size_t size) {
|
||||
char text_buffer[16];
|
||||
text_buffer[15] = '\0';
|
||||
|
||||
int i;
|
||||
for (i = 14; i > 0; i--) {
|
||||
text_buffer[i] = '0' + (num % 10);
|
||||
num = num / 10;
|
||||
if (num == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ui_text_width(text_buffer + i, size);
|
||||
}
|
||||
|
||||
void ui_draw_time(float time, vec2i_t pos, ui_text_size_t size, rgba_t color) {
|
||||
int msec = time * 1000;
|
||||
int tenths = (msec / 100) % 10;
|
||||
int secs = (msec / 1000) % 60;
|
||||
int mins = msec / (60 * 1000);
|
||||
|
||||
char text_buffer[8];
|
||||
text_buffer[0] = '0' + (mins / 10) % 10;
|
||||
text_buffer[1] = '0' + mins % 10;
|
||||
text_buffer[2] = 'e'; // ":"
|
||||
text_buffer[3] = '0' + secs / 10;
|
||||
text_buffer[4] = '0' + secs % 10;
|
||||
text_buffer[5] = 'f'; // "."
|
||||
text_buffer[6] = '0' + tenths;
|
||||
text_buffer[7] = '\0';
|
||||
ui_draw_text(text_buffer, pos, size, color);
|
||||
}
|
||||
|
||||
void ui_draw_number(int num, vec2i_t pos, ui_text_size_t size, rgba_t color) {
|
||||
char text_buffer[16];
|
||||
text_buffer[15] = '\0';
|
||||
|
||||
int i;
|
||||
for (i = 14; i > 0; i--) {
|
||||
text_buffer[i] = '0' + (num % 10);
|
||||
num = num / 10;
|
||||
if (num == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ui_draw_text(text_buffer + i, pos, size, color);
|
||||
}
|
||||
|
||||
void ui_draw_text(const char *text, vec2i_t pos, ui_text_size_t size, rgba_t color) {
|
||||
char_set_t *cs = &char_set[size];
|
||||
|
||||
for (int i = 0; text[i] != 0; i++) {
|
||||
if (text[i] != ' ') {
|
||||
glyph_t *glyph = &cs->glyphs[char_to_glyph_index(text[i])];
|
||||
vec2i_t size = vec2i(glyph->width, cs->height);
|
||||
render_push_2d_tile(pos, glyph->offset, size, ui_scaled(size), color, cs->texture);
|
||||
pos.x += glyph->width * ui_scale;
|
||||
}
|
||||
else {
|
||||
pos.x += 8 * ui_scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ui_draw_image(vec2i_t pos, uint16_t texture) {
|
||||
vec2i_t scaled_size = ui_scaled(render_texture_size(texture));
|
||||
render_push_2d(pos, scaled_size, rgba(128, 128, 128, 255), texture);
|
||||
}
|
||||
|
||||
void ui_draw_icon(ui_icon_type_t icon, vec2i_t pos, rgba_t color) {
|
||||
render_push_2d(pos, ui_scaled(render_texture_size(icon_textures[icon])), color, icon_textures[icon]);
|
||||
}
|
||||
|
||||
void ui_draw_text_centered(const char *text, vec2i_t pos, ui_text_size_t size, rgba_t color) {
|
||||
pos.x -= (ui_text_width(text, size) * ui_scale) >> 1;
|
||||
ui_draw_text(text, pos, size, color);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef UI_H
|
||||
#define UI_H
|
||||
|
||||
#include "../types.h"
|
||||
|
||||
typedef enum {
|
||||
UI_SIZE_16,
|
||||
UI_SIZE_12,
|
||||
UI_SIZE_8,
|
||||
UI_SIZE_MAX
|
||||
} ui_text_size_t;
|
||||
|
||||
#define UI_COLOR_ACCENT rgba(123, 98, 12, 255)
|
||||
#define UI_COLOR_DEFAULT rgba(128, 128, 128, 255)
|
||||
|
||||
typedef enum {
|
||||
UI_ICON_HAND,
|
||||
UI_ICON_CONFIRM,
|
||||
UI_ICON_CANCEL,
|
||||
UI_ICON_END,
|
||||
UI_ICON_DEL,
|
||||
UI_ICON_STAR,
|
||||
UI_ICON_MAX
|
||||
} ui_icon_type_t;
|
||||
|
||||
typedef enum {
|
||||
UI_POS_LEFT = 1 << 0,
|
||||
UI_POS_CENTER = 1 << 1,
|
||||
UI_POS_RIGHT = 1 << 2,
|
||||
UI_POS_TOP = 1 << 3,
|
||||
UI_POS_MIDDLE = 1 << 4,
|
||||
UI_POS_BOTTOM = 1 << 5,
|
||||
} ui_pos_t;
|
||||
|
||||
void ui_load();
|
||||
void ui_cleanup();
|
||||
|
||||
int ui_get_scale();
|
||||
void ui_set_scale(int scale);
|
||||
vec2i_t ui_scaled(vec2i_t v);
|
||||
vec2i_t ui_scaled_screen();
|
||||
vec2i_t ui_scaled_pos(ui_pos_t anchor, vec2i_t offset);
|
||||
|
||||
int ui_char_width(char c, ui_text_size_t size);
|
||||
int ui_text_width(const char *text, ui_text_size_t size);
|
||||
int ui_number_width(int num, ui_text_size_t size);
|
||||
|
||||
void ui_draw_text(const char *text, vec2i_t pos, ui_text_size_t size, rgba_t color);
|
||||
void ui_draw_time(float time, vec2i_t pos, ui_text_size_t size, rgba_t color);
|
||||
void ui_draw_number(int num, vec2i_t pos, ui_text_size_t size, rgba_t color);
|
||||
|
||||
void ui_draw_image(vec2i_t pos, uint16_t texture);
|
||||
void ui_draw_icon(ui_icon_type_t icon, vec2i_t pos, rgba_t color);
|
||||
void ui_draw_text_centered(const char *text, vec2i_t pos, ui_text_size_t size, rgba_t color);
|
||||
|
||||
#endif
|
||||
Executable
+689
@@ -0,0 +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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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->colour[0].as_rgba.r = 230;
|
||||
prm.gt3->colour[1].as_rgba.r = r;
|
||||
prm.gt3->colour[2].as_rgba.r = r;
|
||||
prm.gt3->colour[0].as_rgba.g = 0;
|
||||
prm.gt3->colour[1].as_rgba.g = 0x40;
|
||||
prm.gt3->colour[2].as_rgba.g = 0x40;
|
||||
prm.gt3->colour[0].as_rgba.b = 0;
|
||||
prm.gt3->colour[1].as_rgba.b = 0;
|
||||
prm.gt3->colour[2].as_rgba.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->colour[0].as_rgba.r = col0;
|
||||
poly.g3->colour[0].as_rgba.g = col0;
|
||||
poly.g3->colour[0].as_rgba.b = 255;
|
||||
poly.g3->colour[0].as_rgba.a = shield_alpha;
|
||||
|
||||
poly.g3->colour[1].as_rgba.r = col1;
|
||||
poly.g3->colour[1].as_rgba.g = col1;
|
||||
poly.g3->colour[1].as_rgba.b = 255;
|
||||
poly.g3->colour[1].as_rgba.a = shield_alpha;
|
||||
|
||||
poly.g3->colour[2].as_rgba.r = col2;
|
||||
poly.g3->colour[2].as_rgba.g = col2;
|
||||
poly.g3->colour[2].as_rgba.b = 255;
|
||||
poly.g3->colour[2].as_rgba.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->colour[0].as_rgba.r = col0;
|
||||
poly.g4->colour[0].as_rgba.g = col0;
|
||||
poly.g4->colour[0].as_rgba.b = 255;
|
||||
poly.g4->colour[0].as_rgba.a = shield_alpha;
|
||||
|
||||
poly.g4->colour[1].as_rgba.r = col1;
|
||||
poly.g4->colour[1].as_rgba.g = col1;
|
||||
poly.g4->colour[1].as_rgba.b = 255;
|
||||
poly.g4->colour[1].as_rgba.a = shield_alpha;
|
||||
|
||||
poly.g4->colour[2].as_rgba.r = col2;
|
||||
poly.g4->colour[2].as_rgba.g = col2;
|
||||
poly.g4->colour[2].as_rgba.b = 255;
|
||||
poly.g4->colour[2].as_rgba.a = shield_alpha;
|
||||
|
||||
poly.g4->colour[3].as_rgba.r = col3;
|
||||
poly.g4->colour[3].as_rgba.g = col3;
|
||||
poly.g4->colour[3].as_rgba.b = 255;
|
||||
poly.g4->colour[3].as_rgba.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);
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+51
@@ -0,0 +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 weapons_init();
|
||||
void weapons_fire(ship_t *ship, int weapon_type);
|
||||
void weapons_fire_delayed(ship_t *ship, int weapon_type);
|
||||
void weapons_update();
|
||||
void weapons_draw();
|
||||
int weapon_get_random_type(int type_class);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user