diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index efae89f7b0b..9cb03ea009a 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -6775,20 +6775,26 @@ bool check_los(int objnum, int target_objnum, float threshold, int primary_bank, bool is_primary = secondary_bank == -1; ship *shipp = &Ships[firing_ship->instance]; ship_weapon *swp = &shipp->weapons; + ship_info *sip = &Ship_info[shipp->ship_info_index]; weapon_info *wip = &Weapon_info[is_primary ? swp->primary_bank_weapons[primary_bank] : swp->secondary_bank_weapons[secondary_bank]]; - polymodel* pm = model_get(Ship_info[shipp->ship_info_index].model_num); - + polymodel* pm = model_get(sip->model_num); + vec3d pnt = is_primary ? pm->gun_banks[primary_bank].pnt[swp->primary_next_slot[primary_bank]] : pm->missile_banks[secondary_bank].pnt[swp->secondary_next_slot[secondary_bank]]; vec3d firing_point; + // external model firing points only apply when the external models are actually drawn + // (matching ship_fire_primary and ship_fire_secondary) + bool draw_models = is_primary ? sip->draw_primary_models[primary_bank] : sip->draw_secondary_models[secondary_bank]; polymodel* weapon_model = nullptr; - if (wip->external_model_num >= 0) { + if (draw_models && wip->external_model_num >= 0) { weapon_model = model_get(wip->external_model_num); } // use the same firing point the next shot will use - int external_bank = is_primary ? primary_bank : secondary_bank + MAX_SHIP_PRIMARY_BANKS; - vec3d external_fp_offset = ship_get_external_model_fp_offset(swp, wip, weapon_model, external_bank, false); + auto ext = is_primary ? &swp->primary_bank_external_weapon[primary_bank] : &swp->secondary_bank_external_weapon[secondary_bank]; + auto ship_bank = is_primary ? &pm->gun_banks[primary_bank] : &pm->missile_banks[secondary_bank]; + int slot = is_primary ? swp->primary_next_slot[primary_bank] : swp->secondary_next_slot[secondary_bank]; + vec3d external_fp_offset = ship_get_external_model_fp_offset(ext, wip, weapon_model, ship_bank, slot, false); vm_vec_add2(&pnt, &external_fp_offset); vm_vec_unrotate(&firing_point, &pnt, &firing_ship->orient); diff --git a/code/hud/hudtarget.cpp b/code/hud/hudtarget.cpp index 08a3d985a97..01ab8b3fe40 100644 --- a/code/hud/hudtarget.cpp +++ b/code/hud/hudtarget.cpp @@ -7905,11 +7905,13 @@ void HudGaugeHardpoints::render(float /*frametime*/, bool config) auto ship_pm = model_get(sip->model_num); for (i = 0; i < swp->num_secondary_banks; i++) { - if (swp->secondary_bank_weapons[i] < 0) + if (swp->secondary_bank_weapons[i] < 0 || !sip->draw_secondary_models[i]) continue; auto wip = &Weapon_info[swp->secondary_bank_weapons[i]]; - if (wip->external_model_num < 0 || !sip->draw_secondary_models[i]) + // if the weapon has no dedicated external model, display the weapon's own model, if it has one + int display_model_num = (wip->external_model_num >= 0) ? wip->external_model_num : wip->model_num; + if (display_model_num < 0) continue; auto bank = &ship_pm->missile_banks[i]; @@ -7932,10 +7934,10 @@ void HudGaugeHardpoints::render(float /*frametime*/, bool config) matrix model_orient; vm_matrix_x_matrix(&model_orient, &object_orient, &slot_orient); - model_render_immediate(&weapon_render_info, wip->external_model_num, &model_orient, &world_position); + model_render_immediate(&weapon_render_info, display_model_num, &model_orient, &world_position); } } else { - auto weapon_pm = model_get(wip->external_model_num); + auto weapon_pm = model_get(display_model_num); num_secondaries_rendered = 0; for(k = 0; k < bank->num_slots; k++) @@ -7971,7 +7973,7 @@ void HudGaugeHardpoints::render(float /*frametime*/, bool config) matrix model_orient; vm_matrix_x_matrix(&model_orient, &object_orient, &slot_orient); - model_render_immediate(&weapon_render_info, wip->external_model_num, &model_orient, &world_position); + model_render_immediate(&weapon_render_info, display_model_num, &model_orient, &world_position); } } } @@ -7989,7 +7991,12 @@ void HudGaugeHardpoints::render(float /*frametime*/, bool config) auto bank = &ship_pm->gun_banks[i]; auto wip = (swp->primary_bank_weapons[i] >= 0) ? &Weapon_info[swp->primary_bank_weapons[i]] : nullptr; - if ( wip == nullptr || wip->external_model_num < 0 || !sip->draw_primary_models[i] ) { + // if the weapon has no dedicated external model, display the weapon's own model, if it has one + int display_model_num = -1; + if ( wip != nullptr && sip->draw_primary_models[i] ) + display_model_num = (wip->external_model_num >= 0) ? wip->external_model_num : wip->model_num; + + if ( display_model_num < 0 ) { // no model to draw, so just mark each firing point with a circle for ( k = 0; k < bank->num_slots; k++ ) { vm_vec_unrotate(&subobj_pos, &bank->pnt[k], &object_orient); @@ -8001,7 +8008,7 @@ void HudGaugeHardpoints::render(float /*frametime*/, bool config) renderCircle((int)draw_point.screen.xyw.x + position[0], (int)draw_point.screen.xyw.y + position[1], 10); } } else { - int external_model_instance = ship_get_external_weapon_model_instance(swp, i); + int external_model_instance = ship_get_external_weapon_model_instance(swp, i, display_model_num); for ( k = 0; k < bank->num_slots; k++ ) { model_render_params weapon_render_info; @@ -8020,7 +8027,7 @@ void HudGaugeHardpoints::render(float /*frametime*/, bool config) matrix model_orient; vm_matrix_x_matrix(&model_orient, &object_orient, &slot_orient); - model_render_immediate(&weapon_render_info, wip->external_model_num, external_model_instance, &model_orient, &world_position); + model_render_immediate(&weapon_render_info, display_model_num, external_model_instance, &model_orient, &world_position); } } } diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index 750e8589871..2ac589a6046 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -3699,7 +3699,9 @@ static void parse_ship_values(ship_info* sip, const bool is_template, const bool if(optional_string("$Show Primary Models:")) { sip->flags.set(Ship::Info_Flags::Draw_weapon_models); - stuff_bool_list(sip->draw_primary_models, sip->num_primary_banks); + size_t num_bools = stuff_bool_list(sip->draw_primary_models, sip->num_primary_banks); + if (num_bools != static_cast(sip->num_primary_banks)) + error_display(0, "$Show Primary Models: on ship class %s lists " SIZE_T_ARG " entries, but the ship has %d primary banks.", sip->name, num_bools, sip->num_primary_banks); } // Set the weapons filter used in weapons loadout (for secondary weapons) @@ -3712,7 +3714,9 @@ static void parse_ship_values(ship_info* sip, const bool is_template, const bool if(optional_string("$Show Secondary Models:")) { sip->flags.set(Ship::Info_Flags::Draw_weapon_models); - stuff_bool_list(sip->draw_secondary_models, sip->num_secondary_banks); + size_t num_bools = stuff_bool_list(sip->draw_secondary_models, sip->num_secondary_banks); + if (num_bools != static_cast(sip->num_secondary_banks)) + error_display(0, "$Show Secondary Models: on ship class %s lists " SIZE_T_ARG " entries, but the ship has %d secondary banks.", sip->name, num_bools, sip->num_secondary_banks); } if (optional_string("$Ship Recoil Modifier:")){ @@ -6922,9 +6926,6 @@ void ship::clear() // ---------- special weapons init that isn't setting things to 0 for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++) { - // not part of weapons! - primary_rotate_rate[i] = 0.0f; - primary_rotate_ang[i] = 0.0f; // for fighter beams was_firing_last_frame[i] = 0; } @@ -7130,8 +7131,7 @@ void ship_weapon::clear() { primary_bank_weapons[i] = -1; - primary_bank_external_model_instance[i] = -1; - primary_bank_model_instance_weapon[i] = -1; + primary_bank_external_weapon[i] = external_weapon_state(); next_primary_fire_stamp[i] = timestamp(0); last_primary_fire_stamp[i] = timestamp(-1); @@ -7153,7 +7153,6 @@ void ship_weapon::clear() burst_counter[i] = 0; burst_seed[i] = Random::next(); - external_model_fp_counter[i] = 0; primary_firepoint_indices[i].clear(); primary_firepoint_next_to_fire_index[i] = 0; @@ -7165,6 +7164,8 @@ void ship_weapon::clear() { secondary_bank_weapons[i] = -1; + secondary_bank_external_weapon[i] = external_weapon_state(); + next_secondary_fire_stamp[i] = timestamp(0); last_secondary_fire_stamp[i] = timestamp(-1); @@ -7181,7 +7182,6 @@ void ship_weapon::clear() secondary_bank_substitution_pattern_index[i] = 0; burst_counter[i + MAX_SHIP_PRIMARY_BANKS] = 0; - external_model_fp_counter[i + MAX_SHIP_PRIMARY_BANKS] = 0; } tertiary_bank_ammo = 0; @@ -8630,12 +8630,20 @@ void ship_delete( object * obj ) shipp->model_instance_num = -1; // free up any weapon model instances - for (int i = 0; i < shipp->weapons.num_primary_banks; ++i) + for (auto &ext : shipp->weapons.primary_bank_external_weapon) + { + if (ext.model_instance >= 0) + { + model_delete_instance(ext.model_instance); + ext.model_instance = -1; + } + } + for (auto &ext : shipp->weapons.secondary_bank_external_weapon) { - if (shipp->weapons.primary_bank_external_model_instance[i] >= 0) + if (ext.model_instance >= 0) { - model_delete_instance(shipp->weapons.primary_bank_external_model_instance[i]); - shipp->weapons.primary_bank_external_model_instance[i] = -1; + model_delete_instance(ext.model_instance); + ext.model_instance = -1; } } } @@ -10605,6 +10613,49 @@ void update_firing_sounds(object* objp, ship* shipp) } } +// Spins the Gun_rotation submodels of external weapon models up or down. A bank that tried to +// fire this frame requests spin-up (see ship_fire_primary, which also refuses to fire until the +// barrels are up to speed); all other banks wind down. Only primary banks spin. +void update_external_weapon_spin(ship *shipp, float frametime) +{ + ship_weapon *swp = &shipp->weapons; + + for (int i = 0; i < swp->num_primary_banks; i++) + { + auto &ext = swp->primary_bank_external_weapon[i]; + + if (swp->primary_bank_weapons[i] < 0) + { + ext.rotate_rate = 0.0f; + ext.spin_up_requested = false; + continue; + } + + auto wip = &Weapon_info[swp->primary_bank_weapons[i]]; + + if (ext.spin_up_requested && wip->weapon_submodel_rotate_vel > 0.0f) + { + ext.rotate_rate += wip->weapon_submodel_rotate_accell * frametime; + if (ext.rotate_rate > wip->weapon_submodel_rotate_vel) + ext.rotate_rate = wip->weapon_submodel_rotate_vel; + } + else if (ext.rotate_rate > 0.0f) + { + ext.rotate_rate -= wip->weapon_submodel_rotate_accell * frametime; + if (ext.rotate_rate < 0.0f) + ext.rotate_rate = 0.0f; + } + ext.spin_up_requested = false; + + if (ext.rotate_rate > 0.0f) + { + ext.rotate_ang += ext.rotate_rate * frametime; + while (ext.rotate_ang > PI2) + ext.rotate_ang -= PI2; + } + } +} + // This was previously part of obj_move_call_physics(), but secondary_point_reload_pct is only used for rendering and has nothing to do with physics at all. void update_reload_percent(ship *shipp, float frametime) { @@ -10691,6 +10742,8 @@ void ship_process_post(object * obj, float frametime) update_reload_percent(shipp, frametime); + update_external_weapon_spin(shipp, frametime); + ship_dying_frame(obj, num); ship_move_ets_transfer_buffers(shipp, obj, frametime); @@ -12579,7 +12632,6 @@ DCF(t_max, "") static int ship_stop_fire_primary_bank(object * obj, int bank_to_stop) { ship *shipp; - ship_weapon *swp; if(obj == NULL){ return 0; @@ -12590,22 +12642,9 @@ static int ship_stop_fire_primary_bank(object * obj, int bank_to_stop) } shipp = &Ships[obj->instance]; - swp = &shipp->weapons; - if(swp->primary_bank_weapons[bank_to_stop] >= 0){ - if(shipp->primary_rotate_rate[bank_to_stop] > 0.0f) - shipp->primary_rotate_rate[bank_to_stop] -= Weapon_info[swp->primary_bank_weapons[bank_to_stop]].weapon_submodel_rotate_accell*flFrametime; - if(shipp->primary_rotate_rate[bank_to_stop] < 0.0f) - shipp->primary_rotate_rate[bank_to_stop] = 0.0f; - if(Ship_info[shipp->ship_info_index].draw_primary_models[bank_to_stop]){ - shipp->primary_rotate_ang[bank_to_stop] += shipp->primary_rotate_rate[bank_to_stop]*flFrametime; - if(shipp->primary_rotate_ang[bank_to_stop] > PI2) - shipp->primary_rotate_ang[bank_to_stop] -= PI2; - if(shipp->primary_rotate_ang[bank_to_stop] < 0.0f) - shipp->primary_rotate_ang[bank_to_stop] += PI2; - } - } - + // (external weapon model spin-down is handled by update_external_weapon_spin) + if(shipp->was_firing_last_frame[bank_to_stop] == 0) return 0; @@ -12734,38 +12773,48 @@ bool in_autoaim_fov(ship *shipp, int bank_to_fire, object *obj) } -// Gets the offset, relative to the ship's firing point, of the firing point within an external -// weapon model that the next shot should be fired from. Handles the "chain external model fps" -// weapon flag: chained weapons cycle through the external model's firing points using the bank's -// chain counter; non-chained weapons use the sub_shot index directly. +// Gets the offset, in the ship model's frame, from the ship's firing point to the firing point +// within an external weapon model that the next shot should be fired from. Handles the "chain +// external model fps" weapon flag: chained weapons cycle through the external model's firing +// points using the bank's chain counter; non-chained weapons use the sub_shot index directly. +// The offset is rotated to match how the model is rendered on the slot (aligned to the slot's +// firing normal and rolled by its angle offset; see ship_get_weapon_model_slot_transform). // -// fp_counter_index indexes swp->external_model_fp_counter[]: the bank number for primaries, or -// the bank number plus MAX_SHIP_PRIMARY_BANKS for secondaries. advance_counter should be true -// when actually firing a shot, and false for queries such as the AI's line-of-sight check. -vec3d ship_get_external_model_fp_offset(ship_weapon *swp, const weapon_info *wip, const polymodel *weapon_model, int fp_counter_index, bool advance_counter, int sub_shot) +// ext is the bank's external weapon state; ship_bank and slot identify the ship firing point +// the weapon model is mounted on. advance_counter should be true when actually firing a shot, +// and false for queries such as the AI's line-of-sight check. +vec3d ship_get_external_model_fp_offset(external_weapon_state *ext, const weapon_info *wip, const polymodel *weapon_model, const w_bank *ship_bank, int slot, bool advance_counter, int sub_shot) { if (weapon_model == nullptr || weapon_model->n_guns < 1 || weapon_model->gun_banks[0].num_slots < 1) return vmd_zero_vector; auto &bank = weapon_model->gun_banks[0]; + vec3d local_offset; if (wip->wi_flags[Weapon::Info_Flags::External_weapon_fp]) { - int &fp_counter = swp->external_model_fp_counter[fp_counter_index]; - // the counter cycles through the firing points of the model's first gun bank - if (fp_counter < 0 || fp_counter >= bank.num_slots) - fp_counter = 0; + if (ext->fp_counter < 0 || ext->fp_counter >= bank.num_slots) + ext->fp_counter = 0; - int fp = fp_counter; + int fp = ext->fp_counter; if (advance_counter) - fp_counter++; + ext->fp_counter++; - return bank.pnt[fp]; + local_offset = bank.pnt[fp]; + } + else + { + Assertion(sub_shot >= 0 && sub_shot < bank.num_slots, "sub_shot %d is out of range for the external model of weapon %s, which has %d firing points", sub_shot, wip->name, bank.num_slots); + local_offset = bank.pnt[sub_shot]; } - Assertion(sub_shot >= 0 && sub_shot < bank.num_slots, "sub_shot %d is out of range for the external model of weapon %s, which has %d firing points", sub_shot, wip->name, bank.num_slots); - return bank.pnt[sub_shot]; + vec3d slot_pnt, offset; + matrix slot_orient; + ship_get_weapon_model_slot_transform(ship_bank, slot, 0.0f, &slot_pnt, &slot_orient); + vm_vec_unrotate(&offset, &local_offset, &slot_orient); + + return offset; } // fires a primary weapon for the given object. It also handles multiplayer cases. @@ -12926,18 +12975,12 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) } if (winfo_p->weapon_submodel_rotate_vel > 0.0f) { - if (shipp->primary_rotate_rate[bank_to_fire] < winfo_p->weapon_submodel_rotate_vel) - shipp->primary_rotate_rate[bank_to_fire] += winfo_p->weapon_submodel_rotate_accell*flFrametime; - if (shipp->primary_rotate_rate[bank_to_fire] > winfo_p->weapon_submodel_rotate_vel) - shipp->primary_rotate_rate[bank_to_fire] = winfo_p->weapon_submodel_rotate_vel; - if (sip->draw_primary_models[bank_to_fire]) { - shipp->primary_rotate_ang[bank_to_fire] += shipp->primary_rotate_rate[bank_to_fire]*flFrametime; - if (shipp->primary_rotate_ang[bank_to_fire] > PI2) - shipp->primary_rotate_ang[bank_to_fire] -= PI2; - if (shipp->primary_rotate_ang[bank_to_fire] < 0.0f) - shipp->primary_rotate_ang[bank_to_fire] += PI2; - } - if (shipp->primary_rotate_rate[bank_to_fire] < winfo_p->weapon_submodel_rotate_vel) + auto &ext = swp->primary_bank_external_weapon[bank_to_fire]; + + // spin up the Gun_rotation submodels (see update_external_weapon_spin), and + // don't fire until the barrels are up to speed + ext.spin_up_requested = true; + if (ext.rotate_rate < winfo_p->weapon_submodel_rotate_vel) continue; } // if this is a targeting laser, start it up ///- only targeting laser if it is tag-c, otherwise it's a fighter beam -Bobboau @@ -13402,8 +13445,9 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) firepoint_list = nullptr; } + // external model firing points only apply when the external models are actually drawn polymodel *weapon_model = nullptr; - if (sip->draw_primary_models[bank_to_fire] && (winfo_p->external_model_num >= 0)) + if (sip->draw_primary_models[bank_to_fire] && (winfo_p->external_model_num >= 0)) weapon_model = model_get(winfo_p->external_model_num); for (int pt_count = 0; pt_count < point_count; pt_count++) { @@ -13454,7 +13498,7 @@ int ship_fire_primary(object * obj, int force, bool rollback_shot) vec3d dir; dir = pm->gun_banks[bank_to_fire].norm[pt]; - vec3d external_fp_offset = ship_get_external_model_fp_offset(swp, winfo_p, weapon_model, bank_to_fire, true, s); + vec3d external_fp_offset = ship_get_external_model_fp_offset(&swp->primary_bank_external_weapon[bank_to_fire], winfo_p, weapon_model, &pm->gun_banks[bank_to_fire], pt, true, s); vm_vec_add2(&pnt, &external_fp_offset); vm_vec_unrotate(&gun_point, &pnt, &obj->orient); @@ -14308,16 +14352,19 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot ) shipp->secondary_point_reload_pct.set(bank, pnt_index, 0.0f); pnt = pm->missile_banks[bank].pnt[pnt_index]; vec3d dir; - dir = pm->missile_banks[bank].norm[pnt_index++]; + dir = pm->missile_banks[bank].norm[pnt_index]; + // external model firing points only apply when the external models are actually drawn + // (matching the primary bank behavior in ship_fire_primary) polymodel *weapon_model = nullptr; - if(wip->external_model_num >= 0){ + if(sip->draw_secondary_models[bank] && wip->external_model_num >= 0){ weapon_model = model_get(wip->external_model_num); } // chained weapons cycle through the external model's firing points; other weapons use the 0 index slot - vec3d external_fp_offset = ship_get_external_model_fp_offset(swp, wip, weapon_model, bank + MAX_SHIP_PRIMARY_BANKS, true); + vec3d external_fp_offset = ship_get_external_model_fp_offset(&swp->secondary_bank_external_weapon[bank], wip, weapon_model, &pm->missile_banks[bank], pnt_index, true); vm_vec_add2(&pnt, &external_fp_offset); + pnt_index++; vm_vec_unrotate(&missile_point, &pnt, &obj->orient); vm_vec_add(&firing_pos, &missile_point, &obj->pos); @@ -21477,55 +21524,66 @@ void ship_render_batch_thrusters(object *obj) // the last call (weapons can be swapped mid-mission by SEXPs, scripts, or rearming). The ideal // place to create the instance would be in parse_object_create_sub, but the player can alter // the ship loadout after that function runs. +// display_model_num is the model the caller renders for this bank (the weapon's external model, +// or its own model as the fallback). // Returns the model instance number, or -1 if the bank's weapon doesn't need an instance. -int ship_get_external_weapon_model_instance(ship_weapon *swp, int bank) +int ship_get_external_weapon_model_instance(ship_weapon *swp, int bank, int display_model_num) { int weapon_idx = swp->primary_bank_weapons[bank]; + auto &ext = swp->primary_bank_external_weapon[bank]; - if (swp->primary_bank_model_instance_weapon[bank] != weapon_idx) + if (ext.model_instance_weapon != weapon_idx) { // the weapon changed, so any existing instance belongs to the old weapon's model - if (swp->primary_bank_external_model_instance[bank] >= 0) + if (ext.model_instance >= 0) { - model_delete_instance(swp->primary_bank_external_model_instance[bank]); - swp->primary_bank_external_model_instance[bank] = -1; + model_delete_instance(ext.model_instance); + ext.model_instance = -1; } - if (weapon_idx >= 0 && Weapon_info[weapon_idx].external_model_num >= 0) + if (weapon_idx >= 0 && display_model_num >= 0) { - auto pm = model_get(Weapon_info[weapon_idx].external_model_num); + auto pm = model_get(display_model_num); // create a model instance only if at least one submodel has gun rotation for (int mn = 0; mn < pm->n_models; mn++) { if (pm->submodel[mn].flags[Model::Submodel_flags::Gun_rotation]) { - swp->primary_bank_external_model_instance[bank] = model_create_instance(model_objnum_special::OBJNUM_NONE, Weapon_info[weapon_idx].external_model_num); + ext.model_instance = model_create_instance(model_objnum_special::OBJNUM_NONE, display_model_num); break; } } } - swp->primary_bank_model_instance_weapon[bank] = weapon_idx; + ext.model_instance_weapon = weapon_idx; } - return swp->primary_bank_external_model_instance[bank]; + return ext.model_instance; } // Computes the position and orientation, in the ship model's frame, at which to render an -// external weapon model on slot `slot` of weapon bank `bank`. reload_slide_back is the -// distance a partially reloaded missile is slid backward along the bank's -Z axis; pass -// 0.0f for primaries and launcher-style secondaries, which don't slide. +// external weapon model on slot `slot` of weapon bank `bank`. The model points along the +// slot's firing normal and is "banked" (rolled) by the slot's angle offset. +// reload_slide_back is the distance a partially reloaded missile is slid backward along the +// firing normal; pass 0.0f for primaries and launcher-style secondaries, which don't slide. void ship_get_weapon_model_slot_transform(const w_bank *bank, int slot, float reload_slide_back, vec3d *outpnt, matrix *outorient) { + // point the model along the slot's firing direction (identity for the usual dead-ahead normal) + matrix norm_orient; + vm_vector_2_matrix_norm(&norm_orient, &bank->norm[slot]); + // "Bank" the external model by the angle offset angles angs = { 0.0f, bank->external_model_angle_offset[slot], 0.0f }; - vm_angles_2_matrix(outorient, &angs); + matrix bank_orient; + vm_angles_2_matrix(&bank_orient, &angs); + + vm_matrix_x_matrix(outorient, &norm_orient, &bank_orient); *outpnt = bank->pnt[slot]; if (reload_slide_back > 0.0f) - vm_vec_scale_add2(outpnt, &vmd_z_vector, -reload_slide_back); + vm_vec_scale_add2(outpnt, &bank->norm[slot], -reload_slide_back); } void ship_render_weapon_models(model_render_params *ship_render_info, model_draw_list *scene, object *obj) @@ -21553,16 +21611,18 @@ void ship_render_weapon_models(model_render_params *ship_render_info, model_draw //primary weapons for ( i = 0; i < swp->num_primary_banks; i++ ) { - if ( swp->primary_bank_weapons[i] < 0 ) { + if ( swp->primary_bank_weapons[i] < 0 || !sip->draw_primary_models[i] ) { continue; } auto wip = &Weapon_info[swp->primary_bank_weapons[i]]; - if ( wip->external_model_num < 0 || !sip->draw_primary_models[i] ) { + // if the weapon has no dedicated external model, display the weapon's own model, if it has one + int display_model_num = (wip->external_model_num >= 0) ? wip->external_model_num : wip->model_num; + if ( display_model_num < 0 ) { continue; } - int external_model_instance = ship_get_external_weapon_model_instance(swp, i); + int external_model_instance = ship_get_external_weapon_model_instance(swp, i, display_model_num); auto bank = &ship_pm->gun_banks[i]; @@ -21577,7 +21637,7 @@ void ship_render_weapon_models(model_render_params *ship_render_info, model_draw if (pm->submodel[mn].flags[Model::Submodel_flags::Gun_rotation]) { angles angs = vmd_zero_angles; - angs.b = shipp->primary_rotate_ang[i]; + angs.b = swp->primary_bank_external_weapon[i].rotate_ang; vm_angles_2_matrix(&pmi->submodel[mn].canonical_orient, &angs); } } @@ -21588,18 +21648,20 @@ void ship_render_weapon_models(model_render_params *ship_render_info, model_draw matrix slot_orient; ship_get_weapon_model_slot_transform(bank, k, 0.0f, &slot_pnt, &slot_orient); - model_render_queue(ship_render_info, scene, wip->external_model_num, external_model_instance, &slot_orient, &slot_pnt); + model_render_queue(ship_render_info, scene, display_model_num, external_model_instance, &slot_orient, &slot_pnt); } } //secondary weapons for (i = 0; i < swp->num_secondary_banks; i++) { - if ( swp->secondary_bank_weapons[i] < 0 ) { + if ( swp->secondary_bank_weapons[i] < 0 || !sip->draw_secondary_models[i] ) { continue; } auto wip = &Weapon_info[swp->secondary_bank_weapons[i]]; - if ( wip->external_model_num < 0 || !sip->draw_secondary_models[i] ) { + // if the weapon has no dedicated external model, display the weapon's own model, if it has one + int display_model_num = (wip->external_model_num >= 0) ? wip->external_model_num : wip->model_num; + if ( display_model_num < 0 ) { continue; } @@ -21611,10 +21673,10 @@ void ship_render_weapon_models(model_render_params *ship_render_info, model_draw matrix slot_orient; ship_get_weapon_model_slot_transform(bank, k, 0.0f, &slot_pnt, &slot_orient); - model_render_queue(ship_render_info, scene, wip->external_model_num, &slot_orient, &slot_pnt); + model_render_queue(ship_render_info, scene, display_model_num, &slot_orient, &slot_pnt); } } else { - auto weapon_pm = model_get(wip->external_model_num); + auto weapon_pm = model_get(display_model_num); int num_secondaries_rendered = 0; for ( k = 0; k < bank->num_slots; k++ ) { @@ -21633,7 +21695,7 @@ void ship_render_weapon_models(model_render_params *ship_render_info, model_draw matrix slot_orient; ship_get_weapon_model_slot_transform(bank, k, (1.0f - reload_pct) * weapon_pm->rad, &slot_pnt, &slot_orient); - model_render_queue(ship_render_info, scene, wip->external_model_num, &slot_orient, &slot_pnt); + model_render_queue(ship_render_info, scene, display_model_num, &slot_orient, &slot_pnt); } } } diff --git a/code/ship/ship.h b/code/ship/ship.h index 6f38e0eda3f..034978575c0 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -93,6 +93,18 @@ struct reinforcements { reinforcements(const char *reinforcement_name = nullptr); }; +// Per-bank state for a weapon's external model - the model rendered at the bank's firing +// points when the ship enables $Show Primary Models: / $Show Secondary Models:. +struct external_weapon_state +{ + int model_instance = -1; // model instance used to spin Gun_rotation submodels, or -1 if the weapon doesn't need one + int model_instance_weapon = -1; // the weapon the model instance state was created for, or -1 if not yet checked + int fp_counter = 0; // cycles through the model's firing points, for "chain external model fps" weapons + float rotate_rate = 0.0f; // current spin rate of the model's Gun_rotation submodels (primaries only) + float rotate_ang = 0.0f; // current spin angle of the model's Gun_rotation submodels (primaries only) + bool spin_up_requested = false; // set each frame the bank tries to fire; consumed by update_external_weapon_spin() +}; + class ship_weapon { public: int num_primary_banks; // Number of primary banks (same as model) @@ -102,8 +114,8 @@ class ship_weapon { int primary_bank_weapons[MAX_SHIP_PRIMARY_BANKS]; // Weapon_info[] index for the weapon in the bank int secondary_bank_weapons[MAX_SHIP_SECONDARY_BANKS]; // Weapon_info[] index for the weapon in the bank - int primary_bank_external_model_instance[MAX_SHIP_PRIMARY_BANKS]; - int primary_bank_model_instance_weapon[MAX_SHIP_PRIMARY_BANKS]; // the weapon the model instance was created for, or -1 if not yet checked + external_weapon_state primary_bank_external_weapon[MAX_SHIP_PRIMARY_BANKS]; + external_weapon_state secondary_bank_external_weapon[MAX_SHIP_SECONDARY_BANKS]; int current_primary_bank; // currently selected primary bank int current_secondary_bank; // currently selected secondary bank @@ -162,7 +174,6 @@ class ship_weapon { int burst_counter[MAX_SHIP_PRIMARY_BANKS + MAX_SHIP_SECONDARY_BANKS]; int burst_seed[MAX_SHIP_PRIMARY_BANKS + MAX_SHIP_SECONDARY_BANKS]; // A random seed, recalculated only when the weapon's burst resets - int external_model_fp_counter[MAX_SHIP_PRIMARY_BANKS + MAX_SHIP_SECONDARY_BANKS]; SCP_vector primary_firepoint_indices[MAX_SHIP_PRIMARY_BANKS]; // A list of firepoint indices which is shuffled for random fire ordering int primary_firepoint_next_to_fire_index[MAX_SHIP_PRIMARY_BANKS]; // For cycle firing modes, keeps track of which firepoint we're on @@ -863,8 +874,6 @@ class ship int bay_doors_parent_shipnum; // our parent ship, what we are entering/leaving reload_pct secondary_point_reload_pct; //after fireing a secondary it takes some time for that secondary weapon to reload, this is how far along in that proces it is (from 0 to 1) - float primary_rotate_rate[MAX_SHIP_PRIMARY_BANKS]; - float primary_rotate_ang[MAX_SHIP_PRIMARY_BANKS]; SCP_vector> rcs_activity; //Timestamp of when thrusters started //Sound index for thrusters @@ -1813,9 +1822,9 @@ extern void ship_actually_depart(int shipnum, int method = SHIP_DEPARTED_WARP); extern bool in_autoaim_fov(ship *shipp, int bank_to_fire, object *obj); extern int ship_stop_fire_primary(object * obj); extern int ship_fire_primary(object * objp, int force = 0, bool rollback_shot = false); -extern vec3d ship_get_external_model_fp_offset(ship_weapon *swp, const weapon_info *wip, const polymodel *weapon_model, int fp_counter_index, bool advance_counter, int sub_shot = 0); +extern vec3d ship_get_external_model_fp_offset(external_weapon_state *ext, const weapon_info *wip, const polymodel *weapon_model, const w_bank *ship_bank, int slot, bool advance_counter, int sub_shot = 0); extern void ship_get_weapon_model_slot_transform(const w_bank *bank, int slot, float reload_slide_back, vec3d *outpnt, matrix *outorient); -extern int ship_get_external_weapon_model_instance(ship_weapon *swp, int bank); +extern int ship_get_external_weapon_model_instance(ship_weapon *swp, int bank, int display_model_num); extern int ship_fire_secondary(object * objp, int allow_swarm = 0, bool rollback_shot = false ); bool ship_start_secondary_fire(object* objp); bool ship_stop_secondary_fire(object* objp); diff --git a/code/weapon/weapon.h b/code/weapon/weapon.h index d8d7fa07250..4b1ce288150 100644 --- a/code/weapon/weapon.h +++ b/code/weapon/weapon.h @@ -386,7 +386,7 @@ struct weapon_info char pofbitmap_name[MAX_FILENAME_LEN]; // Name of the pof representing this if POF, or bitmap filename if bitmap int model_num; // modelnum of weapon -- -1 if no model char external_model_name[MAX_FILENAME_LEN]; //the model rendered on the weapon points of a ship - int external_model_num; //the model rendered on the weapon points of a ship + int external_model_num; // modelnum of the model rendered on the weapon points of a ship; -1 if $External Model File: was not given std::unique_ptr tech_desc; // weapon's description (in tech database) char tech_anim_filename[MAX_FILENAME_LEN]; // weapon's tech room animation diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 7e742a42369..53a4889de7d 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -8844,9 +8844,14 @@ static void weapon_page_in_one(weapon_info *wip, bool load_graphics) if (VALID_FNAME(wip->external_model_name)) wip->external_model_num = model_load(wip->external_model_name); - if (wip->external_model_num == -1) - wip->external_model_num = wip->model_num; + if (wip->external_model_num >= 0) + { + polymodel *external_pm = model_get(wip->external_model_num); + // only the first gun bank of an external model supplies firing points + if (external_pm->n_guns > 1) + Warning(LOCATION, "External model %s of weapon %s has %d gun banks; only the firing points of the first bank are used.", wip->external_model_name, wip->name, external_pm->n_guns); + } //Load shockwaves shockwave_create_info_load(&wip->shockwave);