723 lines
22 KiB
C++
723 lines
22 KiB
C++
#include "afterburner.h"
|
|
#include <azur/azur.h>
|
|
#include <azur/gint/render.h>
|
|
#include <gint/drivers/r61524.h>
|
|
#include <gint/keyboard.h>
|
|
#include <gint/usb.h>
|
|
#include <gint/usb-ff-bulk.h>
|
|
#include <gint/cpu.h>
|
|
#include <gint/timer.h>
|
|
#include <gint/kmalloc.h>
|
|
#include <gint/rtc.h>
|
|
#include <libprof.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <fxlibc/printf.h>
|
|
|
|
static char logbuf[512];
|
|
|
|
GUNUSED static void logger(char const *fmt, ...)
|
|
{
|
|
if(!usb_is_open())
|
|
return;
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
|
|
vsnprintf(logbuf, sizeof logbuf, fmt, args);
|
|
va_end(args);
|
|
|
|
usb_fxlink_text(logbuf, 0);
|
|
}
|
|
|
|
#define RGB24(hex) \
|
|
(((hex & 0xf80000) >> 8) | \
|
|
((hex & 0x00fc00) >> 5) | \
|
|
((hex & 0x0000f8) >> 3))
|
|
|
|
/* Generate the gradient used for the horizon. This is an array of 512 colors,
|
|
256 of which represent the ground, while the other 256 represent the sky. */
|
|
static void generate_horizon_gradient(uint16_t *horizon_colors)
|
|
{
|
|
int hc = 0;
|
|
|
|
for(int i = 0; i < 256; i++) {
|
|
horizon_colors[hc++] = RGB24(0x438747);
|
|
}
|
|
for(int i = 0; i < 32; i++) {
|
|
int r = 0x68 + (0x3f - 0x68) * i / 32;
|
|
int g = 0x8c + (0x66 - 0x8c) * i / 32;
|
|
int b = 0xd6 + (0xb3 - 0xd6) * i / 32;
|
|
horizon_colors[hc++] = RGB24(((r << 16) | (g << 8) | b));
|
|
}
|
|
for(int i = 0; i < 64; i++) {
|
|
horizon_colors[hc++] = RGB24(0x3f66b3);
|
|
};
|
|
for(int i = 0; i < 64; i++) {
|
|
int r = 0x3f + (0x0c - 0x3f) * i / 64;
|
|
int g = 0x66 + (0x29 - 0x66) * i / 64;
|
|
int b = 0xb3 + (0x61 - 0xb3) * i / 64;
|
|
horizon_colors[hc++] = RGB24(((r << 16) | (g << 8) | b));
|
|
}
|
|
for(int i = 0; i < 96; i++) {
|
|
horizon_colors[hc++] = RGB24(0x0c2961);
|
|
}
|
|
}
|
|
|
|
/* A plane controller that individually controls each direction.
|
|
* Up/Down: move forward/backward (including pitch)
|
|
* Left/Right: move left/right (ignoring roll; parallel to the ground)
|
|
* SHIFT+Up/Down: pitch down/up
|
|
* SHIFT+Left/Right: turn left/right (yaw)
|
|
* ALPHA+Up/Down: move forward/backward (ignoring pitch)
|
|
* ALPHA+Left/Right: roll */
|
|
GUNUSED static void controller_debug(struct plane *plane, num dt)
|
|
{
|
|
int sh = keydown(KEY_SHIFT);
|
|
int al = keydown(KEY_ALPHA);
|
|
|
|
float dt_f = (float)dt;
|
|
|
|
/* Movement speed for movement controlled here */
|
|
num debug_speed = 6.0;
|
|
|
|
if(keydown(KEY_UP) && !sh && !al)
|
|
plane->pos += plane->forward * debug_speed * dt;
|
|
if(keydown(KEY_DOWN) && !sh && !al)
|
|
plane->pos -= plane->forward * debug_speed * dt;
|
|
if(keydown(KEY_RIGHT) && !sh && !al)
|
|
plane->pos += plane->right * debug_speed * dt;
|
|
if(keydown(KEY_LEFT) && !sh && !al)
|
|
plane->pos -= plane->right * debug_speed * dt;
|
|
|
|
if(keydown(KEY_UP) && sh && !al)
|
|
plane->pitch -= 0.3 * dt_f;
|
|
if(keydown(KEY_DOWN) && sh && !al)
|
|
plane->pitch += 0.3 * dt_f;
|
|
if(keydown(KEY_RIGHT) && sh && !al)
|
|
plane->yaw -= 0.5 * dt_f;
|
|
if(keydown(KEY_LEFT) && sh && !al)
|
|
plane->yaw += 0.5 * dt_f;
|
|
|
|
if(keydown(KEY_UP) && !sh && al)
|
|
plane->pos += plane->forward_ground * debug_speed * dt;
|
|
if(keydown(KEY_DOWN) && !sh && al)
|
|
plane->pos -= plane->forward_ground * debug_speed * dt;
|
|
if(keydown(KEY_LEFT) && !sh && al)
|
|
plane->roll += 0.45 * dt_f;
|
|
if(keydown(KEY_RIGHT) && !sh && al)
|
|
plane->roll -= 0.45 * dt_f;
|
|
|
|
if(keydown(KEY_MINUS))
|
|
plane->pos.z -= 2.0 * dt;
|
|
if(keydown(KEY_PLUS))
|
|
plane->pos.z += 2.0 * dt;
|
|
}
|
|
|
|
/* A simple plane controller for players.
|
|
* Up/Down: pitch down/up
|
|
* Left/Right: turn left/right (yaw)
|
|
* SHIFT/ALPHA: accelerate/decelerate
|
|
TODO: Automatic roll effect when turning */
|
|
static void controller_simple(struct plane *plane, num dt)
|
|
{
|
|
float dt_f = (float)dt;
|
|
|
|
if(keydown(KEY_UP))
|
|
plane->pitch -= 0.3 * dt_f;
|
|
if(keydown(KEY_DOWN))
|
|
plane->pitch += 0.3 * dt_f;
|
|
|
|
if(keydown(KEY_RIGHT))
|
|
plane->yaw -= 0.5 * dt_f;
|
|
if(keydown(KEY_LEFT))
|
|
plane->yaw += 0.5 * dt_f;
|
|
|
|
if(keydown(KEY_SHIFT))
|
|
plane->air_speed += num(4.0) * dt;
|
|
if(keydown(KEY_ALPHA))
|
|
plane->air_speed -= num(4.0) * dt;
|
|
|
|
/* Lock roll to player turning, for more immersion and visual effects
|
|
while keeping very simple controls */
|
|
float target_roll = 0;
|
|
if(keydown(KEY_LEFT))
|
|
target_roll = 0.5;
|
|
if(keydown(KEY_RIGHT))
|
|
target_roll = -0.5;
|
|
|
|
if(plane->roll < target_roll) {
|
|
plane->roll += 0.3 * dt_f;
|
|
if(plane->roll > target_roll)
|
|
plane->roll = target_roll;
|
|
}
|
|
if(plane->roll > target_roll) {
|
|
plane->roll -= 0.3 * dt_f;
|
|
if(plane->roll < target_roll)
|
|
plane->roll = target_roll;
|
|
}
|
|
}
|
|
|
|
/* Sort to have closer objects last */
|
|
static int compare_depth(void const *p1, void const *p2)
|
|
{
|
|
struct ref_point const *u = (struct ref_point const *)p1;
|
|
struct ref_point const *v = (struct ref_point const *)p2;
|
|
/* Return v < u */
|
|
return v->pos.y.v - u->pos.y.v;
|
|
}
|
|
|
|
#define remap(point, _x, _y) { \
|
|
*(_x) = wx + (int)((point).x) * ww / world->w; \
|
|
*(_y) = wy + wh-1 - (int)((point).y) * wh / world->h; \
|
|
}
|
|
|
|
/* Generate the minimap as a bopti image (Azur can't easily draw like in the
|
|
VRAM, and I don't have time to write a full shader for that) */
|
|
static bopti_image_t *generate_minimap(struct world const *world,
|
|
struct plane const *plane, struct objective *objectives,
|
|
int objective_count)
|
|
{
|
|
extern bopti_image_t img_minimap;
|
|
|
|
/* Duplicate the empty minimap (which is full RGB565) */
|
|
image_t *img = image_copy_alloc(&img_minimap, IMAGE_RGB565A);
|
|
|
|
/* Location of world map within image */
|
|
int wx=9, wy=9, ww=30, wh=30, x, y;
|
|
|
|
for(int i = 0; i < objective_count; i++) {
|
|
remap(objectives[i].pos, &x, &y);
|
|
int color = objectives[i].destroyed ? C_RGB(15,3,31) : C_RGB(31,2,5);
|
|
image_set_pixel(img, x, y, color);
|
|
}
|
|
|
|
for(int i = 0; i < 32; i++) {
|
|
remap(plane->pos + num(i) * plane->forward, &x, &y);
|
|
image_set_pixel(img, x, y, C_RGB(0, 18, 4));
|
|
}
|
|
|
|
remap(plane->pos, &x, &y);
|
|
image_set_pixel(img, x, y, C_RGB(0, 31, 10));
|
|
|
|
return img;
|
|
}
|
|
|
|
/* Draw player's plane at (x,y) */
|
|
static void draw_player(int x, int y, struct plane *plane,
|
|
bool orient_with_angles, bool orient_with_keyboard)
|
|
{
|
|
/* Rows: 25 (anchor 17), 27 (anchor 20), 26 (anchor 21)
|
|
Columns: all 48 (anchor 23) */
|
|
extern bopti_image_t img_plane;
|
|
|
|
int row = 1, col = 1;
|
|
if(orient_with_angles) {
|
|
if(plane->pitch > 0.08)
|
|
row = 0;
|
|
if(plane->pitch < -0.08)
|
|
row = 2;
|
|
if(plane->roll > 0.1)
|
|
col = 0;
|
|
if(plane->roll < -0.1)
|
|
col = 2;
|
|
}
|
|
if(orient_with_keyboard) {
|
|
if(keydown(KEY_DOWN))
|
|
row = 0;
|
|
if(keydown(KEY_UP))
|
|
row = 2;
|
|
if(keydown(KEY_LEFT))
|
|
col = 0;
|
|
if(keydown(KEY_RIGHT))
|
|
col = 2;
|
|
}
|
|
|
|
int top=0, height=0, ay=0;
|
|
if(row == 0) top = 0, height = 25, ay = 17;
|
|
if(row == 1) top = 25, height = 27, ay = 20;
|
|
if(row == 2) top = 52, height = 26, ay = 21;
|
|
|
|
azrp_subimage(x-23, y-ay, &img_plane, 48 * col, top, 48, height,
|
|
DIMAGE_NONE);
|
|
}
|
|
|
|
/* Distance between two points, with wrap-around */
|
|
num64 distance2_wrap_64(struct world const *world, vec3 u, vec3 v)
|
|
{
|
|
num dx = u.x - v.x;
|
|
num dy = u.y - v.y;
|
|
num dz = u.z - v.z;
|
|
|
|
/* Choose the closest x distance possible */
|
|
if(dx < num(-world->w))
|
|
dx += num(world->w);
|
|
if(dx > num(world->w))
|
|
dx -= num(world->w);
|
|
/* Choose the closest y distance possible */
|
|
if(dy < num(-world->h))
|
|
dy += num(world->h);
|
|
if(dy > num(world->h))
|
|
dy -= num(world->h);
|
|
|
|
return num32::dmul(dx, dx) + num32::dmul(dy, dy) + num32::dmul(dz, dz);
|
|
}
|
|
|
|
/* Render text with Azur images - quite bad, but I don't have time lol. */
|
|
static void draw_text(int x, int y, char const *fmt, ...)
|
|
{
|
|
char str[128];
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vsnprintf(str, 128, fmt, args);
|
|
va_end(args);
|
|
|
|
extern bopti_image_t img_font;
|
|
|
|
for(int i = 0; str[i]; i++) {
|
|
if(str[i] < 32 || str[i] >= 0x7f) continue;
|
|
|
|
int row = (str[i] - 32) >> 4;
|
|
int col = (str[i] - 32) & 15;
|
|
azrp_subimage(x + 5 * i, y, &img_font, 7 * col + 1, 9 * row + 1, 6, 8,
|
|
DIMAGE_NONE);
|
|
}
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
azrp_config_scale(2);
|
|
azrp_shader_clear_configure();
|
|
azrp_shader_image_rgb16_configure();
|
|
azrp_shader_image_p8_configure();
|
|
azrp_shader_image_p4_configure();
|
|
ab_horizon_configure();
|
|
|
|
__printf_enable_fp();
|
|
|
|
srand(rtc_ticks());
|
|
|
|
prof_init();
|
|
|
|
usb_interface_t const *intf[] = { &usb_ff_bulk, NULL };
|
|
usb_open(intf, GINT_CALL_NULL);
|
|
|
|
int volatile timer_flag = 1;
|
|
int timer = timer_configure(TIMER_ANY, 33000, GINT_CALL_SET(&timer_flag));
|
|
timer_start(timer);
|
|
|
|
bool lost = false;
|
|
|
|
//---
|
|
// Horizon colors
|
|
//---
|
|
|
|
uint16_t horizon_colors[512];
|
|
generate_horizon_gradient(horizon_colors);
|
|
|
|
//---
|
|
// World map
|
|
//---
|
|
|
|
/* Stuff starts to blow up at the density of 1/16 sprite/unit² (4096
|
|
sprites for a 256x256 world): even with chunk-based selection, we end up
|
|
with > 2000 points to consider each frame, which makes the 3D transform
|
|
blow up to 16-20 ms per frame. That's the obvious next optimization. */
|
|
int point_count = 2048;
|
|
struct ref_point *points = (struct ref_point *)
|
|
malloc(point_count * sizeof *points);
|
|
|
|
/* We give 12 bases to destroy */
|
|
struct objective objectives[12];
|
|
int objective_count = 12;
|
|
|
|
/* We allow 3 explosions at the same time */
|
|
struct explosion explosions[3];
|
|
int explosion_count = 3;
|
|
|
|
for(int i = 0; i < explosion_count; i++) {
|
|
explosions[i].pos = vec3();
|
|
explosions[i].time = num(-1);
|
|
}
|
|
|
|
for(int i = 0; i < point_count; i++) {
|
|
num x, y;
|
|
x.v = rand() % num(256).v;
|
|
y.v = rand() % num(256).v;
|
|
points[i].pos = vec3(x, y, 0);
|
|
|
|
if(i < objective_count) {
|
|
objectives[i].pos = points[i].pos;
|
|
objectives[i].destroyed = false;
|
|
objectives[i].type = rand() % 3;
|
|
points[i].id = 100 + i;
|
|
}
|
|
else {
|
|
points[i].id = rand() % 6;
|
|
}
|
|
}
|
|
|
|
struct world *world = world_make(256, 256, points, point_count);
|
|
if(!world) return 0;
|
|
|
|
//---
|
|
// Cached explosion images
|
|
//---
|
|
|
|
prof_t perf_scaling;
|
|
|
|
/* Unlike normal sprites, we don't rotate exlosions, so we can keep them
|
|
cached for the whole execution */
|
|
extern bopti_image_t img_expl_1, img_expl_2, img_expl_3, img_expl_4;
|
|
DynamicImage dyn_expl_1(&img_expl_1, 0.0, &perf_scaling, 24, 24);
|
|
DynamicImage dyn_expl_2(&img_expl_2, 0.0, &perf_scaling, 24, 24);
|
|
DynamicImage dyn_expl_3(&img_expl_3, 0.0, &perf_scaling, 24, 24);
|
|
DynamicImage dyn_expl_4(&img_expl_4, 0.0, &perf_scaling, 31, 31);
|
|
|
|
DynamicImage *dyn_expl[4] = {
|
|
&dyn_expl_1, &dyn_expl_2, &dyn_expl_3, &dyn_expl_4,
|
|
};
|
|
|
|
//---
|
|
// HUD and messages
|
|
//---
|
|
|
|
char const *message = NULL;
|
|
num message_time = num(0.0);
|
|
|
|
//---
|
|
// Camera
|
|
//---
|
|
|
|
struct plane plane;
|
|
plane.pos = vec3(world->w / 2, world->h / 2, 5);
|
|
plane.roll = 0.0f;
|
|
plane.pitch = 0.0f;
|
|
plane.yaw = 0.0f;
|
|
plane.air_speed = 12.0;
|
|
|
|
int plane_screen_x = 198/2;
|
|
int plane_screen_y = 112/2;
|
|
|
|
while(1) {
|
|
/* Wait until a new frame to cap at the desired FPS */
|
|
while(!timer_flag) sleep();
|
|
|
|
//--- Total frame time
|
|
prof_t perf_frame = prof_make();
|
|
prof_enter(perf_frame);
|
|
|
|
/* Time difference for this frame (assumes consistant FPS) */
|
|
num dt = 33e-3;
|
|
|
|
//--- Physics/modeling time (split in two parts)
|
|
prof_t perf_physics = prof_make();
|
|
prof_enter(perf_physics);
|
|
|
|
plane_update(&plane);
|
|
vec3 camera_pos = plane.pos - num(10) * plane.forward;
|
|
|
|
/* Compute horizon reference */
|
|
struct ref_point far_forward;
|
|
far_forward.pos = plane.pos + plane.forward * num(FAR_CLIP_PLANE);
|
|
far_forward.pos.z = 0;
|
|
transform_world2camera(&plane, camera_pos, &far_forward, 1);
|
|
transform_camera2screen(&far_forward, 1);
|
|
|
|
int visible_explosions = 0;
|
|
for(int i = 0; i < explosion_count; i++)
|
|
visible_explosions += (explosions[i].time >= num(0));
|
|
|
|
/* Gather world points in chunks close to the plane */
|
|
int sprite_count;
|
|
struct ref_point *sprites = world_select(world, plane.pos,
|
|
plane.forward, &sprite_count, 1 + visible_explosions);
|
|
|
|
/* Extra sprite for the target under the plane */
|
|
sprites[sprite_count].pos =
|
|
plane.pos + num(BOMB_DISTANCE) * plane.forward;
|
|
sprites[sprite_count].pos.z = 0;
|
|
sprites[sprite_count].id = -1;
|
|
sprite_count++;
|
|
|
|
/* Extra sprites for visible explosions */
|
|
for(int i = 0; i < explosion_count; i++) {
|
|
if(explosions[i].time < num(0))
|
|
continue;
|
|
|
|
int n = (int)(explosions[i].time * 6) % 4;
|
|
sprites[sprite_count].pos = explosions[i].pos;
|
|
sprites[sprite_count].id = 9 + n;
|
|
sprite_count++;
|
|
}
|
|
|
|
transform_world2camera(&plane, camera_pos, sprites, sprite_count);
|
|
|
|
/* Prune sprites that are definitely not visible */
|
|
int close_point_count = 0;
|
|
for(int i = 0; i < sprite_count; i++) {
|
|
if(sprites[i].pos.y < num(NEAR_CLIP_PLANE) ||
|
|
sprites[i].pos.y >= num(FAR_CLIP_PLANE) ||
|
|
abs((int)sprites[i].pos.x) >= SIDE_CLIP_PLANE)
|
|
continue;
|
|
sprites[close_point_count++] = sprites[i];
|
|
}
|
|
|
|
transform_camera2screen(sprites, close_point_count);
|
|
|
|
/* Prune points that are also too far awar from the screen */
|
|
int visible_point_count = 0;
|
|
for(int i = 0; i < close_point_count; i++) {
|
|
int x = (int)sprites[i].pos.x;
|
|
int z = (int)sprites[i].pos.z;
|
|
if(x < -SCREEN_MARGIN || x >= azrp_width + SCREEN_MARGIN
|
|
|| z < -SCREEN_MARGIN || z >= azrp_height + SCREEN_MARGIN)
|
|
continue;
|
|
sprites[visible_point_count++] = sprites[i];
|
|
}
|
|
|
|
/* Sort sprites furthest to closest */
|
|
qsort(sprites, visible_point_count, sizeof *sprites, compare_depth);
|
|
|
|
prof_leave(perf_physics);
|
|
|
|
extern bopti_image_t img_dot;
|
|
extern bopti_image_t img_tree_1, img_tree_2, img_tree_3;
|
|
extern bopti_image_t img_mountain_1;
|
|
extern bopti_image_t img_rock_1, img_rock_2;
|
|
extern bopti_image_t img_base_1, img_base_2, img_base_3;
|
|
|
|
/* Resize images for all visible sprites */
|
|
perf_scaling = prof_make();
|
|
|
|
DynamicImage dyn_tree_1(&img_tree_1,
|
|
-plane.roll, &perf_scaling, 16, 31);
|
|
DynamicImage dyn_tree_2(&img_tree_2,
|
|
-plane.roll, &perf_scaling, 16, 31);
|
|
DynamicImage dyn_tree_3(&img_tree_3,
|
|
-plane.roll, &perf_scaling, 16, 31);
|
|
DynamicImage dyn_mountain_1(&img_mountain_1,
|
|
-plane.roll, &perf_scaling, 24, 34);
|
|
DynamicImage dyn_rock_1(&img_rock_1,
|
|
-plane.roll, &perf_scaling, 15, 20);
|
|
DynamicImage dyn_rock_2(&img_rock_2,
|
|
-plane.roll, &perf_scaling, 11, 15);
|
|
DynamicImage dyn_base_1(&img_base_1,
|
|
-plane.roll, &perf_scaling, 16, 20);
|
|
DynamicImage dyn_base_2(&img_base_2,
|
|
-plane.roll, &perf_scaling, 16, 20);
|
|
DynamicImage dyn_base_3(&img_base_3,
|
|
-plane.roll, &perf_scaling, 16, 20);
|
|
|
|
DynamicImage *dyn_images[9] = {
|
|
&dyn_tree_1, &dyn_tree_2, &dyn_tree_3,
|
|
&dyn_mountain_1,
|
|
&dyn_rock_1, &dyn_rock_2,
|
|
&dyn_base_1, &dyn_base_2, &dyn_base_3,
|
|
};
|
|
|
|
/* Generate the minimap */
|
|
image_t *minimap = generate_minimap(world, &plane, objectives,
|
|
objective_count);
|
|
|
|
//---
|
|
|
|
azrp_perf_clear();
|
|
ab_horizon((int)far_forward.pos.x, (int)far_forward.pos.z, -plane.roll,
|
|
horizon_colors);
|
|
|
|
for(int i = 0; i < visible_point_count; i++) {
|
|
int ax, ay;
|
|
|
|
num scale = 15.0;
|
|
/* Make targeted buildings larger */
|
|
if(sprites[i].id >= 6 && sprites[i].id < 9)
|
|
scale = 25.0;
|
|
scale = scale / sprites[i].pos.y;
|
|
|
|
bopti_image_t *img = NULL;
|
|
if(sprites[i].id == -1) {
|
|
img = &img_dot;
|
|
ax = 2;
|
|
ay = 2;
|
|
}
|
|
else if(sprites[i].id < 6) {
|
|
img = dyn_images[sprites[i].id]->getAtScale(scale,&ax,&ay);
|
|
}
|
|
else if(sprites[i].id >= 9 && sprites[i].id < 13) {
|
|
img = dyn_expl[sprites[i].id - 9]->getAtScale(scale, &ax, &ay);
|
|
}
|
|
else if(sprites[i].id >= 100) {
|
|
struct objective *o = &objectives[sprites[i].id - 100];
|
|
if(!o->destroyed)
|
|
img = dyn_images[6+o->type]->getAtScale(scale,&ax,&ay);
|
|
}
|
|
|
|
if(img) azrp_image((int)sprites[i].pos.x - ax,
|
|
(int)sprites[i].pos.z - ay, img);
|
|
}
|
|
if(!lost)
|
|
draw_player(plane_screen_x, plane_screen_y, &plane, false, !lost);
|
|
if(message)
|
|
draw_text(2, 2, "%s", message);
|
|
else
|
|
draw_text(2, 2, "Speed:%d", (int)(plane.air_speed * 40));
|
|
azrp_image(azrp_width - minimap->width - 2, 2, minimap);
|
|
|
|
/* Shows a dot the forward direction on the ground */
|
|
// azrp_image((int)far_forward.x-2, (int)far_forward.z-2, &img_dot);
|
|
azrp_update();
|
|
|
|
free(sprites);
|
|
image_free(minimap);
|
|
|
|
//---
|
|
// Event handling and physics
|
|
//---
|
|
|
|
bool explosion_trigger = false;
|
|
key_event_t ev;
|
|
while((ev = pollevent()).type != KEYEV_NONE) {
|
|
if(ev.type == KEYEV_DOWN && ev.key == KEY_F1)
|
|
explosion_trigger = true;
|
|
}
|
|
|
|
clearevents();
|
|
if(keydown(KEY_MENU) || keydown(KEY_EXIT))
|
|
break;
|
|
|
|
prof_enter(perf_physics);
|
|
|
|
if(!lost)
|
|
controller_simple(&plane, dt);
|
|
|
|
/* Limit pitch to reasonable ranges (extreme values cause graphical
|
|
glitches with the horizon, and accentuate the lens problem */
|
|
if(plane.pitch < -0.2)
|
|
plane.pitch = -0.2;
|
|
if(plane.pitch > 0.2)
|
|
plane.pitch = 0.2;
|
|
|
|
/* Limit air speed to "reasonable" defaults */
|
|
if(plane.air_speed < num(6.0))
|
|
plane.air_speed = 6.0;
|
|
if(plane.air_speed > num(24.0))
|
|
plane.air_speed = 24.0;
|
|
|
|
if(!lost)
|
|
plane.pos += plane.air_speed * dt * plane.forward;
|
|
|
|
/* Wrap the plane around the world map */
|
|
if((int)plane.pos.x < 0)
|
|
plane.pos.x += world->w;
|
|
if((int)plane.pos.x >= world->w)
|
|
plane.pos.x -= world->w;
|
|
if((int)plane.pos.y < 0)
|
|
plane.pos.y += world->h;
|
|
if((int)plane.pos.y >= world->h)
|
|
plane.pos.y -= world->h;
|
|
|
|
/* Add new explosions */
|
|
if(!lost && explosion_trigger) {
|
|
int id = -1;
|
|
for(int i = 0; i < explosion_count; i++) {
|
|
if(explosions[i].time < num(0)) {
|
|
id = i;
|
|
break;
|
|
}
|
|
}
|
|
if(id >= 0) {
|
|
explosions[id].pos =
|
|
plane.pos + num(BOMB_DISTANCE) * plane.forward;
|
|
explosions[id].pos.z = 0;
|
|
explosions[id].time = 0.0;
|
|
|
|
bool hit_someone = false;
|
|
|
|
/* Destroy bases that are close! */
|
|
for(int i = 0; i < objective_count; i++) {
|
|
if(distance2_64(objectives[i].pos,
|
|
explosions[id].pos) < num64(20)) {
|
|
objectives[i].destroyed = true;
|
|
hit_someone = true;
|
|
}
|
|
}
|
|
|
|
message = hit_someone ? "Hit!" : "Missed!";
|
|
message_time = num(2.0);
|
|
}
|
|
}
|
|
|
|
/* Maintain the time of current explosions */
|
|
for(int i = 0; i < explosion_count; i++) {
|
|
if(explosions[i].time >= num(0)) {
|
|
explosions[i].time += dt;
|
|
if(explosions[i].time >= num(4.0/6.0))
|
|
explosions[i].time = -1;
|
|
}
|
|
}
|
|
|
|
/* Maintain the message time */
|
|
if(message != NULL) {
|
|
message_time -= dt;
|
|
if(message_time < num(0)) {
|
|
message = NULL;
|
|
message_time = num(0.0);
|
|
}
|
|
}
|
|
|
|
prof_leave(perf_physics);
|
|
prof_leave(perf_frame);
|
|
#if 0
|
|
logger("Frame total: %d µs\n - Rendering: update %d µs, shaders %d µs"
|
|
"\n - Physics: %d µs\n - Sprite scaling: %d µs\n"
|
|
"Current info:\n Position: x=%g y=%g z=%g\n Angles: roll=%g "
|
|
"pitch=%g yaw=%g\n Air speed: %g\n"
|
|
"Points: %d selected, %d close, %d visible\n"
|
|
"Explosions: %d\n",
|
|
prof_time(perf_frame),
|
|
prof_time(azrp_perf_render),
|
|
prof_time(azrp_perf_shaders),
|
|
prof_time(perf_physics),
|
|
prof_time(perf_scaling),
|
|
(double)plane.pos.x,
|
|
(double)plane.pos.y,
|
|
(double)plane.pos.z,
|
|
(double)plane.roll,
|
|
(double)plane.pitch,
|
|
(double)plane.yaw,
|
|
(double)plane.air_speed,
|
|
sprite_count,
|
|
close_point_count,
|
|
visible_point_count,
|
|
visible_explosions);
|
|
#endif
|
|
|
|
/* Victory condition */
|
|
bool all_objectives_destroyed = true;
|
|
for(int i = 0; i < objective_count; i++)
|
|
all_objectives_destroyed &= objectives[i].destroyed;
|
|
|
|
if(all_objectives_destroyed) {
|
|
message = "Mission accomplished!";
|
|
message_time = num(32000);
|
|
}
|
|
|
|
/* Death condition */
|
|
if(!lost && plane.pos.z <= num(0.5)) {
|
|
message = "Unfortunate.";
|
|
message_time = num(32000);
|
|
lost = true;
|
|
|
|
/* Hijack one of the explosions */
|
|
explosions[0].pos = plane.pos;
|
|
explosions[0].time = num(0.0);
|
|
}
|
|
}
|
|
|
|
timer_stop(timer);
|
|
return 1;
|
|
}
|