1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
|
#include "CombatDamage.h"
#include "CombatSystem.h"
#include "../util/Logger.h"
#include "../util/GameRules.h"
#include "../universe/Condition.h"
#include "../universe/Enums.h"
#include "../universe/Fighter.h"
#include "../universe/Ship.h"
#include "../universe/ShipDesign.h"
#include "../universe/ShipPart.h"
#include "../universe/ValueRef.h"
// This knows about combat bouts, fighter launches, combatTargets conditions,
// damage calculation (shortrange, fighters, shields)
namespace {
std::vector<float> WeaponDamageCalcImpl(
const std::shared_ptr<const Ship>& ship, bool max, bool include_fighters,
bool target_ships, const ScriptingContext& context)
{
std::vector<float> retval;
if (!ship)
return retval;
const ShipDesign* design = context.ContextUniverse().GetShipDesign(ship->DesignID());
if (!design)
return retval;
const auto& parts = design->Parts();
if (parts.empty())
return retval;
MeterType METER = max ?
MeterType::METER_MAX_CAPACITY : MeterType::METER_CAPACITY;
MeterType SECONDARY_METER = max ?
MeterType::METER_MAX_SECONDARY_STAT : MeterType::METER_SECONDARY_STAT;
float fighter_damage = 0.0f;
int fighter_launch_capacity = 0;
int available_fighters = 0;
retval.reserve(parts.size() + 1);
int num_bouts = GetGameRules().Get<int>("RULE_NUM_COMBAT_ROUNDS");
// for each weapon part, get its damage meter value
for (const auto& part_name : parts) {
const ShipPart* part = GetShipPart(part_name);
if (!part)
continue;
ShipPartClass part_class = part->Class();
// get the attack power for each weapon part.
if (part_class == ShipPartClass::PC_DIRECT_WEAPON) {
if (target_ships)
retval.push_back(ship->WeaponPartShipDamage(part, context));
else
retval.push_back(ship->WeaponPartFighterDamage(part, context));
} else if (part_class == ShipPartClass::PC_FIGHTER_BAY && include_fighters) {
// launch capacity determined by capacity of bay
fighter_launch_capacity += static_cast<int>(ship->CurrentPartMeterValue(METER, part_name));
} else if (part_class == ShipPartClass::PC_FIGHTER_HANGAR && include_fighters) {
// attack strength of a ship's fighters per bout determined by the hangar...
// assuming all hangars on a ship are the same part type...
if (target_ships && part->TotalShipDamage()) {
retval.push_back(part->TotalShipDamage()->Eval(context));
// as TotalShipDamage contains the damage from all fighters, do not further include fighter
include_fighters = false;
} else if (part->TotalFighterDamage() && !target_ships) {
retval.push_back(part->TotalFighterDamage()->Eval(context));
// as TotalFighterDamage contains the damage from all fighters, do not further include fighter
include_fighters = false;
} else if (part->CombatTargets() && context.effect_target &&
part->CombatTargets()->EvalOne(context, context.effect_target))
{
fighter_damage = ship->CurrentPartMeterValue(SECONDARY_METER, part_name);
available_fighters = std::max(0, static_cast<int>(
ship->CurrentPartMeterValue(METER, part_name))); // stacked meter
} else {
// target is not of the right type; no damage; stop checking hangars/launch bays
fighter_damage = 0.0f;
include_fighters = false;
}
}
}
if (!include_fighters || fighter_damage <= 0.0f || available_fighters <= 0 || fighter_launch_capacity <= 0)
return retval;
// Calculate fighter damage manually if not
int fighter_shots = std::min(available_fighters, fighter_launch_capacity); // how many fighters launched in bout 1
available_fighters -= fighter_shots;
int launched_fighters = fighter_shots;
int remaining_bouts = num_bouts - 2; // no attack for first round, second round already added
while (remaining_bouts > 0) {
int fighters_launched_this_bout = std::min(available_fighters, fighter_launch_capacity);
available_fighters -= fighters_launched_this_bout;
launched_fighters += fighters_launched_this_bout;
fighter_shots += launched_fighters;
--remaining_bouts;
}
// how much damage does a fighter shot do?
fighter_damage = std::max(0.0f, fighter_damage);
if (target_ships)
retval.push_back(fighter_damage * fighter_shots);
else
retval.push_back(static_cast<float>(fighter_shots));
return retval;
}
std::shared_ptr<Ship> TempShipForDamageCalcs(const std::shared_ptr<const Ship>& template_ship,
const ScriptingContext& context)
{
// create temporary ship to test targetting condition on...
static constexpr float shields = 0.0f;
static constexpr float structure = 100.0f;
if (!template_ship) {
ErrorLogger() << "TempShipForDamageCalcs passed null template ship";
return nullptr;
} else if (template_ship->DesignID() == INVALID_DESIGN_ID) {
DebugLogger() << "TempShipForDamageCalcs passed template ship with no known design ID";
return nullptr;
}
// use the given design and species as default enemy.
std::shared_ptr<Ship> target;
try {
target = std::make_shared<Ship>(ALL_EMPIRES, template_ship->DesignID(),
template_ship->SpeciesName(), context.ContextUniverse(),
context.species, ALL_EMPIRES, context.current_turn);
} catch (...) {
ErrorLogger() << "Couldn't create temporary ship from template ship: " << template_ship->Dump();
return nullptr;
}
// target needs to have an ID != -1 to be visible, stealth should be low enough
// structure must be higher than zero to be valid target
target->SetID(TEMPORARY_OBJECT_ID); // also see InsertTemp function usage
target->GetMeter(MeterType::METER_STRUCTURE)->Set(structure, structure);
target->GetMeter(MeterType::METER_MAX_STRUCTURE)->Set(structure, structure);
// Shield value is used for structural damage estimation
target->GetMeter(MeterType::METER_SHIELD)->Set(shields, shields);
return target;
}
auto TempFighterForDamageCalcs(const std::shared_ptr<const Ship>& template_ship,
const ScriptingContext& context)
{
static constexpr auto combat_targets = nullptr;
auto target = std::make_shared<Fighter>(ALL_EMPIRES, template_ship->ID(),
template_ship->SpeciesName(), 1.0f, combat_targets);
target->SetID(TEMPORARY_OBJECT_ID);
return target;
}
}
std::vector<float> Combat::WeaponDamageImpl(
const ScriptingContext& context, std::shared_ptr<const Ship> source, float shields,
bool max, bool launch_fighters, bool target_ships)
{
if (!source) {
ErrorLogger() << "Combat::WeaponDamageImpl passed null source ship";
return {};
} else if (source->DesignID() == INVALID_DESIGN_ID) {
if (source->ID() == TEMPORARY_OBJECT_ID) {
ErrorLogger() << "Combat::WeaponDamageImpl passed TEMPORARY source ship without a valid design ID: " << source->Dump();
} else {
ErrorLogger() << "Combat::WeaponDamageImpl passed source ship without a valid design ID: " << source->Dump();
}
return {};
}
const Universe::EmpireObjectVisibilityMap empire_object_vis{
{source->Owner(), {{TEMPORARY_OBJECT_ID, Visibility::VIS_FULL_VISIBILITY}}}};
const Universe::EmpireObjectVisibilityTurnMap empire_object_visibility_turns{
{source->Owner(), {{TEMPORARY_OBJECT_ID, {{Visibility::VIS_FULL_VISIBILITY, context.current_turn}}}}}};
if (target_ships) {
auto temp_ship = TempShipForDamageCalcs(source, context);
ScriptingContext temp_ship_context{context, empire_object_vis, empire_object_visibility_turns,
source.get(), temp_ship.get()};
return WeaponDamageCalcImpl(source, max, launch_fighters,
target_ships, temp_ship_context);
} else {
// create temporary fighter to test targetting condition on...
auto temp_fighter = TempFighterForDamageCalcs(source, context);
ScriptingContext temp_fighter_context{context, empire_object_vis, empire_object_visibility_turns,
source.get(), temp_fighter.get()};
return WeaponDamageCalcImpl(source, max, launch_fighters,
target_ships, temp_fighter_context);
}
}
/** Populate fighter state quantities and damages for each combat round until @p limit_to_bout */
std::map<int, Combat::FighterBoutInfo> Combat::ResolveFighterBouts(
const ScriptingContext& context, std::shared_ptr<const Ship> ship,
const Condition::Condition* combat_targets, int bay_capacity,
int current_docked, float fighter_damage, int limit_to_bout)
{
std::map<int, FighterBoutInfo> retval;
const int NUM_BOUTS = GetGameRules().Get<int>("RULE_NUM_COMBAT_ROUNDS");
int target_bout = limit_to_bout < 1 ? NUM_BOUTS : limit_to_bout;
Universe::EmpireObjectVisibilityMap empire_object_vis{
{ship->Owner(), {{TEMPORARY_OBJECT_ID, Visibility::VIS_FULL_VISIBILITY}}}};
Universe::EmpireObjectVisibilityTurnMap empire_object_visibility_turns{
{ship->Owner(), {{TEMPORARY_OBJECT_ID, {{Visibility::VIS_FULL_VISIBILITY, context.current_turn}}}}}};
auto temp_ship = TempShipForDamageCalcs(ship, context);
ScriptingContext ship_target_context{context, empire_object_vis, empire_object_visibility_turns,
ship.get(), temp_ship.get()};
for (int bout = 1; bout <= target_bout; ++bout) {
ship_target_context.combat_bout = bout;
// init current fighters
if (bout == 1) {
retval[bout].docked = current_docked;
retval[bout].attacking = 0;
retval[bout].launched = 0;
} else {
retval[bout] = retval[bout - 1];
retval[bout].docked = retval[bout - 1].docked;
retval[bout].attacking = retval[bout - 1].attacking + retval[bout].launched;
retval[bout].launched = 0;
retval[bout].total_damage = retval[bout - 1].total_damage;
}
// calc damage this bout, apply to total
int shots_this_bout = retval[bout].attacking;
if (combat_targets && !combat_targets->EvalOne(ship_target_context, ship_target_context.effect_target))
shots_this_bout = 0;
retval[bout].damage = shots_this_bout * fighter_damage;
retval[bout].total_damage += retval[bout].damage;
// launch fighters
if (bout < NUM_BOUTS) {
retval[bout].launched = std::min(bay_capacity, retval[bout].docked);
retval[bout].docked -= retval[bout].launched;
}
}
return retval;
}
/** Returns max number of shots a carrier's fighters in a battle. Evals @p sampling_condition for each bout vs @p context */
int Combat::TotalFighterShots(const ScriptingContext& context, const Ship& ship,
const Condition::Condition* sampling_condition)
{
// Iterate over context, but change bout number
ScriptingContext mut_context{context};
int launch_capacity = static_cast<int>(ship.SumCurrentPartMeterValuesForPartClass(MeterType::METER_CAPACITY, ShipPartClass::PC_FIGHTER_BAY, context.ContextUniverse()));
int hangar_fighters = static_cast<int>(ship.SumCurrentPartMeterValuesForPartClass(MeterType::METER_CAPACITY, ShipPartClass::PC_FIGHTER_HANGAR, context.ContextUniverse()));
int launched_fighters = 0;
int shots_total = 0;
Condition::ObjectSet condition_matches;
for (int bout = 1; bout <= GetGameRules().Get<int>("RULE_NUM_COMBAT_ROUNDS"); ++bout) {
mut_context.combat_bout = bout;
int launch_this_bout = std::min(launch_capacity, hangar_fighters);
int shots_this_bout = launched_fighters;
if (sampling_condition && launched_fighters > 0) {
// check if not shooting
condition_matches = sampling_condition->Eval(std::as_const(mut_context));
if (condition_matches.size() == 0)
shots_this_bout = 0;
}
shots_total += shots_this_bout;
launched_fighters += launch_this_bout;
hangar_fighters -= launch_this_bout;
}
return shots_total;
}
|