shithub: wipeout

ref: 7cfaff0c7b618e8150bd97ad43fca9899f4a5514
dir: /src/wipeout/ship_ai.c/

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