File: CombatDamage.cpp

package info (click to toggle)
freeorion 0.5.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 194,940 kB
  • sloc: cpp: 186,508; python: 40,969; ansic: 1,164; xml: 719; makefile: 32; sh: 7
file content (276 lines) | stat: -rw-r--r-- 13,251 bytes parent folder | download
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;
}