diff --git a/code/ai/ai_flags.h b/code/ai/ai_flags.h index a5debd21f9e..1708f7bd066 100644 --- a/code/ai/ai_flags.h +++ b/code/ai/ai_flags.h @@ -174,6 +174,8 @@ namespace AI { Unify_usage_ai_shield_manage_delay, Disable_ai_transferring_energy, Freespace_1_missile_behavior, + ETS_uses_power_output, + ETS_energy_same_regardless_of_system_presence, NUM_VALUES }; diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index cc7ecd26625..14767416d53 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -703,6 +703,11 @@ void parse_ai_profiles_tbl(const char *filename) set_flag(profile, "$enable freespace 1 style missile behavior:", AI::Profile_Flags::Freespace_1_missile_behavior); + set_flag(profile, "$ETS uses ship class power output:", AI::Profile_Flags::ETS_uses_power_output); + + set_flag(profile, "$ETS energy same regardless of system presence:", AI::Profile_Flags::ETS_energy_same_regardless_of_system_presence); + + // end of options ---------------------------------------- // if we've been through once already and are at the same place, force a move diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 0bc1b046186..a291f7cae7e 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -2097,7 +2097,7 @@ float get_wing_lowest_av_ab_speed(object *objp) } else { - recharge_scale = Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level]; + recharge_scale = ets_power_factor(objp) * Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level]; recharge_scale = sip->afterburner_recover_rate * recharge_scale / (sip->afterburner_burn_rate + sip->afterburner_recover_rate * recharge_scale); lowest_max_av_ab_speed = recharge_scale * (objp->phys_info.afterburner_max_vel.xyz.z - objp->phys_info.max_vel.xyz.z) + objp->phys_info.max_vel.xyz.z; } @@ -2125,7 +2125,7 @@ float get_wing_lowest_av_ab_speed(object *objp) } else { - recharge_scale = Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level]; + recharge_scale = ets_power_factor(o) * Energy_levels[oshipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level]; recharge_scale = osip->afterburner_recover_rate * recharge_scale / (osip->afterburner_burn_rate + osip->afterburner_recover_rate * recharge_scale); cur_max = recharge_scale * (o->phys_info.afterburner_max_vel.xyz.z - o->phys_info.max_vel.xyz.z) + o->phys_info.max_vel.xyz.z; } @@ -4796,7 +4796,7 @@ void ai_fly_to_target_position(const vec3d* target_pos, bool* pl_done_p=NULL, bo max_allowed_speed = 0.9f * get_wing_lowest_max_speed(Pl_objp); max_allowed_ab_speed = 0.95f * get_wing_lowest_av_ab_speed(Pl_objp); if (ab_allowed) { - float self_ab_scale = Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level]; + float self_ab_scale = ets_power_factor(Pl_objp) * Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level]; self_ab_scale = sip->afterburner_recover_rate * self_ab_scale / (sip->afterburner_burn_rate + sip->afterburner_recover_rate * self_ab_scale); self_ab_speed = 0.95f * (self_ab_scale * (Pl_objp->phys_info.afterburner_max_vel.xyz.z - Pl_objp->phys_info.max_vel.xyz.z) + Pl_objp->phys_info.max_vel.xyz.z); } diff --git a/code/hud/hudets.cpp b/code/hud/hudets.cpp index 2a850040b6d..e97dc28a88e 100644 --- a/code/hud/hudets.cpp +++ b/code/hud/hudets.cpp @@ -25,7 +25,7 @@ #include "weapon/weapon.h" #include "globalincs/alphacolors.h" -float Energy_levels[NUM_ENERGY_LEVELS] = {0.0f, 0.0833f, 0.167f, 0.25f, 0.333f, 0.417f, 0.5f, 0.583f, 0.667f, 0.75f, 0.833f, 0.9167f, 1.0f}; +float Energy_levels[NUM_ENERGY_LEVELS] = {0.0f, 1.0f/12, 2.0f/12, 3.0f/12, 4.0f/12, 5.0f/12, 6.0f/12, 7.0f/12, 8.0f/12, 9.0f/12, 10.0f/12, 11.0f/12, 1.0f}; bool Weapon_energy_cheat = false; // ------------------------------------------------------------------------------------------------- @@ -54,6 +54,53 @@ void ets_init_ship(object* obj) set_default_recharge_rates(obj); } +int ets_properties(object* objp) +{ + int properties = 0; + ship* ship_p = &Ships[objp->instance]; + ship_info* ship_info_p = &Ship_info[ship_p->ship_info_index]; + + if (ship_has_energy_weapons(ship_p)) + properties |= HAS_WEAPONS; + + if (!(objp->flags[Object::Object_Flags::No_shields]) && !ship_info_p->flags[Ship::Info_Flags::Intrinsic_no_shields]) + properties |= HAS_SHIELDS; + + if (ship_has_engine_power(ship_p)) + properties |= HAS_ENGINES; + + return properties; +} + +// returns the energy that should be dedicated towards a single ETS system +// in retail, this is always 1.0 +float ets_power_factor(object *objp, bool include_power_output) +{ + auto shipp = &Ships[objp->instance]; + int properties = ets_properties(objp); + + if (The_mission.ai_profile->flags[AI::Profile_Flags::ETS_energy_same_regardless_of_system_presence] && (properties != (HAS_WEAPONS | HAS_SHIELDS | HAS_ENGINES))) + { + // in retail, the effect of having a missing system (e.g. an unshielded ship) is as if all that energy were redirected to other systems, so take the inverse of that + constexpr float missing_single_factor = 2.0f/3; + constexpr float missing_double_factor = 1.0f/3; + + float missing_factor = (properties == HAS_WEAPONS || properties == HAS_SHIELDS || properties == HAS_ENGINES) ? missing_double_factor : missing_single_factor; + + if (The_mission.ai_profile->flags[AI::Profile_Flags::ETS_uses_power_output] && include_power_output) + return Ship_info[shipp->ship_info_index].power_output * missing_factor; + else + return missing_factor; + } + else + { + if (The_mission.ai_profile->flags[AI::Profile_Flags::ETS_uses_power_output] && include_power_output) + return Ship_info[shipp->ship_info_index].power_output; + else + return 1.0f; + } +} + // ------------------------------------------------------------------------------------------------- // update_ets() is called once per frame for every OBJ_SHIP in the game. // The amount of energy to send to the weapons and shields is calculated. @@ -83,14 +130,15 @@ void update_ets(object* objp, float fl_frametime) return; } + // See? Volition did, at one point, intend for power output to affect ETS! // new_energy = fl_frametime * sinfo_p->power_output; // update weapon energy max_new_weapon_energy = fl_frametime * ship_p->max_weapon_regen_per_second * max_g; if ( objp->flags[Object::Object_Flags::Player_ship] ) { - ship_p->weapon_energy += Energy_levels[ship_p->weapon_recharge_index] * max_new_weapon_energy * The_mission.ai_profile->weapon_energy_scale[Game_skill_level]; + ship_p->weapon_energy += ets_power_factor(objp) * Energy_levels[ship_p->weapon_recharge_index] * max_new_weapon_energy * The_mission.ai_profile->weapon_energy_scale[Game_skill_level]; } else { - ship_p->weapon_energy += Energy_levels[ship_p->weapon_recharge_index] * max_new_weapon_energy; + ship_p->weapon_energy += ets_power_factor(objp) * Energy_levels[ship_p->weapon_recharge_index] * max_new_weapon_energy; } if ( ship_p->weapon_energy > sinfo_p->max_weapon_reserve ){ @@ -100,9 +148,9 @@ void update_ets(object* objp, float fl_frametime) float shield_delta; max_new_shield_energy = fl_frametime * ship_p->max_shield_regen_per_second * shield_get_max_strength(ship_p, true); // recharge rate is unaffected by $Max Shield Recharge if ( objp->flags[Object::Object_Flags::Player_ship] ) { - shield_delta = Energy_levels[ship_p->shield_recharge_index] * max_new_shield_energy * The_mission.ai_profile->shield_energy_scale[Game_skill_level]; + shield_delta = ets_power_factor(objp) * Energy_levels[ship_p->shield_recharge_index] * max_new_shield_energy * The_mission.ai_profile->shield_energy_scale[Game_skill_level]; } else { - shield_delta = Energy_levels[ship_p->shield_recharge_index] * max_new_shield_energy; + shield_delta = ets_power_factor(objp) * Energy_levels[ship_p->shield_recharge_index] * max_new_shield_energy; } if (Missiontime - Ai_info[ship_p->ai_index].last_hit_time < fl2f(sinfo_p->shield_regen_hit_delay)) @@ -170,16 +218,32 @@ void update_ets(object* objp, float fl_frametime) float ets_get_max_speed(object* objp, float engine_energy) { + // NOTE: ets_power_factor() doesn't need to be called in this function, because all the factors cancel out. But + // the system presence does need to be checked since it affects the recharge indexes. + Assertion(objp != NULL, "Invalid object pointer passed!"); Assertion(objp->type == OBJ_SHIP, "Object needs to be a ship object!"); Assertion(engine_energy >= 0.0f && engine_energy <= 1.0f, "Invalid float passed, needs to be in [0, 1], was %f!", engine_energy); ship* shipp = &Ships[objp->instance]; - ship_info* sip = &Ship_info[shipp->ship_info_index]; + float initial_engine_recharge_energy_level; + if (The_mission.ai_profile->flags[AI::Profile_Flags::ETS_energy_same_regardless_of_system_presence]) + { + int properties = ets_properties(objp); + if (properties == (HAS_WEAPONS | HAS_SHIELDS | HAS_ENGINES)) + initial_engine_recharge_energy_level = Energy_levels[INTIAL_ENGINE_RECHARGE_INDEX]; + else if (properties == HAS_WEAPONS || properties == HAS_SHIELDS || properties == HAS_ENGINES) + initial_engine_recharge_energy_level = Energy_levels[ALL_INDEX]; + else + initial_engine_recharge_energy_level = Energy_levels[ONE_HALF_INDEX]; + } + else + initial_engine_recharge_energy_level = Energy_levels[INTIAL_ENGINE_RECHARGE_INDEX]; + // check for a shortcuts first before doing linear interpolation - if ( engine_energy == Energy_levels[INTIAL_ENGINE_RECHARGE_INDEX] ){ + if ( engine_energy == initial_engine_recharge_energy_level ){ return sip->max_speed; } else if ( engine_energy == 0.0f ){ return 0.5f * sip->max_speed; @@ -188,11 +252,11 @@ float ets_get_max_speed(object* objp, float engine_energy) } else { // do a linear interpolation to find the current max speed, using points (0,1/2 default_max_speed) (.333,default_max_speed) // x = x1 + (y-y1) * (x2-x1) / (y2-y1); - if ( engine_energy < Energy_levels[INTIAL_ENGINE_RECHARGE_INDEX] ){ - return 0.5f*sip->max_speed + (engine_energy * (0.5f*sip->max_speed) ) / Energy_levels[INTIAL_ENGINE_RECHARGE_INDEX]; + if ( engine_energy < initial_engine_recharge_energy_level ){ + return 0.5f*sip->max_speed + (engine_energy * (0.5f*sip->max_speed) ) / initial_engine_recharge_energy_level; } else { // do a linear interpolation to find the current max speed, using points (.333,default_max_speed) (1,max_overclock_speed) - return sip->max_speed + (engine_energy - Energy_levels[INTIAL_ENGINE_RECHARGE_INDEX]) * (sip->max_overclocked_speed - sip->max_speed) / (1.0f - Energy_levels[INTIAL_ENGINE_RECHARGE_INDEX]); + return sip->max_speed + (engine_energy - initial_engine_recharge_energy_level) * (sip->max_overclocked_speed - sip->max_speed) / (1.0f - initial_engine_recharge_energy_level); } } } @@ -203,6 +267,7 @@ void ets_update_max_speed(object* ship_objp) Assertion(ship_objp->type == OBJ_SHIP, "Object needs to be a ship object!"); // calculate the top speed of the ship based on the energy flow to engines + // (note: this doesn't need the power factor; see comments in ets_get_max_speed()) float x = Energy_levels[Ships[ship_objp->instance].engine_recharge_index]; ship_objp->phys_info.max_vel.xyz.z = ets_get_max_speed(ship_objp, x); } @@ -335,23 +400,13 @@ void set_recharge_rates(object* obj, int shields, int weapons, int engines) { // engines to their default levels void set_default_recharge_rates(object* obj) { - int ship_properties; - ship* ship_p = &Ships[obj->instance]; ship_info* ship_info_p = &Ship_info[ship_p->ship_info_index]; if ( ship_info_p->power_output == 0 ) return; - ship_properties = 0; - if (ship_has_energy_weapons(ship_p)) - ship_properties |= HAS_WEAPONS; - - if (!(obj->flags[Object::Object_Flags::No_shields]) && !ship_info_p->flags[Ship::Info_Flags::Intrinsic_no_shields]) - ship_properties |= HAS_SHIELDS; - - if (ship_has_engine_power(ship_p)) - ship_properties |= HAS_ENGINES; + int ship_properties = ets_properties(obj); // the default charge rate depends on what systems are on each ship switch ( ship_properties ) { diff --git a/code/hud/hudets.h b/code/hud/hudets.h index 9320e7a8675..c49c9c46c94 100644 --- a/code/hud/hudets.h +++ b/code/hud/hudets.h @@ -42,6 +42,8 @@ enum SYSTEM_TYPE {WEAPONS, SHIELDS, ENGINES}; void update_ets(object* obj, float fl_frametime); void ets_init_ship(object* obj); +int ets_properties(object* objp); +float ets_power_factor(object* objp, bool include_power_output = true); void ai_manage_ets(object* obj); void increase_recharge_rate(object* obj, SYSTEM_TYPE enum_value); diff --git a/code/model/animation/modelanimation_driver.cpp b/code/model/animation/modelanimation_driver.cpp index f7978d05d75..bf59530689c 100644 --- a/code/model/animation/modelanimation_driver.cpp +++ b/code/model/animation/modelanimation_driver.cpp @@ -111,7 +111,7 @@ namespace animation { int objnum = get_pmi_objnum(pmi); Assertion(objnum >= 0, "Invalid object used in animation property driver!"); Assertion(Objects[objnum].type == OBJ_SHIP, "Non-ship object used in ship animation property driver!"); - return Energy_levels[Ships[Objects[objnum].instance].*ets_property]; + return ets_power_factor(&Objects[objnum], false) * Energy_levels[Ships[Objects[objnum].instance].*ets_property]; } std::function parse_ship_property_driver_source() { diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 446c29f4ee5..c6f4bfcec49 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -20516,7 +20516,7 @@ int sexp_gse_recharge_pct(int node, int op_num) return SEXP_NAN_FOREVER; // recharge pct - return (int)(100.0f * Energy_levels[index]); + return (int)(100.0f * ets_power_factor(ship_entry->objp(), false) * Energy_levels[index]); } /* diff --git a/code/ship/afterburner.cpp b/code/ship/afterburner.cpp index b69b1658593..2860e61769f 100644 --- a/code/ship/afterburner.cpp +++ b/code/ship/afterburner.cpp @@ -235,7 +235,7 @@ void afterburners_update(object *objp, float fl_frametime) if ( shipp->afterburner_fuel < sip->afterburner_fuel_capacity ) { float recharge_scale; - recharge_scale = Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level]; + recharge_scale = ets_power_factor(objp) * Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level]; shipp->afterburner_fuel += (sip->afterburner_recover_rate * fl_frametime * recharge_scale); if ( shipp->afterburner_fuel > sip->afterburner_fuel_capacity){