ref: 5fcf2feec81e753d0188bb10da9db0a5ee061957
dir: /src/wipeout/weapon.c/
#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); } }