shithub: wipeout

ref: 6d241d506fbce2d0b6a69f19f83b34bdda0ac6af
dir: /src/wipeout/ship_player.c/

View raw version
#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;
	}
}