• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

APN-Pucky / tyrant_optimize / 15799373702

21 Jun 2025 08:29PM UTC coverage: 70.309% (-0.1%) from 70.425%
15799373702

push

github

web-flow
ci: lcov ignore (#84)

* ci: lcov ignore

* latest ubutnut

* ci: lcov more ignore

* try less than latest

* cmake toolchain for windows

* ubuntu in ubuntu

* Revert "cmake toolchain for windows"

This reverts commit 881abfcd5.

* install git

* fetch depth

* add sudo

* non interactive debian

* set time zone

* ok

* 2

* add_pip

* no source but sh

4755 of 6763 relevant lines covered (70.31%)

9581795.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.01
/sim.cpp
1
#include "sim.h"
2

3
#include <boost/range/adaptors.hpp>
4
#include <boost/range/join.hpp>
5
#include <iostream>
6
#include <random>
7
#include <string>
8
#include <sstream>
9
#include <vector>
10
#include <cmath>
11

12
#include "card.h"
13
#include "cards.h"
14
#include "deck.h"
15

16
template<enum CardType::CardType def_cardtype>
17
void perform_bge_devour(Field* fd, CardStatus* att_status, CardStatus* def_status, unsigned att_dmg);
18
template<enum CardType::CardType def_cardtype>
19
void perform_bge_heroism(Field* fd, CardStatus* att_status, CardStatus* def_status, unsigned att_dmg);
20
void perform_poison(Field* fd, CardStatus* att_status, CardStatus* def_status);
21
void perform_corrosive(Field* fd, CardStatus* att_status, CardStatus* def_status);
22
void perform_mark(Field* fd, CardStatus* att_status, CardStatus* def_status);
23
void perform_berserk(Field* fd, CardStatus* att_status, CardStatus* def_status);
24
template<enum CardType::CardType def_cardtype>
25
void perform_counter(Field* fd, CardStatus* att_status, CardStatus* def_status);
26
void perform_hunt(Field* fd, CardStatus* att_status, CardStatus* def_status);
27
void perform_subdue(Field* fd, CardStatus* att_status, CardStatus* def_status);
28
bool check_and_perform_valor(Field* fd, CardStatus* src);
29
bool check_and_perform_bravery(Field* fd, CardStatus* src);
30
bool check_and_perform_early_enhance(Field* fd, CardStatus* src);
31
bool check_and_perform_later_enhance(Field* fd, CardStatus* src);
32
CardStatus* check_and_perform_summon(Field* fd, CardStatus* src);
33
//------------------------------------------------------------------------------
34
inline unsigned remove_absorption(Field* fd, CardStatus* status, unsigned dmg);
35
inline unsigned remove_absorption(CardStatus* status, unsigned dmg);
36
inline unsigned remove_disease(CardStatus* status, unsigned heal);
37
//------------------------------------------------------------------------------
38
inline bool has_attacked(CardStatus* c) { return (c->m_step == CardStep::attacked); }
15,538,814✔
39
inline bool is_alive(CardStatus* c) { return (c->m_hp > 0); }
16,750,468✔
40
inline bool can_act(CardStatus* c) { return is_alive(c) && !c->m_jammed; }
435,278,521✔
41
inline bool is_active(CardStatus* c) { return can_act(c) && (c->m_delay == 0); }
359,597,148✔
42
inline bool is_active_next_turn(CardStatus* c) { return can_act(c) && (c->m_delay <= 1); }
51,522,413✔
43
inline bool will_activate_this_turn(CardStatus* c) { return is_active(c) && ((c->m_step == CardStep::none) || (c->m_step == CardStep::attacking && c->has_skill(Skill::flurry) && c->m_action_index < c->skill_base_value(Skill::flurry)));}
255,950✔
44
// Can be healed / repaired
45
inline bool can_be_healed(CardStatus* c) { return is_alive(c) && (c->m_hp < c->max_hp()); }
35,824✔
46
// Strange Transmission [Gilians] features
47
#ifdef TUO_GILIAN
48
inline bool is_gilian(CardStatus* c) { return(
49
        (c->m_card->m_id >= 25054 && c->m_card->m_id <= 25063) // Gilian Commander
50
        ||  (c->m_card->m_id >= 38348 && c->m_card->m_id <= 38388) // Gilian assaults plus the Gil's Shard
51
        ); }
52
inline bool is_alive_gilian(CardStatus* c) { return(is_alive(c) && is_gilian(c)); }
53
#else
54
# define is_gilian(c) (false)
55
# define is_alive_gilian(c) (false)
56
#endif
57

58
//------------------------------------------------------------------------------
59
inline std::string status_description(const CardStatus* status)
54,794,499✔
60
{
61
    return status->description();
21,816,803✔
62
}
63
//------------------------------------------------------------------------------
64
template <typename CardsIter, typename Functor>
65
inline unsigned Field::make_selection_array(CardsIter first, CardsIter last, Functor f)
125,968,918✔
66
{
67
    this->selection_array.clear();
125,968,918✔
68
    for(auto c = first; c != last; ++c)
510,613,345✔
69
    {
70
        if (f(*c))
774,531,402✔
71
        {
72
            this->selection_array.push_back(*c);
197,454,123✔
73
        }
74
    }
75
    return(this->selection_array.size());
125,968,918✔
76
}
77
inline CardStatus * Field::left_assault(const CardStatus * status)
1,074,165✔
78
{
79
    return left_assault(status, 1);
4,051,648✔
80
}
81
inline CardStatus * Field::left_assault(const CardStatus * status, const unsigned n)
1,074,165✔
82
{
83
    auto & assaults = this->players[status->m_player]->assaults;
1,074,165✔
84
    if (status->m_index >= n)
1,074,165✔
85
    {
86
        auto left_status = &assaults[status->m_index - n];
822,604✔
87
        if (is_alive(left_status))
822,604✔
88
        {
89
            return left_status;
90
        }
91
    }
92
    return nullptr;
93
}
94
inline CardStatus * Field::right_assault(const CardStatus * status)
1,074,165✔
95
{
96
    return right_assault(status, 1);
4,015,655✔
97
}
98
inline CardStatus * Field::right_assault(const CardStatus * status, const unsigned n)
1,074,165✔
99
{
100
    auto & assaults = this->players[status->m_player]->assaults;
1,074,165✔
101
    if ((status->m_index + n) < assaults.size())
1,074,165✔
102
    {
103
        auto right_status = &assaults[status->m_index + n];
832,044✔
104
        if (is_alive(right_status))
832,044✔
105
        {
106
            return right_status;
107
        }
108
    }
109
    return nullptr;
110
}
111
inline void Field::print_selection_array()
×
112
{
113
#ifndef NDEBUG
114
    for(auto c: this->selection_array)
×
115
    {
116
        _DEBUG_MSG(2, "+ %s\n", status_description(c).c_str());
×
117
    }
118
#endif
119
}
×
120

121
//------------------------------------------------------------------------------
122
inline void Field::prepare_action()
105,540,267✔
123
{
124
    damaged_units_to_times.clear();
211,080,534✔
125
}
126

127
//------------------------------------------------------------------------------
128
inline void Field::finalize_action()
104,165,005✔
129
{
130
    for (auto unit_it = damaged_units_to_times.begin(); unit_it != damaged_units_to_times.end(); ++ unit_it)
104,452,903✔
131
    {
132
        if (__builtin_expect(!unit_it->second, false))
287,898✔
133
        { continue; }
×
134
        CardStatus * dmg_status = unit_it->first;
287,898✔
135
        if (__builtin_expect(!is_alive(dmg_status), false))
287,898✔
136
        { continue; }
123,667✔
137
        unsigned barrier_base = dmg_status->skill(Skill::barrier);
164,231✔
138
        if (barrier_base)
164,231✔
139
        {
140
            unsigned protect_value = barrier_base * unit_it->second;
164,231✔
141
            _DEBUG_MSG(1, "%s protects itself for %u (barrier %u x %u damage taken)\n",
164,231✔
142
                    status_description(dmg_status).c_str(), protect_value, barrier_base, unit_it->second);
164,231✔
143
            dmg_status->m_protected += protect_value;
164,231✔
144
        }
145
    }
146
}
104,165,005✔
147

148
//------------------------------------------------------------------------------
149
inline unsigned CardStatus::skill_base_value(Skill::Skill skill_id) const
1,295,954,772✔
150
{
151
    return m_card->m_skill_value[skill_id + m_primary_skill_offset[skill_id]]
1,295,954,772✔
152
        + (skill_id == Skill::berserk ? m_enraged : 0)
×
153
        + (skill_id == Skill::counter ? m_entrapped : 0)
36,216,285✔
154
        ;
155
}
156
//------------------------------------------------------------------------------
157
inline unsigned CardStatus::skill(Skill::Skill skill_id) const
821,166,767✔
158
{
159
    return (is_activation_skill_with_x(skill_id)
821,166,767✔
160
            ? safe_minus(skill_base_value(skill_id), m_sabotaged)
161
            : skill_base_value(skill_id))
823,302,581✔
162
        + enhanced(skill_id);
823,302,581✔
163
}
164
//------------------------------------------------------------------------------
165
inline bool CardStatus::has_skill(Skill::Skill skill_id) const
472,767,375✔
166
{
167
    return skill_base_value(skill_id);
53,184✔
168
}
169
//------------------------------------------------------------------------------
170
inline unsigned CardStatus::enhanced(Skill::Skill skill_id) const
933,048,579✔
171
{
172
    return m_enhanced_value[skill_id + m_primary_skill_offset[skill_id]];
164,231✔
173
}
174
//------------------------------------------------------------------------------
175
inline unsigned CardStatus::protected_value() const
55,274,589✔
176
{
177
    return m_protected + m_protected_stasis;
55,274,589✔
178
}
179
//------------------------------------------------------------------------------
180
/**
181
 * @brief Maximum health.
182
 * This takes subduing into account by reducing the permanent health buffs by subdue.
183
 * 
184
 * @return unsigned maximum health.
185
 */
186
inline unsigned CardStatus::max_hp() const
101,623,244✔
187
{
188
    return (m_card->m_health + safe_minus(m_perm_health_buff, m_subdued));
97,345,789✔
189
}
190
/** @brief Increase of current health.
191
 *  This takes disease into account and removes as needed.
192
 *
193
 *  @param [in] value increase of health.
194
 *  @return applied increase of health.
195
 */
196
inline unsigned CardStatus::add_hp(unsigned value)
23,109,460✔
197
{
198
    value = remove_disease(this,value);
23,109,460✔
199
    return (m_hp = std::min(m_hp + value, max_hp()));
41,210,837✔
200
}
201
/** @brief Permanent increase of maximum health.
202
 *  This takes disease into account and removes as needed.
203
 *  The increase of the maximum health entails an increase of current health by the same amount.
204
 *
205
 *  @param [in] value increase of maximum health.
206
 *  @return applied increase of maximum health.
207
 */ 
208
inline unsigned CardStatus::ext_hp(unsigned value)
14,181,399✔
209
{
210
    value = remove_disease(this,value);
14,181,399✔
211
    m_perm_health_buff += value;
14,181,399✔
212
    // we can safely call add_hp without worring about the second call to remove_disease because
213
    // the first call will have already removed the disease or the value will be 0
214
    return add_hp(value);
14,181,399✔
215
}
216
//------------------------------------------------------------------------------
217
inline void CardStatus::set(const Card* card)
25,449,589✔
218
{
219
    this->set(*card);
25,449,589✔
220
}
221
//------------------------------------------------------------------------------
222
inline void CardStatus::set(const Card& card)
25,449,589✔
223
{
224
    m_card = &card;
25,449,589✔
225
    m_index = 0;
25,449,589✔
226
    m_action_index=0;
25,449,589✔
227
    m_player = 0;
25,449,589✔
228
    m_delay = card.m_delay;
25,449,589✔
229
    m_hp = card.m_health;
25,449,589✔
230
    m_absorption = 0;
25,449,589✔
231
    m_step = CardStep::none;
25,449,589✔
232
    m_perm_health_buff = 0;
25,449,589✔
233
    m_perm_attack_buff = 0;
25,449,589✔
234
    m_temp_attack_buff = 0;
25,449,589✔
235
    m_corroded_rate = 0;
25,449,589✔
236
    m_corroded_weakened = 0;
25,449,589✔
237
    m_subdued = 0;
25,449,589✔
238
    m_enfeebled = 0;
25,449,589✔
239
    m_evaded = 0;
25,449,589✔
240
    m_inhibited = 0;
25,449,589✔
241
    m_sabotaged = 0;
25,449,589✔
242
    m_jammed = false;
25,449,589✔
243
    m_overloaded = false;
25,449,589✔
244
    m_paybacked = 0;
25,449,589✔
245
    m_tributed = 0;
25,449,589✔
246
    m_poisoned = 0;
25,449,589✔
247
    m_protected = 0;
25,449,589✔
248
    m_protected_stasis = 0;
25,449,589✔
249
    m_enraged = 0;
25,449,589✔
250
    m_entrapped = 0;
25,449,589✔
251
    m_marked = 0;
25,449,589✔
252
    m_diseased = 0;
25,449,589✔
253
    m_rush_attempted = false;
25,449,589✔
254
    m_sundered = false;
25,449,589✔
255
    //APN
256
    m_summoned = false;
25,449,589✔
257

258
    std::memset(m_primary_skill_offset, 0, sizeof m_primary_skill_offset);
25,449,589✔
259
    std::memset(m_evolved_skill_offset, 0, sizeof m_evolved_skill_offset);
25,449,589✔
260
    std::memset(m_enhanced_value, 0, sizeof m_enhanced_value);
25,449,589✔
261
    std::memset(m_skill_cd, 0, sizeof m_skill_cd);
25,449,589✔
262
}
25,449,589✔
263
//------------------------------------------------------------------------------
264
/**
265
 * @brief Calculate the attack power of the CardStatus.
266
 * 
267
 * @return unsigned 
268
 */
269
inline unsigned CardStatus::attack_power() const
195,080,804✔
270
{
271
    signed attack = calc_attack_power();
195,080,804✔
272
    if(__builtin_expect(attack <0,false))
195,080,804✔
273
    {
274
        std::cout << m_card->m_name << " " << m_card->m_attack  << " " << attack << " " << m_temp_attack_buff << " " << m_corroded_weakened << std::endl;
×
275
    }
276
    _DEBUG_ASSERT(attack >= 0);
195,080,804✔
277
    return (unsigned)attack;
195,080,804✔
278
}
279

280
/**
281
 * @brief Calculate the attack power of the CardStatus.
282
 * 
283
 * The attack power is the base attack plus. 
284
 * The subdued value gets subtracted from the permanent attack buff, if this is above zero it is added to the attack power.
285
 * The corroded value gets subtracted from the attack power, but not below zero.
286
 * Finally the temporary attack buff is added.
287
 * 
288
 * @return signed attack power.
289
 */
290
inline signed CardStatus::calc_attack_power() const
195,929,857✔
291
{
292
    if(__builtin_expect(this->m_field->fixes[Fix::corrosive_protect_armor],true)) // MK - 05/01/2023 6:58 AM: "Corrosive now counts as temporary attack reduction instead of permanent, so it is calculated after Subdue and can now counter Rally even if the permanent attack is zeroed."
195,929,857✔
293
    {
294
        return
195,929,857✔
295
        (signed)safe_minus(
391,859,714✔
296
                m_card->m_attack + safe_minus(m_perm_attack_buff, m_subdued)+ m_temp_attack_buff,
195,929,857✔
297
                m_corroded_weakened
195,929,857✔
298
                );
195,929,857✔
299
    }
300
    else{
301
        return
×
302
        (signed)safe_minus(
×
303
                m_card->m_attack + safe_minus(m_perm_attack_buff, m_subdued),
×
304
                m_corroded_weakened
×
305
                )
306
        + m_temp_attack_buff;
×
307
    }
308

309
}
310
//------------------------------------------------------------------------------
311
const Card* card_by_id_safe(const Cards& cards, const unsigned card_id)
×
312
{
313
    const auto cardIter = cards.cards_by_id.find(card_id);
×
314
    if (cardIter == cards.cards_by_id.end()) assert(false);//"UnknownCard.id[" + to_string(card_id) + "]"); }
×
315
    return cardIter->second;
×
316
}
317
std::string card_name_by_id_safe(const Cards& cards, const unsigned card_id)
90,035✔
318
{
319
    const auto cardIter = cards.cards_by_id.find(card_id);
90,035✔
320
    if (cardIter == cards.cards_by_id.end()) { return "UnknownCard.id[" + tuo::to_string(card_id) + "]"; }
90,035✔
321
    return cardIter->second->m_name;
180,070✔
322
}
323
//------------------------------------------------------------------------------
324
std::string card_description(const Cards& cards, const Card* c)
2,482,547✔
325
{
326
    std::string desc;
2,482,547✔
327
    desc = c->m_name;
2,482,547✔
328
    switch(c->m_type)
2,482,547✔
329
    {
330
        case CardType::assault:
2,055,232✔
331
            desc += ": " + tuo::to_string(c->m_attack) + "/" + tuo::to_string(c->m_health) + "/" + tuo::to_string(c->m_delay);
10,276,160✔
332
            break;
2,055,232✔
333
        case CardType::structure:
426,759✔
334
            desc += ": " + tuo::to_string(c->m_health) + "/" + tuo::to_string(c->m_delay);
1,707,036✔
335
            break;
426,759✔
336
        case CardType::commander:
556✔
337
            desc += ": hp:" + tuo::to_string(c->m_health);
1,668✔
338
            break;
556✔
339
        case CardType::num_cardtypes:
×
340
            assert(false);
×
341
            break;
342
    }
343
    if (c->m_rarity >= 4) { desc += " " + rarity_names[c->m_rarity]; }
4,165,899✔
344
    if (c->m_faction != allfactions) { desc += " " + faction_names[c->m_faction]; }
4,965,094✔
345
    for (auto& skill: c->m_skills_on_play) { desc += ", " + skill_description(cards, skill, Skill::Trigger::play); }
2,608,467✔
346
    for (auto& skill: c->m_skills) { desc += ", " + skill_description(cards, skill, Skill::Trigger::activate); }
16,425,075✔
347
    //APN
348
    for (auto& skill: c->m_skills_on_attacked) { desc += ", " + skill_description(cards, skill, Skill::Trigger::attacked); }
3,047,263✔
349
    for (auto& skill: c->m_skills_on_death) { desc += ", " + skill_description(cards, skill, Skill::Trigger::death); }
2,648,627✔
350
    return desc;
2,482,547✔
351
}
×
352
//------------------------------------------------------------------------------
353
std::string CardStatus::description() const
54,794,499✔
354
{
355
    std::string desc = "P" + tuo::to_string(m_player) + " ";
164,383,497✔
356
    switch(m_card->m_type)
54,794,499✔
357
    {
358
        case CardType::commander: desc += "Commander "; break;
16,924,508✔
359
        case CardType::assault: desc += "Assault " + tuo::to_string(m_index) + " "; break;
126,257,816✔
360
        case CardType::structure: desc += "Structure " + tuo::to_string(m_index) + " "; break;
25,222,148✔
361
        case CardType::num_cardtypes: assert(false); break;
×
362
    }
363
    desc += "[" + m_card->m_name;
109,588,998✔
364
    switch (m_card->m_type)
54,794,499✔
365
    {
366
        case CardType::assault:
31,564,454✔
367
            desc += " att:[[" + tuo::to_string(m_card->m_attack) + "(base)";
126,257,816✔
368
            if (m_perm_attack_buff)
31,564,454✔
369
            {
370
                desc += "+[" + tuo::to_string(m_perm_attack_buff) + "(perm)";
28,963,036✔
371
                if (m_subdued) { desc += "-" + tuo::to_string(m_subdued) + "(subd)"; }
8,526,697✔
372
                desc += "]";
7,240,759✔
373
            }
374
            if (m_corroded_weakened) { desc += "-" + tuo::to_string(m_corroded_weakened) + "(corr)"; }
32,976,527✔
375
            desc += "]";
31,564,454✔
376
            if (m_temp_attack_buff) { desc += (m_temp_attack_buff > 0 ? "+" : "") + tuo::to_string(m_temp_attack_buff) + "(temp)"; }
48,444,071✔
377
            desc += "]=" + tuo::to_string(attack_power());
94,693,362✔
378
        case CardType::structure:
54,794,499✔
379
        case CardType::commander:
54,794,499✔
380
            desc += " hp:" + tuo::to_string(m_hp);
164,383,497✔
381
            if(m_absorption)desc += " absorb:" + tuo::to_string(m_absorption);
60,254,523✔
382
            break;
383
        case CardType::num_cardtypes:
×
384
            assert(false);
×
385
            break;
386
    }
387
    if (m_delay) { desc += " cd:" + tuo::to_string(m_delay); }
79,079,657✔
388
    // Status w/o value
389
    if (m_jammed) { desc += ", jammed"; }
54,794,499✔
390
    if (m_overloaded) { desc += ", overloaded"; }
54,794,499✔
391
    if (m_sundered) { desc += ", sundered"; }
54,794,499✔
392
    if (m_summoned) { desc+= ", summoned"; }
54,794,499✔
393
    // Status w/ value
394
    if (m_corroded_weakened || m_corroded_rate) { desc += ", corroded " + tuo::to_string(m_corroded_weakened) + " (rate: " + tuo::to_string(m_corroded_rate) + ")"; }
56,813,943✔
395
    if (m_subdued) { desc += ", subdued " + tuo::to_string(m_subdued); }
56,924,949✔
396
    if (m_enfeebled) { desc += ", enfeebled " + tuo::to_string(m_enfeebled); }
60,015,791✔
397
    if (m_inhibited) { desc += ", inhibited " + tuo::to_string(m_inhibited); }
57,789,155✔
398
    if (m_sabotaged) { desc += ", sabotaged " + tuo::to_string(m_sabotaged); }
57,981,569✔
399
    if (m_poisoned) { desc += ", poisoned " + tuo::to_string(m_poisoned); }
56,535,951✔
400
    if (m_protected) { desc += ", protected " + tuo::to_string(m_protected); }
75,879,521✔
401
    if (m_protected_stasis) { desc += ", stasis " + tuo::to_string(m_protected_stasis); }
59,586,437✔
402
    if (m_enraged) { desc += ", enraged " + tuo::to_string(m_enraged); }
60,911,743✔
403
    if (m_entrapped) { desc += ", entrapped " + tuo::to_string(m_entrapped); }
74,371,621✔
404
    if (m_marked) { desc += ", marked " + tuo::to_string(m_marked); }
55,067,073✔
405
    if (m_diseased) { desc += ", diseased " + tuo::to_string(m_diseased); }
55,797,611✔
406
    //    if(m_step != CardStep::none) { desc += ", Step " + to_string(static_cast<int>(m_step)); }
407
    //APN
408
    const Skill::Trigger s_triggers[] = { Skill::Trigger::play, Skill::Trigger::activate, Skill::Trigger::death , Skill::Trigger::attacked};
54,794,499✔
409
    for (const Skill::Trigger& trig: s_triggers)
273,972,495✔
410
    {
411
        std::vector<SkillSpec> card_skills(
219,177,996✔
412
                (trig == Skill::Trigger::play) ? m_card->m_skills_on_play :
219,177,996✔
413
                (trig == Skill::Trigger::activate) ? m_card->m_skills :
164,383,497✔
414
                //APN
415
                (trig == Skill::Trigger::attacked) ? m_card->m_skills_on_attacked :
109,588,998✔
416
                (trig == Skill::Trigger::death) ? m_card->m_skills_on_death :
54,794,499✔
417
                std::vector<SkillSpec>());
219,177,996✔
418

419
        // emulate Berserk/Counter by status Enraged/Entrapped unless such skills exist (only for normal skill triggering)
420
        if (trig == Skill::Trigger::activate)
219,177,996✔
421
        {
422
            if (m_enraged && !std::count_if(card_skills.begin(), card_skills.end(), [](const SkillSpec ss) { return (ss.id == Skill::berserk); }))
57,853,121✔
423
            {
424
                SkillSpec ss{Skill::berserk, m_enraged, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
3,014,108✔
425
                card_skills.emplace_back(ss);
3,014,108✔
426
            }
427
            if (m_entrapped && !std::count_if(card_skills.begin(), card_skills.end(), [](const SkillSpec ss) { return (ss.id == Skill::counter); }))
64,583,060✔
428
            {
429
                SkillSpec ss{Skill::counter, m_entrapped, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
9,552,510✔
430
                card_skills.emplace_back(ss);
9,552,510✔
431
            }
432
        }
433
        for (const auto& ss : card_skills)
395,373,174✔
434
        {
435
            std::string skill_desc;
176,195,178✔
436
            if (m_evolved_skill_offset[ss.id]) { skill_desc += "->" + skill_names[ss.id + m_evolved_skill_offset[ss.id]]; }
176,591,738✔
437
            if (m_enhanced_value[ss.id]) { skill_desc += " +" + tuo::to_string(m_enhanced_value[ss.id]); }
178,979,294✔
438
            if (!skill_desc.empty())
176,195,178✔
439
            {
440
                desc += ", " + (
3,577,236✔
441
                        (trig == Skill::Trigger::play) ? "(On Play)" :
3,577,236✔
442
                        (trig == Skill::Trigger::attacked) ? "(On Attacked)" :
1,788,618✔
443
                        (trig == Skill::Trigger::death) ? "(On Death)" :
1,788,618✔
444
                        std::string("")) + skill_names[ss.id] + skill_desc;
8,943,090✔
445
            }
446
        }
176,195,178✔
447
    }
219,177,996✔
448
    return desc + "]";
54,794,499✔
449
}
54,794,499✔
450
//------------------------------------------------------------------------------
451
void Hand::reset(std::mt19937& re)
2,792,746✔
452
{
453
    assaults.reset();
2,792,746✔
454
    structures.reset();
2,792,746✔
455
    deck->shuffle(re);
2,792,746✔
456
    commander.set(deck->shuffled_commander);
2,792,746✔
457
    total_cards_destroyed = 0;
2,792,746✔
458
    total_nonsummon_cards_destroyed = 0;
2,792,746✔
459
    if (commander.skill(Skill::stasis))
2,792,746✔
460
    {
461
        stasis_faction_bitmap |= (1u << commander.m_card->m_faction);
×
462
    }
463
}
2,792,746✔
464

465
//---------------------- $40 Game rules implementation -------------------------
466
// Everything about how a battle plays out, except the following:
467
// the implementation of the attack by an assault card is in the next section;
468
// the implementation of the active skills is in the section after that.
469
unsigned turn_limit{50};
470

471
//------------------------------------------------------------------------------
472
inline unsigned opponent(unsigned player)
53,291,413✔
473
{
474
    return((player + 1) % 2);
53,291,413✔
475
}
476

477
//------------------------------------------------------------------------------
478
SkillSpec apply_evolve(const SkillSpec& s, signed offset)
310,273✔
479
{
480
    SkillSpec evolved_s = s;
310,273✔
481
    evolved_s.id = static_cast<Skill::Skill>(evolved_s.id + offset);
310,273✔
482
    return(evolved_s);
310,273✔
483
}
484

485
//------------------------------------------------------------------------------
486
SkillSpec apply_enhance(const SkillSpec& s, unsigned enhanced_value)
×
487
{
488
    SkillSpec enahnced_s = s;
×
489
    enahnced_s.x += enhanced_value;
×
490
    return(enahnced_s);
×
491
}
492

493
//------------------------------------------------------------------------------
494
SkillSpec apply_sabotage(const SkillSpec& s, unsigned sabotaged_value)
139,907✔
495
{
496
    SkillSpec sabotaged_s = s;
139,907✔
497
    sabotaged_s.x -= std::min(sabotaged_s.x, sabotaged_value);
×
498
    return(sabotaged_s);
139,907✔
499
}
500

501
//------------------------------------------------------------------------------
502
inline void resolve_scavenge(Storage<CardStatus>& store)
39,766,544✔
503
{
504
    for(auto status : store.m_indirect)
151,515,465✔
505
    {
506
        if(!is_alive(status))continue;
111,748,921✔
507
        unsigned scavenge_value = status->skill(Skill::scavenge);
97,744,896✔
508
        if(!scavenge_value)continue;
97,744,896✔
509

510
        _DEBUG_MSG(1, "%s activates Scavenge %u\n",
7,071,115✔
511
                status_description(status).c_str(), scavenge_value);
7,071,115✔
512
        status->ext_hp(scavenge_value);
7,071,115✔
513
    }
514
}
39,766,544✔
515
//------------------------------------------------------------------------------
516
/**
517
 * @brief Handle death of a card
518
 * 
519
 * @param fd Field pointer 
520
 * @param paybacked Is the death caused by payback?
521
 */
522
void prepend_on_death(Field* fd, bool paybacked=false)
128,009,158✔
523
{
524
    if (fd->killed_units.empty())
128,009,158✔
525
        return;
526
    bool skip_all_except_summon = fd->fixes[Fix::death_from_bge] && (fd->current_phase == Field::bge_phase);
9,801,111✔
527
    if (skip_all_except_summon)
×
528
    {
529
        _DEBUG_MSG(2, "Death from BGE Fix (skip all death depended triggers except summon)\n");
×
530
    }
531
    auto& assaults = fd->players[fd->killed_units[0]->m_player]->assaults;
9,801,111✔
532
    unsigned stacked_poison_value = 0;
9,801,111✔
533
    unsigned last_index = 99999;
9,801,111✔
534
    CardStatus* left_virulence_victim = nullptr;
9,801,111✔
535
    for (auto status: fd->killed_units)
19,742,747✔
536
    {
537
        // Skill: Scavenge
538
        // Any unit dies => perm-hp-buff
539
        if (__builtin_expect(!skip_all_except_summon, true))
9,941,636✔
540
        {
541
            resolve_scavenge(fd->players[0]->assaults);
9,941,636✔
542
            resolve_scavenge(fd->players[1]->assaults);
9,941,636✔
543
            resolve_scavenge(fd->players[0]->structures);
9,941,636✔
544
            resolve_scavenge(fd->players[1]->structures);
9,941,636✔
545
        }
546

547
        if ((status->m_card->m_type == CardType::assault) && (!skip_all_except_summon))
9,941,636✔
548
        {
549
            // Skill: Avenge
550
            const unsigned host_idx = status->m_index;
8,979,244✔
551
            unsigned from_idx, till_idx;
8,979,244✔
552
            //scan all assaults for Avenge
553
            from_idx = 0;
8,979,244✔
554
            till_idx = assaults.size() - 1;
8,979,244✔
555
            for (; from_idx <= till_idx; ++ from_idx)
35,301,299✔
556
            {
557
                if (from_idx == host_idx) { continue; }
26,322,055✔
558
                CardStatus* adj_status = &assaults[from_idx];
17,342,811✔
559
                if (!is_alive(adj_status)) { continue; }
17,342,811✔
560
                unsigned avenge_value = adj_status->skill(Skill::avenge);
15,175,535✔
561
                if (!avenge_value) { continue; }
15,175,535✔
562

563
                // Use half value rounded up
564
                // (for distance > 1, i. e. non-standard Avenge triggering)
565
                if (std::abs((signed)from_idx - (signed)host_idx) > 1)
3,391,198✔
566
                {
567
                    avenge_value = (avenge_value + 1) / 2;
1,859,210✔
568
                }
569
                _DEBUG_MSG(1, "%s activates %sAvenge %u\n",
3,546,017✔
570
                        status_description(adj_status).c_str(),
571
                        (std::abs((signed)from_idx - (signed)host_idx) > 1 ? "Half-" : ""),
572
                         avenge_value);
3,391,198✔
573
                if (!adj_status->m_sundered)
3,391,198✔
574
                { adj_status->m_perm_attack_buff += avenge_value; }
3,149,625✔
575
                adj_status->ext_hp(avenge_value);
3,391,198✔
576
            }
577

578
            // Passive BGE: Virulence
579
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::virulence], false))
8,979,244✔
580
            {
581
                if (status->m_index != last_index + 1)
34,506✔
582
                {
583
                    stacked_poison_value = 0;
34,094✔
584
                    left_virulence_victim = nullptr;
34,094✔
585
                    if (status->m_index > 0)
34,094✔
586
                    {
587
                        auto left_status = &assaults[status->m_index - 1];
7,175✔
588
                        if (is_alive(left_status))
7,175✔
589
                        {
590
                            left_virulence_victim = left_status;
34,506✔
591
                        }
592
                    }
593
                }
594
                if (status->m_poisoned > 0)
34,506✔
595
                {
596
                    if (left_virulence_victim != nullptr)
1,641✔
597
                    {
598
                        _DEBUG_MSG(1, "Virulence: %s spreads left poison +%u to %s\n",
172✔
599
                                status_description(status).c_str(), status->m_poisoned,
600
                                status_description(left_virulence_victim).c_str());
172✔
601
                        left_virulence_victim->m_poisoned += status->m_poisoned;
172✔
602
                    }
603
                    stacked_poison_value += status->m_poisoned;
1,641✔
604
                    _DEBUG_MSG(1, "Virulence: %s spreads right poison +%u = %u\n",
1,641✔
605
                            status_description(status).c_str(), status->m_poisoned, stacked_poison_value);
606
                }
607
                if (status->m_index + 1 < assaults.size())
34,506✔
608
                {
609
                    auto right_status = &assaults[status->m_index + 1];
11,186✔
610
                    if (is_alive(right_status))
11,186✔
611
                    {
612
                        _DEBUG_MSG(1, "Virulence: spreads stacked poison +%u to %s\n",
10,328✔
613
                                stacked_poison_value, status_description(right_status).c_str());
10,328✔
614
                        right_status->m_poisoned += stacked_poison_value;
10,328✔
615
                    }
616
                }
617
                last_index = status->m_index;
34,506✔
618
            }
619
        }
620

621
        // Passive BGE: Revenge
622
        // Fix::death_from_bge: should not affect passive BGE, keep it as was before
623
        if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::revenge], false))
9,941,636✔
624
        {
625
            if (fd->bg_effects[fd->tapi][PassiveBGE::revenge] < 0)
72,724✔
626
                throw std::runtime_error("BGE Revenge: value must be defined & positive");
×
627
            SkillSpec ss_heal{Skill::heal, (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::revenge], allfactions, 0, 0, Skill::no_skill, Skill::no_skill, true, 0,};
72,724✔
628
            SkillSpec ss_rally{Skill::rally, (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::revenge], allfactions, 0, 0, Skill::no_skill, Skill::no_skill, true, 0,};
72,724✔
629
            CardStatus* commander = &fd->players[status->m_player]->commander;
72,724✔
630
            _DEBUG_MSG(2, "Revenge: Preparing (head) skills  %s and %s\n",
72,724✔
631
                    skill_description(fd->cards, ss_heal).c_str(),
632
                    skill_description(fd->cards, ss_rally).c_str());
72,724✔
633
            fd->skill_queue.emplace(fd->skill_queue.begin()+0, commander, ss_heal);
72,724✔
634
            fd->skill_queue.emplace(fd->skill_queue.begin()+1, commander, ss_rally); // +1: keep ss_heal at first place
72,724✔
635
        }
636

637
        // resolve On-Death skills
638
        for (auto& ss: status->m_card->m_skills_on_death)
10,351,972✔
639
        {
640
            if (__builtin_expect(skip_all_except_summon && (ss.id != Skill::summon), false))
410,336✔
641
            { continue; }
×
642
                SkillSpec tss = ss;
410,336✔
643
            _DEBUG_MSG(2, "On Death %s: Preparing (tail) skill %s\n",
410,336✔
644
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
410,336✔
645
            if(fd->fixes[Fix::revenge_on_death] && is_activation_harmful_skill(ss.id) && paybacked)
410,336✔
646
            {
647
                    _DEBUG_MSG(2, "On Death Revenge Fix\n");
308✔
648
                    tss.s2 = Skill::revenge;
308✔
649
            }
650
            fd->skill_queue.emplace_back(status, tss);
410,336✔
651
        }
652
    }
653
    fd->killed_units.clear();
137,810,269✔
654
}
655

656
//------------------------------------------------------------------------------
657
void(*skill_table[Skill::num_skills])(Field*, CardStatus* src, const SkillSpec&);
658
void resolve_skill(Field* fd)
185,513,777✔
659
{
660
    while (!fd->skill_queue.empty())
300,168,504✔
661
    {
662
        auto skill_instance(fd->skill_queue.front());
114,654,727✔
663
        auto& status(std::get<0>(skill_instance));
114,654,727✔
664
        const auto& ss(std::get<1>(skill_instance));
114,654,727✔
665
        fd->skill_queue.pop_front();
114,654,727✔
666
        if (__builtin_expect(status->m_card->m_skill_trigger[ss.id] == Skill::Trigger::activate, true))
114,654,727✔
667
        {
668
            if (!is_alive(status))
110,336,843✔
669
            {
670
                _DEBUG_MSG(2, "%s failed to %s because it is dead.\n",
3,981✔
671
                        status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
3,981✔
672
                continue;
3,122,647✔
673
            }
3,981✔
674
            if (status->m_jammed)
110,332,862✔
675
            {
676
                _DEBUG_MSG(2, "%s failed to %s because it is Jammed.\n",
1,384✔
677
                        status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
1,384✔
678
                continue;
1,384✔
679
            }
1,384✔
680
        }
681

682
        // is summon? (non-activation skill)
683
        if (ss.id == Skill::summon)
114,649,362✔
684
        {
685
            check_and_perform_summon(fd, status);
3,047,829✔
686
            continue;
3,047,829✔
687
        }
688
        _DEBUG_ASSERT(is_activation_skill(ss.id) || ss.id == Skill::enhance); // enhance is no trigger, but  queues the skill
111,693,561✔
689

690
        SkillSpec modified_s = ss;
111,601,533✔
691

692
        // apply evolve
693
        signed evolved_offset = status->m_evolved_skill_offset[modified_s.id];
111,601,533✔
694
        if (evolved_offset != 0)
111,601,533✔
695
        { modified_s = apply_evolve(modified_s, evolved_offset); }
310,273✔
696

697
        // apply sabotage (only for X-based activation skills)
698
        unsigned sabotaged_value = status->m_sabotaged;
111,601,533✔
699
        if ((sabotaged_value > 0) && is_activation_skill_with_x(modified_s.id))
111,601,533✔
700
        { modified_s = apply_sabotage(modified_s, sabotaged_value); }
210,361✔
701

702
        // apply enhance
703
        unsigned enhanced_value = status->enhanced(modified_s.id);
111,601,533✔
704
        if (enhanced_value > 0)
111,601,533✔
705
        { modified_s = apply_enhance(modified_s, enhanced_value); }
×
706

707
        // perform skill (if it is still applicable)
708
        if (is_activation_skill_with_x(modified_s.id) && !modified_s.x)
111,601,533✔
709
        {
710
            _DEBUG_MSG(2, "%s failed to %s because its X value is zeroed (sabotaged).\n",
69,453✔
711
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
69,453✔
712
            continue;
69,453✔
713
        }
69,453✔
714
        else { skill_table[modified_s.id](fd, status, modified_s); }
111,532,080✔
715
    }
716
}
185,513,777✔
717

718
void apply_corrosion(CardStatus * status)
22,046,705✔
719
{
720
      if (status->m_corroded_rate)
22,046,705✔
721
      {
722
        unsigned v = std::min(status->m_corroded_rate, status->attack_power());
97,897✔
723
        unsigned corrosion = std::min(v, status->m_card->m_attack
195,794✔
724
                + status->m_perm_attack_buff - status->m_corroded_weakened);
97,897✔
725
        _DEBUG_MSG(1, "%s loses Attack by %u (+corrosion %u).\n", status_description(status).c_str(), v, corrosion);
97,897✔
726
        status->m_corroded_weakened += corrosion;
97,897✔
727
      }
728
}
22,046,705✔
729
void remove_corrosion(CardStatus * status)
2,875,600✔
730
{
731
       if (status->m_corroded_rate)
2,875,600✔
732
       {
733
           _DEBUG_MSG(1, "%s loses Status corroded.\n", status_description(status).c_str());
5,222✔
734
           status->m_corroded_rate = 0;
5,222✔
735
           status->m_corroded_weakened = 0;
5,222✔
736
       }
737
}
2,875,600✔
738
//------------------------------------------------------------------------------
739
bool attack_phase(Field* fd);
740

741
template<Skill::Skill skill_id>
742
inline bool check_and_perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s, bool is_evadable
743
#ifndef NQUEST
744
        , bool & has_counted_quest
745
#endif
746
        );
747

748
    template <enum CardType::CardType type>
749
void evaluate_skills(Field* fd, CardStatus* status, const std::vector<SkillSpec>& skills, bool* attacked=nullptr)
62,256,470✔
750
{
751
    _DEBUG_ASSERT(status);
62,256,470✔
752
    unsigned num_actions(1);
753
    for (unsigned action_index(0); action_index < num_actions; ++ action_index)
129,039,451✔
754
    {
755
        status->m_action_index = action_index;
68,158,243✔
756
        fd->prepare_action();
68,158,243✔
757
        _DEBUG_ASSERT(fd->skill_queue.size() == 0);
68,158,243✔
758
        for (auto & ss: skills)
263,156,621✔
759
        {
760
            if (!is_activation_skill(ss.id)) { continue; }
278,990,203✔
761
            if (status->m_skill_cd[ss.id] > 0) { continue; }
111,006,553✔
762
            _DEBUG_MSG(2, "Evaluating %s skill %s\n",
110,099,367✔
763
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
764
            fd->skill_queue.emplace_back(status, ss);
110,099,367✔
765
            resolve_skill(fd);
110,099,367✔
766
        }
767
        if (type == CardType::assault)
768
        {
769
            // Attack
770
            if (can_act(status))
26,297,567✔
771
            {
772
                if (attack_phase(fd))
26,122,768✔
773
                {
774
                    *attacked = true;
23,421,967✔
775
                    if (__builtin_expect(fd->end, false)) { break; }
23,421,967✔
776
                    //Apply corrosion
777
                    apply_corrosion(status);
22,046,705✔
778
                }
779
                else
780
                {
781
                  // Remove Corrosion
782
                  remove_corrosion(status);
2,700,801✔
783
                }
784
            }
785
            else
786
            {
787
                _DEBUG_MSG(2, "%s cannot take attack.\n", status_description(status).c_str());
174,799✔
788
                // Remove Corrosion
789
                remove_corrosion(status);
174,799✔
790
            }
791
        }
792
        fd->finalize_action();
66,782,981✔
793
        // Flurry
794
        if (can_act(status) && status->has_skill(Skill::flurry) && (status->m_skill_cd[Skill::flurry] == 0))
135,216,996✔
795
        {
796
#ifndef NQUEST
797
            if (status->m_player == 0)
798
            {
799
                fd->inc_counter(QuestType::skill_use, Skill::flurry);
800
            }
801
#endif
802
            _DEBUG_MSG(1, "%s activates Flurry x %d\n",
2,546,248✔
803
                    status_description(status).c_str(), status->skill_base_value(Skill::flurry));
804
            num_actions += status->skill_base_value(Skill::flurry);
2,420,474✔
805
            for (const auto & ss : skills)
9,681,896✔
806
            {
807
                Skill::Skill evolved_skill_id = static_cast<Skill::Skill>(ss.id + status->m_evolved_skill_offset[ss.id]);
7,261,422✔
808
                if (evolved_skill_id == Skill::flurry)
7,261,422✔
809
                {
810
                    status->m_skill_cd[ss.id] = ss.c;
2,420,474✔
811
                }
812
            }
813
        }
814
    }
815
}
62,256,470✔
816

817
struct PlayCard
818
{
819
    const Card* card;
820
    Field* fd;
821
    CardStatus* status;
822
    Storage<CardStatus>* storage;
823
    const unsigned actor_index;
824
    const CardStatus* actor_status;
825

826
    PlayCard(const Card* card_, Field* fd_, unsigned ai_, CardStatus* as_) :
22,656,843✔
827
        card{card_},
22,656,843✔
828
        fd{fd_},
22,656,843✔
829
        status{nullptr},
22,656,843✔
830
        storage{nullptr},
22,656,843✔
831
        actor_index{ai_},
22,656,843✔
832
        actor_status{as_}
22,656,843✔
833
    {}
834

835
    template <enum CardType::CardType type>
836
        CardStatus* op()
19,222,352✔
837
        {
838
            return op<type>(false);
×
839
        }
840

841
    template <enum CardType::CardType type>
842
        CardStatus* op(bool summoned)
22,656,843✔
843
        {
844
            setStorage<type>();
22,656,843✔
845
            placeCard<type>();
22,656,843✔
846
            status->m_summoned = summoned;
22,656,843✔
847

848
            unsigned played_faction_mask(0);
22,656,843✔
849
            unsigned same_faction_cards_count(0);
22,656,843✔
850
            bool bge_megamorphosis = fd->bg_effects[status->m_player][PassiveBGE::megamorphosis];
22,656,843✔
851
            //played_status = status;
852
            //played_card = card;
853
            if(__builtin_expect(fd->fixes[Fix::barrier_each_turn],true) && status->has_skill(Skill::barrier)){
22,656,843✔
854
                _DEBUG_MSG(1,"%s gets barrier protection %u per turn\n",status_description(status).c_str(),status->skill(Skill::barrier));
628,845✔
855
                status->m_protected += status->skill(Skill::barrier);
517,343✔
856
            }
857
            if (status->m_delay == 0)
22,656,843✔
858
            {
859
                    check_and_perform_bravery(fd,status);
1,335,206✔
860
                check_and_perform_valor(fd, status);
1,335,206✔
861
            }
862
            
863

864
            //refresh/init absorb
865
            if(status->has_skill(Skill::absorb))
22,656,843✔
866
            {
867
                status->m_absorption = status->skill_base_value(Skill::absorb);
1,287,948✔
868
            }
869

870

871
            // 1. Evaluate skill Allegiance & count assaults with same faction (structures will be counted later)
872
            // 2. Passive BGE Cold Sleep
873
            for (CardStatus* status_i : fd->players[status->m_player]->assaults.m_indirect)
88,376,015✔
874
            {
875
                if (status_i == status || !is_alive(status_i)) { continue; } // except itself
65,719,172✔
876
                //std::cout << status_description(status_i).c_str();
877
                _DEBUG_ASSERT(is_alive(status_i));
47,566,088✔
878
                if (bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction))
47,566,088✔
879
                {
880
                    ++ same_faction_cards_count;
19,733,540✔
881
                    unsigned allegiance_value = status_i->skill(Skill::allegiance);
19,733,540✔
882
                    if (__builtin_expect(allegiance_value, false) && !status->m_summoned)
19,733,540✔
883
                    {
884
                        _DEBUG_MSG(1, "%s activates Allegiance %u\n", status_description(status_i).c_str(), allegiance_value);
1,331,365✔
885
                        if (! status_i->m_sundered)
1,242,999✔
886
                        { status_i->m_perm_attack_buff += allegiance_value; }
1,188,328✔
887
                        status_i->ext_hp(allegiance_value);
1,242,999✔
888
                    }
889
                }
890
                if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::coldsleep], false)
47,566,088✔
891
                        && status_i->m_protected_stasis && can_be_healed(status_i))
113,285,260✔
892
                {
893
                    unsigned bge_value = (status_i->m_protected_stasis + 1) / 2;
2,180✔
894
                    _DEBUG_MSG(1, "Cold Sleep: %s heals itself for %u\n", status_description(status_i).c_str(), bge_value);
6,540✔
895
                    status_i->add_hp(bge_value);
2,180✔
896
                }
897
            }
898

899
            // Setup faction marks (bitmap) for stasis (skill Stasis / Passive BGE TemporalBacklash)
900
            // unless Passive BGE Megamorphosis is enabled
901
            if (__builtin_expect(!bge_megamorphosis, true))
22,656,843✔
902
            {
903
                played_faction_mask = (1u << card->m_faction);
22,591,845✔
904
                // do played card have stasis? mark this faction for stasis check
905
                if (__builtin_expect(status->skill(Skill::stasis), false)
22,591,845✔
906
                        || __builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::temporalbacklash] && status->skill(Skill::counter), false))
22,591,845✔
907
                {
908
                    fd->players[status->m_player]->stasis_faction_bitmap |= played_faction_mask;
4,066,512✔
909
                }
910
            }
911

912
            // Evaluate Passive BGE Oath-of-Loyalty
913
            unsigned allegiance_value;
914
            if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::oath_of_loyalty], false)
22,656,843✔
915
                    && ((allegiance_value = status->skill(Skill::allegiance)) > 0))
22,656,843✔
916
            {
917
                // count structures with same faction (except fortresses, dominions and other non-normal structures)
918
                for (CardStatus * status_i : fd->players[status->m_player]->structures.m_indirect)
2,057✔
919
                {
920
                    if ((status_i->m_card->m_category == CardCategory::normal)
851✔
921
                            && (bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)))
851✔
922
                    {
923
                        ++ same_faction_cards_count;
×
924
                    }
925
                }
926

927
                // apply Passive BGE Oath-of-Loyalty when multiplier isn't zero
928
                if (same_faction_cards_count)
1,206✔
929
                {
930
                    unsigned bge_value = allegiance_value * same_faction_cards_count;
432✔
931
                    _DEBUG_MSG(1, "Oath of Loyalty: %s activates Allegiance %u x %u = %u\n",
864✔
932
                            status_description(status).c_str(), allegiance_value, same_faction_cards_count, bge_value);
933
                    status->m_perm_attack_buff += bge_value;
432✔
934
                    status->ext_hp(bge_value);
432✔
935
                }
936
            }
937

938
            // summarize stasis when:
939
            //  1. Passive BGE Megamorphosis is enabled
940
            //  2. current faction is marked for it
941
            if ((card->m_delay > 0) && (card->m_type == CardType::assault)
21,321,637✔
942
                    && __builtin_expect(bge_megamorphosis || (fd->players[status->m_player]->stasis_faction_bitmap & played_faction_mask), false))
39,527,814✔
943
            {
944
                unsigned stacked_stasis = (bge_megamorphosis || (fd->players[status->m_player]->commander.m_card->m_faction == card->m_faction))
7,956,841✔
945
                    ? fd->players[status->m_player]->commander.skill(Skill::stasis)
14,522,124✔
946
                    : 0u;
947
#ifndef NDEBUG
948
                if (stacked_stasis > 0)
6,565,283✔
949
                {
950
                    _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
×
951
                            faction_names[card->m_faction].c_str(), stacked_stasis,
952
                            status_description(&fd->players[status->m_player]->commander).c_str(), stacked_stasis);
953
                }
954
#endif
955
                for (CardStatus * status_i : fd->players[status->m_player]->structures.m_indirect)
14,888,897✔
956
                {
957
                    if ((bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)) && is_alive(status_i))
6,888,908✔
958
                    {
959
                        stacked_stasis += status_i->skill(Skill::stasis);
2,019,826✔
960
#ifndef NDEBUG
961
                        if (status_i->skill(Skill::stasis) > 0)
2,019,826✔
962
                        {
963
                            _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
6,888,908✔
964
                                    faction_names[card->m_faction].c_str(), status_i->skill(Skill::stasis),
965
                                    status_description(status_i).c_str(), stacked_stasis);
966
                        }
967
#endif
968
                    }
969
                }
970
                for (CardStatus * status_i : fd->players[status->m_player]->assaults.m_indirect)
31,083,162✔
971
                {
972
                    if ((bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)) && is_alive(status_i))
23,083,173✔
973
                    {
974
                        stacked_stasis += status_i->skill(Skill::stasis);
17,845,027✔
975
#ifndef NDEBUG
976
                        if (status_i->skill(Skill::stasis) > 0)
17,845,027✔
977
                        {
978
                            _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
7,920,819✔
979
                                    faction_names[card->m_faction].c_str(), status_i->skill(Skill::stasis),
980
                                    status_description(status_i).c_str(), stacked_stasis);
981
                        }
982
#endif
983
                        if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::temporalbacklash] && status_i->skill(Skill::counter), false))
17,845,027✔
984
                        {
985
                            stacked_stasis += (status_i->skill(Skill::counter) + 1) / 2;
3,043✔
986
#ifndef NDEBUG
987
                            _DEBUG_MSG(2, "Temporal Backlash: + Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
23,086,216✔
988
                                    faction_names[card->m_faction].c_str(), (status_i->skill(Skill::counter) + 1) / 2,
989
                                    status_description(status_i).c_str(), stacked_stasis);
990
#endif
991
                        }
992
                    }
993
                }
994
                status->m_protected_stasis = stacked_stasis;
7,999,989✔
995
#ifndef NDEBUG
996
                if (stacked_stasis > 0)
7,999,989✔
997
                {
998
                    _DEBUG_MSG(1, "%s gains %u stasis protection\n",
6,561,060✔
999
                            status_description(status).c_str(), stacked_stasis);
1000
                }
1001
#endif
1002
                // no more stasis for current faction: do unmark (if no Passive BGE Megamorphosis)
1003
                if (__builtin_expect(!bge_megamorphosis, true) && __builtin_expect(!stacked_stasis, false))
7,999,989✔
1004
                {
1005
                    fd->players[status->m_player]->stasis_faction_bitmap &= ~played_faction_mask;
1,671,523✔
1006
                    _DEBUG_MSG(1, "- Stasis [%s]: no more units with stasis from %s\n",
1,680,786✔
1007
                            faction_names[card->m_faction].c_str(),status_description(&fd->players[status->m_player]->commander).c_str());
1008
                }
1009

1010
            }
1011
            //Devotion BGE
1012
            if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::devotion], false)
22,656,843✔
1013
                    && !summoned && card->m_category == CardCategory::normal
64,646✔
1014
                    && fd->players[status->m_player]->commander.m_card->m_faction == card->m_faction)
22,716,679✔
1015
            {
1016
                unsigned devotion_percent = fd->bg_effects[status->m_player][PassiveBGE::devotion];
9,349✔
1017
                unsigned bge_buff = (card->m_health*devotion_percent+99)/100;
9,349✔
1018
                _DEBUG_MSG(1, "Devotion %s: Gains %u HP\n",
18,698✔
1019
                        status_description(status).c_str(), bge_buff);
1020
                status->ext_hp(bge_buff); // <bge_value>% bonus health (rounded up)
9,349✔
1021
            }
1022

1023

1024
            // resolve On-Play skills
1025
            // Fix Death on BGE: [On Play] skills during BGE phase can be invoked only by means of [On Death] trigger
1026
            if (__builtin_expect(fd->fixes[Fix::death_from_bge] && (fd->current_phase != Field::bge_phase), true))
22,656,843✔
1027
            {
1028
                for (const auto& ss: card->m_skills_on_play)
26,364,775✔
1029
                {
1030
                    _DEBUG_MSG(2, "On Play %s: Preparing (tail) skill %s\n",
3,707,932✔
1031
                            status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
1032
                    fd->skill_queue.emplace_back(status, ss);
3,707,932✔
1033
                }
1034
            }
1035
            else
1036
            {
1037
                _DEBUG_MSG(2, "Death from BGE Fix: suppress [On Play] skills invoked by [On Death] summon\n");
×
1038
            }
1039

1040
            return status;
22,656,843✔
1041
        }
1042

1043
    template <enum CardType::CardType>
1044
        void setStorage()
1045
        {
1046
        }
1047

1048
    template <enum CardType::CardType type>
1049
        void placeCard()
22,656,843✔
1050
        {
1051
            status = &storage->add_back();
22,656,843✔
1052
            status->set(card);
22,656,843✔
1053
            status->m_index = storage->size() - 1;
22,656,843✔
1054
            status->m_player = actor_index;
22,656,843✔
1055
            status->m_field = fd;
22,656,843✔
1056
#ifndef NQUEST
1057
            if (actor_index == 0)
1058
            {
1059
                if (card->m_type == CardType::assault)
1060
                {
1061
                    fd->inc_counter(QuestType::faction_assault_card_use, card->m_faction);
1062
                }
1063
                fd->inc_counter(QuestType::type_card_use, type);
1064
            }
1065
#endif
1066
            _DEBUG_MSG(1, "%s plays %s %u [%s]\n",
25,135,089✔
1067
                    status_description(actor_status).c_str(), cardtype_names[type].c_str(),
1068
                    static_cast<unsigned>(storage->size() - 1), card_description(fd->cards, card).c_str());
1069
        }
22,656,843✔
1070
};
1071
// assault
1072
    template <>
1073
void PlayCard::setStorage<CardType::assault>()
18,068,669✔
1074
{
1075
    storage = &fd->players[actor_index]->assaults;
18,068,669✔
1076
}
×
1077
// structure
1078
    template <>
1079
void PlayCard::setStorage<CardType::structure>()
4,588,174✔
1080
{
1081
    storage = &fd->players[actor_index]->structures;
4,588,174✔
1082
}
×
1083

1084
// Check if a skill actually proc'ed.
1085
    template<Skill::Skill skill_id>
1086
inline bool skill_check(Field* fd, CardStatus* c, CardStatus* ref)
253,616,226✔
1087
{
1088
    return is_defensive_skill(skill_id) || is_alive(c);
2,648,801✔
1089
}
1090

1091
    template<>
1092
inline bool skill_check<Skill::heal>(Field* fd, CardStatus* c, CardStatus* ref)
73,274,581✔
1093
{
1094
    return can_be_healed(c);
38,259✔
1095
}
1096

1097
    template<>
1098
inline bool skill_check<Skill::mend>(Field* fd, CardStatus* c, CardStatus* ref)
158,135✔
1099
{
1100
    return can_be_healed(c);
×
1101
}
1102

1103
    template<>
1104
inline bool skill_check<Skill::rally>(Field* fd, CardStatus* c, CardStatus* ref)
37,484,006✔
1105
{
1106
    return !c->m_sundered;
37,484,006✔
1107
}
1108

1109
    template<>
1110
inline bool skill_check<Skill::overload>(Field* fd, CardStatus* c, CardStatus* ref)
933,380✔
1111
{
1112
    return is_active(c) && !c->m_overloaded && !has_attacked(c);
730,760✔
1113
}
1114

1115
    template<>
1116
inline bool skill_check<Skill::jam>(Field* fd, CardStatus* c, CardStatus* ref)
11,225,797✔
1117
{
1118
    if(__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::ironwill], false) && c->has_skill(Skill::armor))return false;
11,225,797✔
1119
    // active player performs Jam
1120
    if (fd->tapi == ref->m_player)
11,223,584✔
1121
    { return is_active_next_turn(c); }
11,194,881✔
1122

1123

1124
    // inactive player performs Jam
1125
    return will_activate_this_turn(c);
28,703✔
1126
}
1127

1128
    template<>
1129
inline bool skill_check<Skill::leech>(Field* fd, CardStatus* c, CardStatus* ref)
51,608✔
1130
{
1131
    return can_be_healed(c) || (fd->fixes[Fix::leech_increase_max_hp] && is_alive(c));
43,150✔
1132
}
1133

1134
    template<>
1135
inline bool skill_check<Skill::coalition>(Field* fd, CardStatus* c, CardStatus* ref)
1136
{
1137
    return is_active(c);
1138
}
1139

1140
    template<>
1141
inline bool skill_check<Skill::payback>(Field* fd, CardStatus* c, CardStatus* ref)
2,856,344✔
1142
{
1143
    return (ref->m_card->m_type == CardType::assault);
2,856,344✔
1144
}
1145

1146
    template<>
1147
inline bool skill_check<Skill::revenge>(Field* fd, CardStatus* c, CardStatus* ref)
1148
{
1149
    return skill_check<Skill::payback>(fd, c, ref);
1150
}
1151

1152
    template<>
1153
inline bool skill_check<Skill::tribute>(Field* fd, CardStatus* c, CardStatus* ref)
121,483,684✔
1154
{
1155
    return (ref->m_card->m_type == CardType::assault) && (c != ref);
121,483,684✔
1156
}
1157

1158
    template<>
1159
inline bool skill_check<Skill::refresh>(Field* fd, CardStatus* c, CardStatus* ref)
3,338,926✔
1160
{
1161
    return can_be_healed(c);
3,338,926✔
1162
}
1163

1164
    template<>
1165
inline bool skill_check<Skill::drain>(Field* fd, CardStatus* c, CardStatus* ref)
750,604✔
1166
{
1167
    return can_be_healed(c);
750,604✔
1168
}
1169

1170
    template<>
1171
inline bool skill_check<Skill::mark>(Field* fd, CardStatus* c, CardStatus* ref)
48,674✔
1172
{
1173
    return (ref->m_card->m_type == CardType::assault);
48,674✔
1174
}
1175

1176
    template<>
1177
inline bool skill_check<Skill::disease>(Field* fd, CardStatus* c, CardStatus* ref)
272,724✔
1178
{
1179
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
272,724✔
1180
}
1181
    template<>
1182
inline bool skill_check<Skill::inhibit>(Field* fd, CardStatus* c, CardStatus* ref)
2,958,854✔
1183
{
1184
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
2,958,854✔
1185
}
1186
    template<>
1187
inline bool skill_check<Skill::sabotage>(Field* fd, CardStatus* c, CardStatus* ref)
432,193✔
1188
{
1189
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
432,193✔
1190
}
1191
inline unsigned remove_disease(CardStatus* status, unsigned heal)
37,290,859✔
1192
{
1193
    unsigned remaining_heal(heal);
37,290,859✔
1194
    if(__builtin_expect(status->m_diseased == 0,true))
37,290,859✔
1195
    {
1196
        //skip
1197
    }
1198
    else if (heal > status->m_diseased)
96,435✔
1199
    {
1200
        _DEBUG_MSG(1, "%s disease-blocked %u heal\n", status_description(status).c_str(), status->m_diseased);
25,272✔
1201
        remaining_heal = heal - status->m_diseased;
25,272✔
1202
        status->m_diseased = 0;
25,272✔
1203
    }
1204
    else
1205
    {
1206
        _DEBUG_MSG(1, "%s disease-blocked %u heal\n", status_description(status).c_str(), heal);
71,163✔
1207
        status->m_diseased -= heal;
71,163✔
1208
        remaining_heal = 0;
71,163✔
1209
    }
1210
    return remaining_heal;
37,290,859✔
1211
}
1212

1213
// Field is currently not needed for remove_absorption, but is here for similiar structure as remove_hp
1214
inline unsigned remove_absorption(Field* fd, CardStatus* status, unsigned dmg)
25,479,174✔
1215
{
1216
    return remove_absorption(status,dmg);
25,479,174✔
1217
}
1218
/**
1219
 * @brief Remove absorption from a CardStatus
1220
 * 
1221
 * @param status CardStatus to remove absorption from
1222
 * @param dmg damage to remove absorption from
1223
 * @return unsigned remaining damage after absorption is removed
1224
 */
1225
inline unsigned remove_absorption(CardStatus* status, unsigned dmg)
25,479,174✔
1226
{
1227
    unsigned remaining_dmg(dmg);
25,479,174✔
1228
    if(__builtin_expect(status->m_absorption == 0,true))
25,479,174✔
1229
    {
1230
        //skip
1231
    }
1232
    else if(dmg > status->m_absorption)
2,534,601✔
1233
    {
1234
        _DEBUG_MSG(1, "%s absorbs %u damage\n", status_description(status).c_str(), status->m_absorption);
343,404✔
1235
        remaining_dmg = dmg - status->m_absorption;
343,404✔
1236
        status->m_absorption = 0;
343,404✔
1237
    }
1238
    else
1239
    {
1240
        _DEBUG_MSG(1, "%s absorbs %u damage\n", status_description(status).c_str(), dmg);
2,191,197✔
1241
        status->m_absorption -= dmg;
2,191,197✔
1242
        remaining_dmg = 0;
2,191,197✔
1243
    }
1244
    return remaining_dmg;
25,479,174✔
1245
}
1246

1247
void remove_hp(Field* fd, CardStatus* status, unsigned dmg)
37,466,685✔
1248
{
1249
    if (__builtin_expect(!dmg, false)) { return; }
37,466,685✔
1250
    _DEBUG_ASSERT(is_alive(status));
26,965,373✔
1251
    _DEBUG_MSG(2, "%s takes %u damage\n", status_description(status).c_str(), dmg);
26,965,373✔
1252
    status->m_hp = safe_minus(status->m_hp, dmg);
26,965,373✔
1253
    if (fd->current_phase < Field::end_phase && status->has_skill(Skill::barrier))
26,965,373✔
1254
    {
1255
        ++fd->damaged_units_to_times[status];
292,108✔
1256
        _DEBUG_MSG(2, "%s damaged %u times\n",
292,108✔
1257
                status_description(status).c_str(), fd->damaged_units_to_times[status]);
1258
    }
1259
    if (status->m_hp == 0)
26,965,373✔
1260
    {
1261
#ifndef NQUEST
1262
        if (status->m_player == 1)
1263
        {
1264
            if (status->m_card->m_type == CardType::assault)
1265
            {
1266
                fd->inc_counter(QuestType::faction_assault_card_kill, status->m_card->m_faction);
1267
            }
1268
            fd->inc_counter(QuestType::type_card_kill, status->m_card->m_type);
1269
        }
1270
#endif
1271
        _DEBUG_MSG(1, "%s dies\n", status_description(status).c_str());
9,941,636✔
1272
        _DEBUG_ASSERT(status->m_card->m_type != CardType::commander);
9,941,636✔
1273
        fd->killed_units.push_back(status);
9,941,636✔
1274
        ++fd->players[status->m_player]->total_cards_destroyed;
9,941,636✔
1275
        if(!status->m_summoned)++fd->players[status->m_player]->total_nonsummon_cards_destroyed;
9,941,636✔
1276
        if (__builtin_expect((status->m_player == 0) && (fd->players[0]->deck->vip_cards.count(status->m_card->m_id)), false))
13,007,987✔
1277
        {
1278
            fd->players[0]->commander.m_hp = 0;
×
1279
            fd->end = true;
×
1280
        }
1281
    }
1282
}
1283

1284
inline bool is_it_dead(CardStatus& c)
153,920,740✔
1285
{
1286
    if (c.m_hp == 0) // yes it is
153,920,740✔
1287
    {
1288
        _DEBUG_MSG(1, "Dead and removed: %s\n", status_description(&c).c_str());
9,941,636✔
1289
        return true;
9,941,636✔
1290
    }
1291
    return false; // nope still kickin'
1292
}
1293

1294
inline bool is_it_dominion(CardStatus* c)
×
1295
{
1296
    return (c->m_card->m_category == CardCategory::dominion_alpha);
×
1297
}
1298

1299
inline void remove_dead(Storage<CardStatus>& storage)
78,331,112✔
1300
{
1301
    storage.remove(is_it_dead);
78,331,112✔
1302
}
1303

1304
void cooldown_skills(CardStatus * status)
50,958,167✔
1305
{
1306
    for (const auto & ss : status->m_card->m_skills)
197,066,032✔
1307
    {
1308
        if (status->m_skill_cd[ss.id] > 0)
146,107,865✔
1309
        {
1310
            _DEBUG_MSG(2, "%s reduces timer (%u) of skill %s\n",
3,440,463✔
1311
                    status_description(status).c_str(), status->m_skill_cd[ss.id], skill_names[ss.id].c_str());
3,440,463✔
1312
            -- status->m_skill_cd[ss.id];
3,440,463✔
1313
        }
1314
    }
1315
}
50,958,167✔
1316
/**
1317
 * Handle:
1318
 * Absorb, (Activation)Summon, Bravery, (Initial)Valor, Inhibit, Sabotage, Disease, Enhance, (Cooldown/Every X) Reductions
1319
 * 
1320
 * Does not handle these skills for newly summoned units ( i.e. valor, barrier)
1321
 **/
1322
void turn_start_phase_update(Field*fd, CardStatus * status)
61,574,790✔
1323
{
1324
            //apply Absorb + Triggered\{Valor} Enhances
1325
            check_and_perform_early_enhance(fd,status);
123,149,580✔
1326
            if(fd->fixes[Fix::enhance_early])check_and_perform_later_enhance(fd,status);
61,574,790✔
1327
            //refresh absorb
1328
            if(status->has_skill(Skill::absorb))
61,574,790✔
1329
            {
1330
                status->m_absorption = status->skill_base_value(Skill::absorb);
4,126,662✔
1331
            }
1332
            if(__builtin_expect(fd->fixes[Fix::barrier_each_turn],true) && status->has_skill(Skill::barrier)){
61,574,790✔
1333
                _DEBUG_MSG(1,"%s gets barrier protection %u per turn\n",status_description(status).c_str(),status->skill(Skill::barrier));
1,364,933✔
1334
                status->m_protected += status->skill(Skill::barrier);
1,364,933✔
1335
            }
1336
            check_and_perform_bravery(fd,status);
61,574,790✔
1337
            if (status->m_delay > 0)
61,574,790✔
1338
            {
1339
                _DEBUG_MSG(1, "%s reduces its timer\n", status_description(status).c_str());
30,199,401✔
1340
                --status->m_delay;
30,199,401✔
1341
                if (status->m_delay == 0)
30,199,401✔
1342
                {
1343
                    check_and_perform_valor(fd, status);
13,221,353✔
1344
                    if(status->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate)check_and_perform_summon(fd, status);
13,221,353✔
1345
                }
1346
            }
1347
            else
1348
            {
1349
                cooldown_skills(status);
31,375,389✔
1350
            }
1351
}
61,574,790✔
1352

1353
void turn_start_phase(Field* fd)
19,582,778✔
1354
{
1355
    // Active player's commander card:
1356
    cooldown_skills(&fd->tap->commander);
19,582,778✔
1357
    //grab assaults before new ones get summoned
1358
    auto& assaults(fd->tap->assaults);
19,582,778✔
1359
    unsigned end(assaults.size());
19,582,778✔
1360

1361
    //Perform early enhance for commander
1362
    check_and_perform_early_enhance(fd,&fd->tap->commander);
39,165,556✔
1363
    if(fd->fixes[Fix::enhance_early])check_and_perform_later_enhance(fd,&fd->tap->commander);
19,582,778✔
1364

1365
    // Active player's structure cards:
1366
    // update index
1367
    // reduce delay; reduce skill cooldown
1368
    {
19,582,778✔
1369
        auto& structures(fd->tap->structures);
19,582,778✔
1370
        for(unsigned index(0); index < structures.size(); ++index)
42,551,884✔
1371
        {
1372
            CardStatus * status = &structures[index];
22,969,106✔
1373
            status->m_index = index;
22,969,106✔
1374
            turn_start_phase_update(fd,status);
22,969,106✔
1375
        }
1376
    }
1377
    // Active player's assault cards:
1378
    // update index
1379
    // reduce delay; reduce skill cooldown
1380
    {
1381
        for(unsigned index(0); index < end; ++index)
58,188,462✔
1382
        {
1383
            CardStatus * status = &assaults[index];
38,605,684✔
1384
            status->m_index = index;
38,605,684✔
1385
            turn_start_phase_update(fd,status);
38,605,684✔
1386
        }
1387
    }
1388
    // Defending player's structure cards:
1389
    // update index
1390
    {
19,582,778✔
1391
        auto& structures(fd->tip->structures);
19,582,778✔
1392
        for(unsigned index(0), end(structures.size()); index < end; ++index)
43,334,732✔
1393
        {
1394
            CardStatus& status(structures[index]);
23,751,954✔
1395
            status.m_index = index;
23,751,954✔
1396
        }
1397
    }
1398
    // Defending player's assault cards:
1399
    // update index
1400
    {
19,582,778✔
1401
        auto& assaults(fd->tip->assaults);
19,582,778✔
1402
        for(unsigned index(0), end(assaults.size()); index < end; ++index)
67,035,065✔
1403
        {
1404
            CardStatus& status(assaults[index]);
47,452,287✔
1405
            status.m_index = index;
47,452,287✔
1406
        }
1407
    }
1408
}
19,582,778✔
1409

1410
void turn_end_phase(Field* fd)
19,582,778✔
1411
{
1412
    // Inactive player's assault cards:
1413
    {
19,582,778✔
1414
        auto& assaults(fd->tip->assaults);
19,582,778✔
1415
        for(unsigned index(0), end(assaults.size()); index < end; ++ index)
67,125,054✔
1416
        {
1417
            CardStatus& status(assaults[index]);
47,542,276✔
1418
            if (status.m_hp <= 0)
47,542,276✔
1419
            {
1420
                continue;
8,104,207✔
1421
            }
1422
            status.m_enfeebled = 0;
39,438,069✔
1423
            status.m_protected = 0;
39,438,069✔
1424
            std::memset(status.m_primary_skill_offset, 0, sizeof status.m_primary_skill_offset);
39,438,069✔
1425
            std::memset(status.m_evolved_skill_offset, 0, sizeof status.m_evolved_skill_offset);
39,438,069✔
1426
            std::memset(status.m_enhanced_value, 0, sizeof status.m_enhanced_value);
39,438,069✔
1427
            status.m_evaded = 0;  // so far only useful in Inactive turn
39,438,069✔
1428
            status.m_paybacked = 0;  // ditto
39,438,069✔
1429
            status.m_entrapped = 0;
39,438,069✔
1430
        }
1431
    }
1432
    // Inactive player's structure cards:
1433
    {
19,582,778✔
1434
        auto& structures(fd->tip->structures);
19,582,778✔
1435
        for(unsigned index(0), end(structures.size()); index < end; ++ index)
43,338,341✔
1436
        {
1437
            CardStatus& status(structures[index]);
23,755,563✔
1438
            if (status.m_hp <= 0)
23,755,563✔
1439
            {
1440
                continue;
961,953✔
1441
            }
1442
            // reset the structure's (barrier) protect
1443
            status.m_protected = 0;
22,793,610✔
1444
            status.m_evaded = 0;  // so far only useful in Inactive turn
22,793,610✔
1445
        }
1446
    }
1447

1448
    // Active player's assault cards:
1449
    {
19,582,778✔
1450
        auto& assaults(fd->tap->assaults);
19,582,778✔
1451
        for(unsigned index(0), end(assaults.size()); index < end; ++ index)
76,167,022✔
1452
        {
1453
            CardStatus& status(assaults[index]);
56,584,244✔
1454
            if (status.m_hp <= 0)
56,584,244✔
1455
            {
1456
                continue;
857,796✔
1457
            }
1458
            unsigned refresh_value = status.skill(Skill::refresh) + (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::crackdown],false)?(status.skill(Skill::subdue)+1)/2:0); //BGE: crackdown refresh+=subdue/2
55,726,448✔
1459

1460
            if (refresh_value && skill_check<Skill::refresh>(fd, &status, nullptr))
55,726,448✔
1461
            {
1462
                _DEBUG_MSG(1, "%s refreshes %u health\n", status_description(&status).c_str(), refresh_value);
142,731✔
1463
                status.add_hp(refresh_value);
142,731✔
1464
            }
1465
            if (status.m_poisoned > 0)
55,726,448✔
1466
            {
1467
                if(! __builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
95,844✔
1468
                {
1469
                    unsigned poison_dmg = remove_absorption(fd,&status,status.m_poisoned + status.m_enfeebled);
×
1470
                    poison_dmg = safe_minus(poison_dmg, status.protected_value());
×
1471
                    if (poison_dmg > 0)
×
1472
                    {
1473
#ifndef NQUEST
1474
                    if (status.m_player == 1)
1475
                    {
1476
                        fd->inc_counter(QuestType::skill_damage, Skill::poison, 0, poison_dmg);
1477
                    }
1478
#endif
1479
                        _DEBUG_MSG(1, "%s takes poison damage %u\n", status_description(&status).c_str(), poison_dmg);
×
1480
                        remove_hp(fd, &status, poison_dmg);  // simultaneous
×
1481
                    }
1482
                }
1483
                else {
1484
                    unsigned poison_dmg = status.m_poisoned;
95,844✔
1485
                    _DEBUG_MSG(1, "%s takes poison damage %u\n", status_description(&status).c_str(), poison_dmg);
95,844✔
1486
                    remove_hp(fd, &status, poison_dmg);  // simultaneous
95,844✔
1487
                    status.m_poisoned = status.m_poisoned - (poison_dmg+1)/2;
95,844✔
1488
                }
1489
            }
1490
            // end of the opponent's next turn for enemy units
1491
            status.m_temp_attack_buff = 0;
55,726,448✔
1492
            status.m_jammed = false;
55,726,448✔
1493
            status.m_enraged = 0;
55,726,448✔
1494
            status.m_sundered = false;
55,726,448✔
1495
            status.m_inhibited = 0;
55,726,448✔
1496
            status.m_sabotaged = 0;
55,726,448✔
1497
            status.m_tributed = 0;
55,726,448✔
1498
            status.m_overloaded = false;
55,726,448✔
1499
            status.m_step = CardStep::none;
55,726,448✔
1500
        }
1501
    }
1502
    // Active player's structure cards:
1503
    // nothing so far
1504

1505
    prepend_on_death(fd);  // poison
19,582,778✔
1506
    resolve_skill(fd);
19,582,778✔
1507
    remove_dead(fd->tap->assaults);
19,582,778✔
1508
    remove_dead(fd->tap->structures);
19,582,778✔
1509
    remove_dead(fd->tip->assaults);
19,582,778✔
1510
    remove_dead(fd->tip->structures);
19,582,778✔
1511
}
19,582,778✔
1512

1513
//---------------------- $50 attack by assault card implementation -------------
1514
/**
1515
 * @brief Return the damage dealt to the attacker (att) by defender (def) through counter skill
1516
 * The counter is increased by the attacker's enfeebled value, while the attacker's protected value is subtracted from the damage.
1517
 * The absorption is removed from the damage before the protected value is subtracted.
1518
 * 
1519
 * @param fd Field pointer 
1520
 * @param att attacker CardStatus
1521
 * @param def defender CardStatus
1522
 * @return unsigned 
1523
 */
1524
inline unsigned counter_damage(Field* fd, CardStatus* att, CardStatus* def)
2,132,771✔
1525
{
1526
    _DEBUG_ASSERT(att->m_card->m_type == CardType::assault);
2,132,771✔
1527
    _DEBUG_ASSERT(def->skill(Skill::counter) > 0); // counter skill must be present otherwise enfeeble is wrongly applied
2,132,771✔
1528

1529
    return safe_minus(remove_absorption(fd,att,def->skill(Skill::counter) + att->m_enfeebled), att->protected_value());
2,132,771✔
1530
}
1531

1532
inline CardStatus* select_first_enemy_wall(Field* fd)
8,334,770✔
1533
{
1534
    for(unsigned i(0); i < fd->tip->structures.size(); ++i)
15,635,423✔
1535
    {
1536
        CardStatus* c(&fd->tip->structures[i]);
8,090,559✔
1537
        if (c->has_skill(Skill::wall) && is_alive(c)) { return c; }
8,090,559✔
1538
    }
1539
    return nullptr;
1540
}
1541

1542
inline CardStatus* select_first_enemy_assault(Field* fd)
1543
{
1544
    for(unsigned i(0); i < fd->tip->assaults.size(); ++i)
48,330✔
1545
    {
1546
        CardStatus* c(&fd->tip->assaults[i]);
38,042✔
1547
        if (is_alive(c)) { return c; }
38,042✔
1548
    }
1549
    return nullptr;
1550
}
1551

1552

1553
inline bool alive_assault(Storage<CardStatus>& assaults, unsigned index)
23,421,967✔
1554
{
1555
    return(assaults.size() > index && is_alive(&assaults[index]));
16,750,468✔
1556
}
1557

1558
void remove_commander_hp(Field* fd, CardStatus& status, unsigned dmg)
7,543,113✔
1559
{
1560
    _DEBUG_ASSERT(is_alive(&status));
7,543,113✔
1561
    _DEBUG_ASSERT(status.m_card->m_type == CardType::commander);
7,543,113✔
1562
    _DEBUG_MSG(2, "%s takes %u damage\n", status_description(&status).c_str(), dmg);
7,543,113✔
1563
    status.m_hp = safe_minus(status.m_hp, dmg);
7,543,113✔
1564
    if (status.m_hp == 0)
7,543,113✔
1565
    {
1566
        _DEBUG_MSG(1, "%s dies\n", status_description(&status).c_str());
1,375,262✔
1567
        fd->end = true;
1,375,262✔
1568
    }
1569
}
7,543,113✔
1570

1571
//------------------------------------------------------------------------------
1572
// implementation of one attack by an assault card, against either an enemy
1573
// assault card, the first enemy wall, or the enemy commander.
1574
struct PerformAttack
1575
{
1576
    Field* fd;
1577
    CardStatus* att_status;
1578
    CardStatus* def_status;
1579
    unsigned att_dmg;
1580

1581
    PerformAttack(Field* fd_, CardStatus* att_status_, CardStatus* def_status_) :
23,421,967✔
1582
        fd(fd_), att_status(att_status_), def_status(def_status_), att_dmg(0)
23,421,967✔
1583
    {}
1584
    /**
1585
     * @brief Perform the attack
1586
     * 
1587
     * New Evaluation order:
1588
     *  Flying
1589
     *  Subdue
1590
     *  Attack Damage
1591
     *  On-Attacked Skills
1592
     *  Counter
1593
     *  Leech
1594
     *  Drain
1595
     *  Berserk
1596
     *  Mark
1597
     * 
1598
     * Old Evaluation order:
1599
     *  check flying
1600
     *  modify damage
1601
     *  deal damage
1602
     *  assaults only: (poison,inihibit, sabotage)
1603
     *  on-attacked skills
1604
     *  counter, berserk
1605
     *  assaults only: (leech if still alive)
1606
     *  bge
1607
     *  subdue
1608
     * 
1609
     * @tparam def_cardtype 
1610
     * @return unsigned 
1611
     */
1612
    template<enum CardType::CardType def_cardtype>
1613
        unsigned op()
23,421,967✔
1614
        {
1615
            // Bug fix? 2023-04-03 a card with zero attack power can not attack and won't trigger subdue
1616
            // Confirmed subdue behaviour by MK 2023-07-12
1617
            if(att_status->attack_power()== 0) { return 0; }
23,421,967✔
1618

1619

1620
            if(__builtin_expect(def_status->has_skill(Skill::flying),false) && fd->flip()) {
23,421,967✔
1621
                _DEBUG_MSG(1, "%s flies away from %s\n",
102,624✔
1622
                        status_description(def_status).c_str(),
1623
                        status_description(att_status).c_str());
1624
                return 0;
51,312✔
1625
            }
1626

1627
            if ( __builtin_expect(fd->fixes[Fix::subdue_before_attack],true)) {
23,370,655✔
1628
                perform_subdue(fd, att_status, def_status);
23,370,655✔
1629
                if(att_status->attack_power() == 0) // MK - 05/01/2023 6:58 AM: "Previously, when an attack was made, it checked if it had 0 attack and canceled. Now, after that check, it applies Subdue and checks again if it has 0 attack, and then cancels the rest of the attack.Previously, when an attack was made, it checked if it had 0 attack and canceled. Now, after that check, it applies Subdue and checks again if it has 0 attack, and then cancels the rest of the attack."
23,370,655✔
1630
                { 
1631
                    return 0; 
1632
                }
1633
            }
1634
            
1635
            // APN Bug fix for subdue not reducing attack damage
1636
            // https://github.com/APN-Pucky/tyrant_optimize/issues/79
1637
            unsigned pre_modifier_dmg = att_status->attack_power();
23,365,930✔
1638

1639
            modify_attack_damage<def_cardtype>(pre_modifier_dmg);
23,365,930✔
1640

1641
            if(att_dmg) {
23,365,930✔
1642
                // Deal the attack damage
1643
                attack_damage<def_cardtype>();
19,434,780✔
1644

1645
                // Game Over -> return
1646
                if (__builtin_expect(fd->end, false)) { return att_dmg; }
19,434,780✔
1647
                damage_dependant_pre_oa<def_cardtype>();
11,101,761✔
1648
            }
1649
            // on attacked does also trigger if the attack damage is blocked to zero
1650
            on_attacked<def_cardtype>();
21,990,668✔
1651

1652
            // only if alive attacker
1653
            if (is_alive(att_status) && (__builtin_expect(fd->fixes[Fix::counter_without_damage],true) || att_dmg) )
21,990,668✔
1654
            {
1655
                perform_counter<def_cardtype>(fd, att_status, def_status);    
21,978,643✔
1656
            }
1657
            if (is_alive(att_status) && (__builtin_expect(fd->fixes[Fix::corrosive_protect_armor],true) ||att_dmg )) // MK - 05/01/2023 6:58 AM: "Corrosive will apply to an attacker even if the attack did 0 damage, just like new Counter"
21,990,668✔
1658
            {
1659
                perform_corrosive(fd, att_status, def_status);
21,234,598✔
1660
            }
1661
            if (is_alive(att_status) && att_dmg)
21,990,668✔
1662
            {
1663
                // Bug fix? 2023-04-03 leech after counter but before drain/berserk
1664
                // Skill: Leech
1665
                do_leech<def_cardtype>();
10,922,008✔
1666
            }
1667

1668
            // Bug fix? 2023-04-03 drain now part of the PerformAttack.op() instead of attack_phase, like hunt.
1669
            // perform swipe/drain
1670
            perform_swipe_drain<def_cardtype>(fd, att_status, def_status, att_dmg);
21,990,668✔
1671

1672
            if (is_alive(att_status) && att_dmg) {
21,990,668✔
1673
                perform_berserk(fd, att_status, def_status);
17,373,405✔
1674
            }
1675
            if (is_alive(att_status) )  {
21,990,668✔
1676
                perform_poison(fd, att_status, def_status);
21,234,598✔
1677
            }
1678

1679
            if (is_alive(att_status) && att_dmg) {
21,990,668✔
1680
                perform_mark(fd, att_status, def_status);
17,373,405✔
1681

1682
                perform_bge_heroism<def_cardtype>(fd, att_status, def_status);
17,373,405✔
1683
                perform_bge_devour<def_cardtype>(fd, att_status, def_status);
17,373,405✔
1684

1685
                if ( __builtin_expect(!fd->fixes[Fix::subdue_before_attack],false)) {
17,373,405✔
1686
                    perform_subdue(fd, att_status, def_status);
×
1687
                }
1688
            }
1689
            // perform hunt
1690
            perform_hunt(fd, att_status, def_status);
21,990,668✔
1691

1692
            return att_dmg;
21,990,668✔
1693
        }
1694

1695
        /**
1696
         * @brief Modify attack damage
1697
         * This setts att_dmg in PerformAttack.
1698
         * 
1699
         * @tparam CardType::CardType 
1700
         * @param pre_modifier_dmg base damage
1701
         */
1702
    template<enum CardType::CardType>
1703
        void modify_attack_damage(unsigned pre_modifier_dmg)
23,365,930✔
1704
        {
1705
            _DEBUG_ASSERT(att_status->m_card->m_type == CardType::assault);
23,365,930✔
1706
            att_dmg = pre_modifier_dmg;
23,365,930✔
1707
            if (att_dmg == 0)
23,365,930✔
1708
            { return; }
×
1709
#ifndef NDEBUG
1710
            std::string desc;
23,365,930✔
1711
#endif
1712
            auto& att_assaults = fd->tap->assaults; // (active) attacker assaults
23,365,930✔
1713
            auto& def_assaults = fd->tip->assaults; // (inactive) defender assaults
23,365,930✔
1714
            unsigned legion_value = 0;
23,365,930✔
1715
            unsigned coalition_value = 0;
23,365,930✔
1716

1717
            // Enhance damage (if additional damage isn't prevented)
1718
            if (! att_status->m_sundered)
23,365,930✔
1719
            {
1720
                // Skill: Mark
1721
                unsigned marked_value = def_status->m_marked;
21,982,550✔
1722
                if(marked_value)
21,982,550✔
1723
                {
1724
#ifndef NDEBUG
1725
                    if (debug_print > 0) { desc += "+" + tuo::to_string(marked_value) + "(mark)"; }
42,164✔
1726
#endif
1727
                    att_dmg += marked_value;
10,541✔
1728
                }
1729
                // Skill: Legion
1730
                unsigned legion_base = att_status->skill(Skill::legion);
21,982,550✔
1731
                if (__builtin_expect(legion_base, false))
21,982,550✔
1732
                {
1733
                    unsigned itr_idx, till_idx;
1734
                    bool bge_megamorphosis = fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis] && !fd->fixes[Fix::legion_under_mega];
2,859,551✔
1735
                    //scan all assaults for Global Legion
1736
                    itr_idx = 0;
2,859,551✔
1737
                    till_idx = att_assaults.size() - 1;
2,859,551✔
1738
                    for (; itr_idx <= till_idx; ++ itr_idx)
18,926,265✔
1739
                    {
1740
                        if(itr_idx == att_status->m_index)continue; //legion doesn't count itself, unlike coalition
16,066,714✔
1741
                        legion_value += is_alive(&att_assaults[itr_idx])
26,390,639✔
1742
                          && (bge_megamorphosis || (att_assaults[itr_idx].m_card->m_faction == att_status->m_card->m_faction));
13,207,163✔
1743
                    }
1744
                    if (legion_value)
2,859,551✔
1745
                    {
1746
                        legion_value *= legion_base;
2,690,115✔
1747
#ifndef NDEBUG
1748
                        if (debug_print > 0) { desc += "+" + tuo::to_string(legion_value) + "(legion)"; }
2,948,277✔
1749
#endif
1750
                        att_dmg += legion_value;
2,690,115✔
1751
                    }
1752
                }
1753

1754
                // Skill: Coalition
1755
                unsigned coalition_base = att_status->skill(Skill::coalition);
21,982,550✔
1756
                if (__builtin_expect(coalition_base, false))
21,982,550✔
1757
                {
1758
                    uint8_t factions_bitmap = 0;
1,587,430✔
1759
                    for (CardStatus * status : att_assaults.m_indirect)
8,485,790✔
1760
                    {
1761
                        if (! is_alive(status)) { continue; }
6,898,360✔
1762
                        factions_bitmap |= (1 << (status->m_card->m_faction));
6,892,715✔
1763
                    }
1764
                    _DEBUG_ASSERT(factions_bitmap);
1,587,430✔
1765
                    unsigned uniq_factions = byte_bits_count(factions_bitmap);
1,587,430✔
1766
                    coalition_value = coalition_base * uniq_factions;
1,587,430✔
1767
#ifndef NDEBUG
1768
                    if (debug_print > 0) { desc += "+" + tuo::to_string(coalition_value) + "(coalition/x" + tuo::to_string(uniq_factions) + ")"; }
2,105,394✔
1769
#endif
1770
                    att_dmg += coalition_value;
1,587,430✔
1771
                }
1772

1773
                // Skill: Rupture
1774
                unsigned rupture_value = att_status->skill(Skill::rupture);
21,982,550✔
1775
                if (rupture_value > 0)
21,982,550✔
1776
                {
1777
#ifndef NDEBUG
1778
                    if (debug_print > 0) { desc += "+" + tuo::to_string(rupture_value) + "(rupture)"; }
×
1779
#endif
1780
                    att_dmg += rupture_value;
×
1781
                }
1782

1783
                // Skill: Venom
1784
                unsigned venom_value = att_status->skill(Skill::venom);
21,982,550✔
1785
                if (venom_value > 0 && def_status->m_poisoned > 0)
21,982,550✔
1786
                {
1787
#ifndef NDEBUG
1788
                    if (debug_print > 0) { desc += "+" + tuo::to_string(venom_value) + "(venom)"; }
79,695✔
1789
#endif
1790
                    att_dmg += venom_value;
29,601✔
1791
                }
1792

1793
                // Passive BGE: Bloodlust
1794
                if (fd->bloodlust_value > 0)
21,982,550✔
1795
                {
1796
#ifndef NDEBUG
1797
                    if (debug_print > 0) { desc += "+" + tuo::to_string(fd->bloodlust_value) + "(bloodlust)"; }
205,336✔
1798
#endif
1799
                    att_dmg += fd->bloodlust_value;
51,334✔
1800
                }
1801

1802
                // State: Enfeebled
1803
                if (def_status->m_enfeebled > 0)
21,982,550✔
1804
                {
1805
#ifndef NDEBUG
1806
                    if (debug_print > 0) { desc += "+" + tuo::to_string(def_status->m_enfeebled) + "(enfeebled)"; }
3,186,875✔
1807
#endif
1808
                    att_dmg += def_status->m_enfeebled;
2,281,253✔
1809
                }
1810
            }
1811
            // prevent damage
1812
#ifndef NDEBUG
1813
            std::string reduced_desc;
23,365,930✔
1814
#endif
1815
            unsigned reduced_dmg(0); // damage reduced by armor, protect and in turn again canceled by pierce, etc.
23,365,930✔
1816
            unsigned armor_value = 0;
23,365,930✔
1817
            // Armor
1818
            if (def_status->m_card->m_type == CardType::assault) {
23,365,930✔
1819
                // Passive BGE: Fortification (adj step -> 1 (1 left, host, 1 right)
1820
                unsigned adj_size = (unsigned)(fd->bg_effects[fd->tapi][PassiveBGE::fortification]);
15,032,911✔
1821
                unsigned host_idx = def_status->m_index;
15,032,911✔
1822
                unsigned from_idx = safe_minus(host_idx, adj_size);
15,032,911✔
1823
                unsigned till_idx = std::min(host_idx + adj_size, safe_minus(def_assaults.size(), 1));
28,086,083✔
1824
                for (; from_idx <= till_idx; ++ from_idx)
30,093,003✔
1825
                {
1826
                    CardStatus* adj_status = &def_assaults[from_idx];
15,060,092✔
1827
                    if (!is_alive(adj_status)) { continue; }
15,060,092✔
1828
                    armor_value = std::max(armor_value, adj_status->skill(Skill::armor));
19,231,679✔
1829
                }
1830
            }
1831
            if (armor_value > 0)
23,365,930✔
1832
            {
1833
#ifndef NDEBUG
1834
                if(debug_print > 0) { reduced_desc += tuo::to_string(armor_value) + "(armor)"; }
4,516,432✔
1835
#endif
1836
                reduced_dmg += armor_value;
1837
            }
1838
            if (def_status->protected_value() > 0)
23,365,930✔
1839
            {
1840
#ifndef NDEBUG
1841
                if(debug_print > 0) { reduced_desc += (reduced_desc.empty() ? "" : "+") + tuo::to_string(def_status->protected_value()) + "(protected)"; }
11,910,490✔
1842
#endif
1843
                reduced_dmg += def_status->protected_value();
9,374,431✔
1844
            }
1845
            unsigned pierce_value = att_status->skill(Skill::pierce);
23,365,930✔
1846
            if (reduced_dmg > 0 && pierce_value > 0)
23,365,930✔
1847
            {
1848
#ifndef NDEBUG
1849
                if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(pierce_value) + "(pierce)"; }
416,309✔
1850
#endif
1851
                reduced_dmg = safe_minus(reduced_dmg, pierce_value);
23,365,930✔
1852
            }
1853
            unsigned rupture_value = att_status->skill(Skill::rupture);
23,365,930✔
1854
            if (reduced_dmg > 0 && rupture_value > 0)
23,365,930✔
1855
            {
1856
#ifndef NDEBUG
1857
                if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(rupture_value) + "(rupture)"; }
×
1858
#endif
1859
                reduced_dmg = safe_minus(reduced_dmg, rupture_value);
23,365,930✔
1860
            }
1861
            if(__builtin_expect(fd->fixes[Fix::corrosive_protect_armor],true)) // MK - 05/01/2023 6:58 AM: "Corrosive status reduces protection from attacks (equivalent to giving the attacker Pierce X). Note that the value is equal to initial Corrosive application, not attack removed."
23,365,930✔
1862
            {
1863
                unsigned corrosive_value = def_status->m_corroded_rate;
23,365,930✔
1864
                if (reduced_dmg > 0 && corrosive_value > 0)
23,365,930✔
1865
                {
1866
#ifndef NDEBUG
1867
                    if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(corrosive_value) + "(corrosive)"; }
29,908✔
1868
#endif
1869
                    reduced_dmg = safe_minus(reduced_dmg, corrosive_value);
23,365,930✔
1870
                }
1871
            }
1872
            att_dmg = safe_minus(att_dmg, reduced_dmg);
23,365,930✔
1873
#ifndef NDEBUG
1874
            if (debug_print > 0)
23,365,930✔
1875
            {
1876
                if(!reduced_desc.empty()) { desc += "-[" + reduced_desc + "]"; }
4,702,227✔
1877
                if(!desc.empty()) { desc += "=" + tuo::to_string(att_dmg); }
5,271,453✔
1878
                else { assert(att_dmg == pre_modifier_dmg); }
1,650,420✔
1879
                _DEBUG_MSG(1, "%s attacks %s for %u%s damage\n",
5,714,862✔
1880
                        status_description(att_status).c_str(),
1881
                        status_description(def_status).c_str(), pre_modifier_dmg, desc.c_str());
1882
            }
1883
#endif
1884
            // Passive BGE: Brigade
1885
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::brigade] && legion_value, false)
23,365,930✔
1886
                    && can_be_healed(att_status))
×
1887
            {
1888
                _DEBUG_MSG(1, "Brigade: %s heals itself for %u\n",
×
1889
                        status_description(att_status).c_str(), legion_value);
1890
                att_status->add_hp(legion_value);
×
1891
            }
1892

1893
            // Passive BGE: Unity
1894
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::unity] && coalition_value, false)
23,365,930✔
1895
                    && can_be_healed(att_status))
23,369,359✔
1896
            {
1897
                _DEBUG_MSG(1, "Unity: %s heals itself for %u\n",
830✔
1898
                        status_description(att_status).c_str(), (coalition_value + 1)/2);
1899
                att_status->add_hp((coalition_value + 1)/2);
415✔
1900
            }
1901
        }
23,365,930✔
1902

1903
    template<enum CardType::CardType>
1904
        void attack_damage()
11,891,667✔
1905
        {
1906

1907
            remove_hp(fd, def_status, att_dmg);
11,891,667✔
1908
            prepend_on_death(fd);
11,891,667✔
1909
            resolve_skill(fd);
11,891,667✔
1910
        }
11,891,667✔
1911

1912
    template<enum CardType::CardType>
1913
        void on_attacked() {
21,990,668✔
1914
            //APN
1915
            // resolve On-Attacked skills
1916
            for (const auto& ss: def_status->m_card->m_skills_on_attacked)
22,190,284✔
1917
            {
1918
                _DEBUG_MSG(1, "On Attacked %s: Preparing (tail) skill %s\n",
399,232✔
1919
                        status_description(def_status).c_str(), skill_description(fd->cards, ss).c_str());
1920
                fd->skill_queue.emplace_back(def_status, ss);
199,616✔
1921
                resolve_skill(fd);
199,616✔
1922
            }
1923
        }
21,990,668✔
1924

1925
    template<enum CardType::CardType>
1926
        void damage_dependant_pre_oa() {}
1927

1928
    template<enum CardType::CardType>
1929
        void do_leech() {}
1930

1931
    template<enum CardType::CardType>
1932
        void perform_swipe_drain(Field* fd, CardStatus* att_status, CardStatus* def_status,unsigned att_dmg) {}
1933
    };
1934

1935

1936
    template<>
1937
void PerformAttack::attack_damage<CardType::commander>()
7,543,113✔
1938
{
1939
    remove_commander_hp(fd, *def_status, att_dmg);
7,543,113✔
1940
}
×
1941

1942
    template<>
1943
void PerformAttack::damage_dependant_pre_oa<CardType::assault>()
11,101,761✔
1944
{
1945
    if (!__builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
11,101,761✔
1946
    {
1947
    unsigned poison_value = std::max(att_status->skill(Skill::poison), att_status->skill(Skill::venom));
×
1948
    
1949
    if (poison_value > def_status->m_poisoned && skill_check<Skill::poison>(fd, att_status, def_status))
×
1950
    {
1951
        // perform_skill_poison
1952
#ifndef NQUEST
1953
        if (att_status->m_player == 0)
1954
        {
1955
            fd->inc_counter(QuestType::skill_use, Skill::poison);
1956
        }
1957
#endif
1958
        _DEBUG_MSG(1, "%s poisons %s by %u\n",
×
1959
                status_description(att_status).c_str(),
1960
                status_description(def_status).c_str(), poison_value);
×
1961
        def_status->m_poisoned = poison_value;
×
1962
    }
1963
    }
1964
    else {            
1965
        unsigned venom_value = att_status->skill(Skill::venom);
11,101,761✔
1966
        if (venom_value > 0 && skill_check<Skill::venom>(fd, att_status, def_status)) {
11,101,761✔
1967
#ifndef NQUEST
1968
              if (att_status->m_player == 0)
1969
              {
1970
                 fd->inc_counter(QuestType::skill_use, Skill::venom);
1971
              }
1972
#endif
1973

1974
              _DEBUG_MSG(1, "%s venoms %s by %u\n",
232,536✔
1975
                  status_description(att_status).c_str(),
1976
                  status_description(def_status).c_str(), venom_value);
232,536✔
1977
              // new venom keeps adding stacks
1978
              def_status->m_poisoned += venom_value;
232,536✔
1979
        }
1980
    }
1981
}
11,101,761✔
1982

1983

1984
    template<>
1985
void PerformAttack::do_leech<CardType::assault>()
10,922,008✔
1986
{
1987
    unsigned leech_value = std::min(att_dmg, att_status->skill(Skill::leech));
10,922,008✔
1988
    if(leech_value > 0 && skill_check<Skill::leech>(fd, att_status, nullptr))
10,922,008✔
1989
    {
1990
#ifndef NQUEST
1991
        if (att_status->m_player == 0)
1992
        {
1993
            fd->inc_counter(QuestType::skill_use, Skill::leech);
1994
        }
1995
#endif
1996
        _DEBUG_MSG(1, "%s leeches %u health\n", status_description(att_status).c_str(), leech_value);
51,608✔
1997
        if (__builtin_expect(fd->fixes[Fix::leech_increase_max_hp],true)) {
51,608✔
1998
            att_status->ext_hp(leech_value);
51,608✔
1999
        }
2000
        else {
2001
            att_status->add_hp(leech_value);
×
2002
        }
2003
    }
2004
}
10,922,008✔
2005

2006
// General attack phase by the currently evaluated assault, taking into accounts exotic stuff such as flurry, etc.
2007
unsigned attack_commander(Field* fd, CardStatus* att_status)
8,334,770✔
2008
{
2009
    CardStatus* def_status{select_first_enemy_wall(fd)}; // defending wall
8,334,770✔
2010
    if (def_status != nullptr)
8,334,770✔
2011
    {
2012
        return PerformAttack{fd, att_status, def_status}.op<CardType::structure>();
789,906✔
2013
    }
2014
    else
2015
    {
2016
        return PerformAttack{fd, att_status, &fd->tip->commander}.op<CardType::commander>();
7,544,864✔
2017
    }
2018
}
2019
// Return true if actually attacks
2020
bool attack_phase(Field* fd)
26,122,768✔
2021
{
2022
    CardStatus* att_status(&fd->tap->assaults[fd->current_ci]); // attacking card
26,122,768✔
2023
    Storage<CardStatus>& def_assaults(fd->tip->assaults);
26,122,768✔
2024

2025
    if (!att_status->attack_power())
26,122,768✔
2026
    {
2027
        _DEBUG_MSG(1, "%s cannot take attack (zeroed)\n", status_description(att_status).c_str());
2,700,801✔
2028
        return false;
2,700,801✔
2029
    }
2030

2031
    unsigned att_dmg = 0;
23,421,967✔
2032
    if (alive_assault(def_assaults, fd->current_ci))
23,421,967✔
2033
    {
2034
        CardStatus* def_status = &def_assaults[fd->current_ci];
15,087,197✔
2035
        att_dmg = PerformAttack{fd, att_status, def_status}.op<CardType::assault>();
15,087,197✔
2036
        
2037
    }
2038
    else
2039
    {
2040
        // might be blocked by walls
2041
        att_dmg = attack_commander(fd, att_status);
8,334,770✔
2042
    }
2043

2044
    // Passive BGE: Bloodlust
2045
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::bloodlust], false)
23,421,967✔
2046
            && !fd->assault_bloodlusted && (att_dmg > 0))
23,421,967✔
2047
    {
2048
        fd->bloodlust_value += fd->bg_effects[fd->tapi][PassiveBGE::bloodlust];
113,194✔
2049
        fd->assault_bloodlusted = true;
113,194✔
2050
    }
2051

2052
    return true;
2053
}
2054

2055
//---------------------- $65 active skills implementation ----------------------
2056
template<
2057
bool C
2058
, typename T1
2059
, typename T2
2060
>
2061
struct if_
2062
{
2063
    typedef T1 type;
2064
};
2065

2066
template<
2067
typename T1
2068
, typename T2
2069
>
2070
struct if_<false,T1,T2>
2071
{
2072
    typedef T2 type;
2073
};
2074

2075
    template<unsigned skill_id>
2076
inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
222✔
2077
{ return skill_check<static_cast<Skill::Skill>(skill_id)>(fd, dst, src); }
384,791,071✔
2078

2079
    template<>
2080
inline bool skill_predicate<Skill::enhance>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
31,739,537✔
2081
{
2082
    if (!is_alive(dst)) return false;
31,739,537✔
2083
    if (!dst->has_skill(s.s)) return false;
63,479,070✔
2084
    if (is_active(dst)) return true;
7,354,651✔
2085
    if (is_defensive_skill(s.s)) return true;
3,842,009✔
2086
    if (is_instant_debuff_skill(s.s)) return true; // Enhance Sabotage, Inhibit, Disease also without dst being active
629,354✔
2087
    if (is_triggered_skill(s.s) && s.s != Skill::valor) return true;// Enhance Allegiance, Stasis, Bravery ( + not in TU: Flurry, Summon; No enhance on inactive dst: Valor)
629,254✔
2088

2089
    /* Strange Transmission [Gilians]: strange gillian's behavior implementation:
2090
     * The Gillian commander and assaults can enhance any skills on any assaults
2091
     * regardless of jammed/delayed states. But what kind of behavior is in the case
2092
     * when gilians are played among standard assaults, I don't know. :)
2093
     */
2094
    return is_alive_gilian(src);
2095
}
2096

2097
    template<>
2098
inline bool skill_predicate<Skill::evolve>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,434,544✔
2099
{
2100
    if (!is_alive(dst)) return false;
3,434,544✔
2101
    if (!dst->has_skill(s.s)) return false;
6,863,636✔
2102
    if (dst->has_skill(s.s2)) return false;
2,089,864✔
2103
    if (is_active(dst)) return true;
1,044,932✔
2104
    if (is_defensive_skill(s.s2)) return true;
601,673✔
2105

2106
    /* Strange Transmission [Gilians]: strange gillian's behavior implementation:
2107
     * The Gillian commander and assaults can enhance any skills on any assaults
2108
     * regardless of jammed/delayed states. But what kind of behavior is in the case
2109
     * when gilians are played among standard assaults, I don't know. :)
2110
     */
2111
    return is_alive_gilian(src);
2112
}
2113

2114
    template<>
2115
inline bool skill_predicate<Skill::mimic>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
643,852✔
2116
{
2117
    // skip dead units
2118
    if (!is_alive(dst)) return false;
643,852✔
2119

2120
    //include on activate/attacked/death
2121
    //for(const auto & a  : {dst->m_card->m_skills,dst->m_card->m_skills_on_play,dst->m_card->m_skills_on_death,dst->m_card->m_skills_on_attacked})
2122
    //
2123
    std::vector<std::vector<SkillSpec>> all;
598,373✔
2124
    all.emplace_back(dst->m_card->m_skills);
598,373✔
2125
    all.emplace_back(dst->m_card->m_skills_on_attacked);
598,373✔
2126

2127
    for(std::vector<SkillSpec> & a  : all)
960,900✔
2128
    {
2129
    // scan all enemy skills until first activation
2130
    for (const SkillSpec & ss: a)
1,842,104✔
2131
    {
2132
        // get skill
2133
        Skill::Skill skill_id = static_cast<Skill::Skill>(ss.id);
1,479,577✔
2134

2135
        // skip non-activation skills and Mimic (Mimic can't be mimicked)
2136
        if (!is_activation_skill(skill_id) || (skill_id == Skill::mimic))
1,906,647✔
2137
        { continue; }
1,052,507✔
2138

2139
        // skip mend for non-assault mimickers
2140
        if ((skill_id == Skill::mend || skill_id == Skill::fortify) && (src->m_card->m_type != CardType::assault))
427,070✔
2141
        { continue; }
2,800✔
2142

2143
        // enemy has at least one activation skill that can be mimicked, so enemy is eligible target for Mimic
2144
        return true;
598,373✔
2145
    }
2146
    }
2147

2148
    // found nothing (enemy has no skills to be mimicked, so enemy isn't eligible target for Mimic)
2149
    return false;
2150
}
598,373✔
2151

2152
    template<>
2153
inline bool skill_predicate<Skill::overload>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
686,085✔
2154
{
2155
    // basic skill check
2156
    if (!skill_check<Skill::overload>(fd, dst, src))
1,095,503✔
2157
    { return false; }
2158

2159
    // check skills
2160
    bool inhibited_searched = false;
349,317✔
2161
    for (const auto& ss: dst->m_card->m_skills)
977,030✔
2162
    {
2163
        // skip cooldown skills
2164
        if (dst->m_skill_cd[ss.id] > 0)
901,633✔
2165
        { continue; }
8,880✔
2166

2167
        // get evolved skill
2168
        Skill::Skill evolved_skill_id = static_cast<Skill::Skill>(ss.id + dst->m_evolved_skill_offset[ss.id]);
892,753✔
2169

2170
        // unit with an activation hostile skill is always valid target for OL
2171
        if (is_activation_hostile_skill(evolved_skill_id))
1,165,923✔
2172
        { return true; }
2173

2174
        // unit with an activation helpful skill is valid target only when there are inhibited units
2175
        // TODO check mend/fortify valid overload target?!?
2176
        if ((evolved_skill_id != Skill::mend && evolved_skill_id != Skill::fortify)
621,667✔
2177
                && is_activation_helpful_skill(evolved_skill_id)
627,713✔
2178
                && __builtin_expect(!inhibited_searched, true))
808,488✔
2179
        {
2180
            for (const auto & c: fd->players[dst->m_player]->assaults.m_indirect)
1,000,043✔
2181
            {
2182
                if (is_alive(c) && c->m_inhibited)
817,409✔
2183
                { return true; }
273,920✔
2184
            }
2185
            inhibited_searched = true;
2186
        }
2187
    }
2188
    return false;
2189
}
2190

2191
    template<>
2192
inline bool skill_predicate<Skill::rally>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
26,309,224✔
2193
{
2194
    return skill_check<Skill::rally>(fd, dst, src) // basic skill check
26,309,224✔
2195
        && (__builtin_expect((fd->tapi == dst->m_player), true) // is target on the active side?
26,309,224✔
2196
                ? is_active(dst) && !has_attacked(dst) // normal case
14,808,428✔
2197
                : is_active_next_turn(dst) // diverted case / on-death activation
26,309,224✔
2198
           )
26,309,224✔
2199
        ;
2200
}
2201

2202
    template<>
2203
inline bool skill_predicate<Skill::enrage>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
32,732,580✔
2204
{
2205
    return (__builtin_expect((fd->tapi == dst->m_player), true) // is target on the active side?
32,732,580✔
2206
            ? is_active(dst) && (dst->m_step == CardStep::none) // normal case
15,036,572✔
2207
            : is_active_next_turn(dst) // on-death activation
47,226,400✔
2208
           )
2209
        && (dst->attack_power()) // card can perform direct attack
47,226,400✔
2210
        ;
2211
}
2212

2213
    template<>
2214
inline bool skill_predicate<Skill::rush>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
×
2215
{
2216
    return (!src->m_rush_attempted)
×
2217
        && (dst->m_delay >= ((src->m_card->m_type == CardType::assault) && (dst->m_index < src->m_index) ? 2u : 1u));
×
2218
}
2219

2220
    template<>
2221
inline bool skill_predicate<Skill::weaken>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
40,139,758✔
2222
{
2223
    if(__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::ironwill], false) && dst->has_skill(Skill::armor))return false;
40,139,758✔
2224
    if (!dst->attack_power()) { return false; }
40,136,172✔
2225

2226
    // active player performs Weaken (normal case)
2227
    if (__builtin_expect((fd->tapi == src->m_player), true))
35,642,054✔
2228
    { return is_active_next_turn(dst); }
35,527,373✔
2229

2230
    // APN - On-Attacked/Death don't target the attacking card
2231

2232
    // inactive player performs Weaken (inverted case (on-death activation))
2233
    return will_activate_this_turn(dst);
114,681✔
2234
}
2235

2236
    template<>
2237
inline bool skill_predicate<Skill::sunder>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
19,517,909✔
2238
{
2239
    return skill_predicate<Skill::weaken>(fd, src, dst, s);
102✔
2240
}
2241

2242
    template<unsigned skill_id>
2243
inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2244
{ assert(false); }
2245

2246
    template<>
2247
inline void perform_skill<Skill::enfeeble>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
8,345,074✔
2248
{
2249
    dst->m_enfeebled += s.x;
8,345,074✔
2250
}
×
2251

2252
    template<>
2253
inline void perform_skill<Skill::enhance>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
6,301,653✔
2254
{
2255
    dst->m_enhanced_value[s.s + dst->m_primary_skill_offset[s.s]] += s.x;
6,301,653✔
2256
}
34✔
2257

2258
    template<>
2259
inline void perform_skill<Skill::evolve>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
429,755✔
2260
{
2261
    auto primary_s1 = dst->m_primary_skill_offset[s.s] + s.s;
429,755✔
2262
    auto primary_s2 = dst->m_primary_skill_offset[s.s2] + s.s2;
429,755✔
2263
    dst->m_primary_skill_offset[s.s] = primary_s2 - s.s;
429,755✔
2264
    dst->m_primary_skill_offset[s.s2] = primary_s1 - s.s2;
429,755✔
2265
    dst->m_evolved_skill_offset[primary_s1] = s.s2 - primary_s1;
429,755✔
2266
    dst->m_evolved_skill_offset[primary_s2] = s.s - primary_s2;
429,755✔
2267
}
×
2268

2269
    template<>
2270
inline void perform_skill<Skill::heal>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
8,678,969✔
2271
{
2272
    dst->add_hp(s.x);
8,678,969✔
2273

2274
    // Passive BGE: ZealotsPreservation
2275
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::zealotspreservation], false)
8,678,969✔
2276
            && (src->m_card->m_type == CardType::assault))
8,678,969✔
2277
    {
2278
        unsigned bge_value = (s.x + 1) / 2;
425✔
2279
        _DEBUG_MSG(1, "Zealot's Preservation: %s Protect %u on %s\n",
425✔
2280
                status_description(src).c_str(), bge_value,
2281
                status_description(dst).c_str());
425✔
2282
        dst->m_protected += bge_value;
425✔
2283
    }
2284
}
8,678,969✔
2285

2286
    template<>
2287
inline void perform_skill<Skill::jam>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
1,821,710✔
2288
{
2289
    dst->m_jammed = true;
1,821,710✔
2290
}
×
2291

2292
    template<>
2293
inline void perform_skill<Skill::mend>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,846✔
2294
{
2295
    dst->add_hp(s.x);
3,844✔
2296
}
2✔
2297

2298
    template<>
2299
inline void perform_skill<Skill::fortify>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,413,705✔
2300
{
2301
    dst->ext_hp(s.x);
1,758,511✔
2302
}
655,194✔
2303

2304
    template<>
2305
inline void perform_skill<Skill::siege>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,576,687✔
2306
{ 
2307
    //only structures can be sieged
2308
    _DEBUG_ASSERT(dst->m_card->m_type != CardType::assault);
2,576,687✔
2309
    _DEBUG_ASSERT(dst->m_card->m_type != CardType::commander);
2,576,687✔
2310
    unsigned siege_dmg = remove_absorption(fd,dst,s.x);
2,576,687✔
2311
    // structure should not have protect normally..., but let's allow it for barrier support
2312
    siege_dmg = safe_minus(siege_dmg, src->m_overloaded ? 0 : dst->m_protected);
2,576,687✔
2313
    remove_hp(fd, dst, siege_dmg);
2,576,687✔
2314
}
2,576,687✔
2315

2316
    template<>
2317
inline void perform_skill<Skill::mortar>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,146,103✔
2318
{
2319
    if (dst->m_card->m_type == CardType::structure)
3,146,103✔
2320
    {
2321
        perform_skill<Skill::siege>(fd, src, dst, s);
2,399,615✔
2322
    }
2323
    else
2324
    {
2325
        unsigned strike_dmg = remove_absorption(fd,dst,(s.x + 1) / 2 + dst->m_enfeebled);
746,488✔
2326
        strike_dmg = safe_minus(strike_dmg, src->m_overloaded ? 0 : dst->protected_value());
746,488✔
2327
        remove_hp(fd, dst, strike_dmg);
746,488✔
2328
    }
2329
}
3,146,103✔
2330

2331
    template<>
2332
inline void perform_skill<Skill::overload>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
247,304✔
2333
{
2334
    dst->m_overloaded = true;
247,304✔
2335
}
9✔
2336

2337
    template<>
2338
inline void perform_skill<Skill::protect>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
36,236,454✔
2339
{
2340
    dst->m_protected += s.x;
36,236,454✔
2341
}
402✔
2342

2343
    template<>
2344
inline void perform_skill<Skill::rally>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
11,102,640✔
2345
{
2346
    dst->m_temp_attack_buff += s.x;
11,102,640✔
2347
}
227,033✔
2348

2349
    template<>
2350
inline void perform_skill<Skill::enrage>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
9,983,367✔
2351
{
2352
    dst->m_enraged += s.x;
9,983,367✔
2353
    // Passive BGE: Furiosity
2354
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::furiosity], false)
9,983,367✔
2355
            && can_be_healed(dst))
9,983,367✔
2356
    {
2357
        unsigned bge_value = s.x;
7,019✔
2358
        _DEBUG_MSG(1, "Furiosity: %s Heals %s for %u\n",
7,019✔
2359
                status_description(src).c_str(),
2360
                status_description(dst).c_str(), bge_value);
7,019✔
2361
        dst->add_hp(bge_value);
7,019✔
2362
    }
2363
}
9,983,367✔
2364

2365
    template<>
2366
inline void perform_skill<Skill::entrap>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
6,285,911✔
2367
{
2368
    dst->m_entrapped += s.x;
6,285,911✔
2369
}
86,774✔
2370

2371
    template<>
2372
inline void perform_skill<Skill::rush>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
×
2373
{
2374
    dst->m_delay -= std::min(std::max(s.x, 1u), dst->m_delay);
×
2375
    if (dst->m_delay == 0)
×
2376
    {
2377
        check_and_perform_valor(fd, dst);
×
2378
        if(dst->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate)check_and_perform_summon(fd, dst);
×
2379
    }
2380
}
×
2381

2382
    template<>
2383
inline void perform_skill<Skill::strike>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
19,117,195✔
2384
{
2385
    unsigned strike_dmg = remove_absorption(fd,dst,s.x+ dst->m_enfeebled);
19,117,195✔
2386
    strike_dmg = safe_minus(strike_dmg , src->m_overloaded ? 0 : dst->protected_value());
19,117,195✔
2387
    remove_hp(fd, dst, strike_dmg);
19,117,195✔
2388
}
19,117,195✔
2389

2390
    template<>
2391
inline void perform_skill<Skill::weaken>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
10,410,458✔
2392
{
2393
    dst->m_temp_attack_buff -= (unsigned)std::min(s.x, dst->attack_power());
10,410,458✔
2394
}
×
2395

2396
    template<>
2397
inline void perform_skill<Skill::sunder>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,587,098✔
2398
{
2399
    dst->m_sundered = true;
3,587,098✔
2400
    perform_skill<Skill::weaken>(fd, src, dst, s);
3,521,417✔
2401
}
×
2402

2403
    template<>
2404
inline void perform_skill<Skill::mimic>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
280,279✔
2405
{
2406
    // collect all mimickable enemy skills
2407
    std::vector<const SkillSpec *> mimickable_skills;
280,279✔
2408
    mimickable_skills.reserve(dst->m_card->m_skills.size()+dst->m_card->m_skills_on_play.size()+dst->m_card->m_skills_on_death.size()+dst->m_card->m_skills_on_attacked.size());
280,279✔
2409
    _DEBUG_MSG(2, " * Mimickable skills of %s\n", status_description(dst).c_str());
280,279✔
2410
    //include on activate/attacked
2411
    std::vector<std::vector<SkillSpec>> all;
280,279✔
2412
    all.emplace_back(dst->m_card->m_skills);
280,279✔
2413
    all.emplace_back(dst->m_card->m_skills_on_attacked);
280,279✔
2414
    for(std::vector<SkillSpec> & a : all)
840,837✔
2415
    {
2416
    for (const SkillSpec & ss: a)
1,400,255✔
2417
    {
2418
        // get skill
2419
        Skill::Skill skill_id = static_cast<Skill::Skill>(ss.id);
839,697✔
2420

2421
        // skip non-activation skills and Mimic (Mimic can't be mimicked)
2422
        if (!is_activation_skill(skill_id) || (skill_id == Skill::mimic))
1,208,913✔
2423
        { continue; }
470,481✔
2424

2425
        // skip mend for non-assault mimickers
2426
        if ((skill_id == Skill::mend || skill_id == Skill::fortify) && (src->m_card->m_type != CardType::assault))
369,216✔
2427
        { continue; }
1,200✔
2428

2429
        mimickable_skills.emplace_back(&ss);
368,016✔
2430
        _DEBUG_MSG(2, "  + %s\n", skill_description(fd->cards, ss).c_str());
839,697✔
2431
    }
2432
    }
2433

2434
    // select skill
2435
    unsigned mim_idx = 0;
280,279✔
2436
    switch (mimickable_skills.size())
280,279✔
2437
    {
2438
        case 0: assert(false); break;
×
2439
        case 1: break;
2440
        default: mim_idx = (fd->re() % mimickable_skills.size()); break;
86,053✔
2441
    }
2442
    // prepare & perform selected skill
2443
    const SkillSpec & mim_ss = *mimickable_skills[mim_idx];
280,279✔
2444
    Skill::Skill mim_skill_id = static_cast<Skill::Skill>(mim_ss.id);
280,279✔
2445
    auto skill_value = s.x + src->enhanced(mim_skill_id); //enhanced skill from mimic ?!?
280,279✔
2446
    SkillSpec mimicked_ss{mim_skill_id, skill_value, allfactions, mim_ss.n, 0, mim_ss.s, mim_ss.s2, mim_ss.all, mim_ss.card_id,};
280,279✔
2447
    _DEBUG_MSG(1, " * Mimicked skill: %s\n", skill_description(fd->cards, mimicked_ss).c_str());
280,279✔
2448
    skill_table[mim_skill_id](fd, src, mimicked_ss);
280,279✔
2449
}
280,279✔
2450

2451
    template<unsigned skill_id>
2452
inline unsigned select_fast(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
125,968,918✔
2453
{
2454
    if ((s.y == allfactions)
125,968,918✔
2455
            || fd->bg_effects[fd->tapi][PassiveBGE::metamorphosis]
21,012,711✔
2456
            || fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis])
146,825,902✔
2457
    {
2458
        auto pred = [fd, src, s](CardStatus* c) {
400,249,528✔
2459
            return(skill_predicate<skill_id>(fd, src, c, s));
344,358,391✔
2460
        };
2461
        return fd->make_selection_array(cards.begin(), cards.end(), pred);
105,265,424✔
2462
    }
2463
    else
2464
    {
2465
        auto pred = [fd, src, s](CardStatus* c) {
110,363,817✔
2466
            return ((c->m_card->m_faction == s.y || c->m_card->m_faction == progenitor) && skill_predicate<skill_id>(fd, src, c, s));
104,743,479✔
2467
        };
2468
        return fd->make_selection_array(cards.begin(), cards.end(), pred);
20,703,494✔
2469
    }
2470
}
2471

2472
    template<>
2473
inline unsigned select_fast<Skill::mend>(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
108,369✔
2474
{
2475
    fd->selection_array.clear();
108,369✔
2476
    bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
108,369✔
2477
    auto& assaults = fd->players[src->m_player]->assaults;
108,369✔
2478
    unsigned adj_size = 1 + (unsigned)(critical_reach);
108,369✔
2479
    unsigned host_idx = src->m_index;
108,369✔
2480
    unsigned from_idx = safe_minus(host_idx, adj_size);
108,369✔
2481
    unsigned till_idx = std::min(host_idx + adj_size, safe_minus(assaults.size(), 1));
211,619✔
2482
    for (; from_idx <= till_idx; ++ from_idx)
372,532✔
2483
    {
2484
        if (from_idx == host_idx) { continue; }
265,666✔
2485
        CardStatus* adj_status = &assaults[from_idx];
155,794✔
2486
        if (!is_alive(adj_status)) { continue; }
155,794✔
2487
        if (skill_predicate<Skill::mend>(fd, src, adj_status, s))
308,582✔
2488
        {
2489
            fd->selection_array.push_back(adj_status);
4,121✔
2490
        }
2491
    }
2492
    return fd->selection_array.size();
108,369✔
2493
}
2494

2495
    template<>
2496
inline unsigned select_fast<Skill::fortify>(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
1,194,583✔
2497
{
2498
    fd->selection_array.clear();
1,194,583✔
2499
    bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
1,194,583✔
2500
    auto& assaults = fd->players[src->m_player]->assaults;
1,194,583✔
2501
    unsigned adj_size = 1 + (unsigned)(critical_reach);
1,194,583✔
2502
    unsigned host_idx = src->m_index;
1,194,583✔
2503
    unsigned from_idx = safe_minus(host_idx, adj_size);
1,194,583✔
2504
    unsigned till_idx = std::min(host_idx + adj_size, safe_minus(assaults.size(), 1));
2,348,514✔
2505
    for (; from_idx <= till_idx; ++ from_idx)
4,233,126✔
2506
    {
2507
        if (from_idx == host_idx) { continue; }
3,053,498✔
2508
        CardStatus* adj_status = &assaults[from_idx];
1,843,960✔
2509
        if (!is_alive(adj_status)) { continue; }
1,843,960✔
2510
        if (skill_predicate<Skill::fortify>(fd, src, adj_status, s))
1,829,005✔
2511
        {
2512
            fd->selection_array.push_back(adj_status);
1,829,005✔
2513
        }
2514
    }
2515
    return fd->selection_array.size();
1,194,583✔
2516
}
2517
inline std::vector<CardStatus*>& skill_targets_hostile_assault(Field* fd, CardStatus* src)
43,974,556✔
2518
{
2519
    return(fd->players[opponent(src->m_player)]->assaults.m_indirect);
43,974,556✔
2520
}
2521

2522
inline std::vector<CardStatus*>& skill_targets_allied_assault(Field* fd, CardStatus* src)
80,078,221✔
2523
{
2524
    return(fd->players[src->m_player]->assaults.m_indirect);
80,078,221✔
2525
}
2526

2527
inline std::vector<CardStatus*>& skill_targets_hostile_structure(Field* fd, CardStatus* src)
3,219,093✔
2528
{
2529
    return(fd->players[opponent(src->m_player)]->structures.m_indirect);
3,219,093✔
2530
}
2531

2532
inline std::vector<CardStatus*>& skill_targets_allied_structure(Field* fd, CardStatus* src)
2533
{
2534
    return(fd->players[src->m_player]->structures.m_indirect);
2535
}
2536

2537
    template<unsigned skill>
2538
std::vector<CardStatus*>& skill_targets(Field* fd, CardStatus* src)
2539
{
2540
    std::cerr << "skill_targets: Error: no specialization for " << skill_names[skill] << "\n";
2541
    throw;
2542
}
2543

2544
template<> std::vector<CardStatus*>& skill_targets<Skill::enfeeble>(Field* fd, CardStatus* src)
10,908,354✔
2545
{ return(skill_targets_hostile_assault(fd, src)); }
10,908,354✔
2546

2547
template<> std::vector<CardStatus*>& skill_targets<Skill::enhance>(Field* fd, CardStatus* src)
14,827,149✔
2548
{ return(skill_targets_allied_assault(fd, src)); }
14,827,149✔
2549

2550
template<> std::vector<CardStatus*>& skill_targets<Skill::evolve>(Field* fd, CardStatus* src)
1,520,511✔
2551
{ return(skill_targets_allied_assault(fd, src)); }
1,520,511✔
2552

2553
template<> std::vector<CardStatus*>& skill_targets<Skill::heal>(Field* fd, CardStatus* src)
23,855,775✔
2554
{ return(skill_targets_allied_assault(fd, src)); }
23,855,775✔
2555

2556
template<> std::vector<CardStatus*>& skill_targets<Skill::jam>(Field* fd, CardStatus* src)
3,629,948✔
2557
{ return(skill_targets_hostile_assault(fd, src)); }
3,629,948✔
2558

2559
template<> std::vector<CardStatus*>& skill_targets<Skill::mend>(Field* fd, CardStatus* src)
108,369✔
2560
{ return(skill_targets_allied_assault(fd, src)); }
108,369✔
2561

2562
template<> std::vector<CardStatus*>& skill_targets<Skill::fortify>(Field* fd, CardStatus* src)
1,194,583✔
2563
{ return(skill_targets_allied_assault(fd, src)); }
1,194,583✔
2564

2565
template<> std::vector<CardStatus*>& skill_targets<Skill::overload>(Field* fd, CardStatus* src)
164,194✔
2566
{ return(skill_targets_allied_assault(fd, src)); }
164,194✔
2567

2568
template<> std::vector<CardStatus*>& skill_targets<Skill::protect>(Field* fd, CardStatus* src)
12,223,931✔
2569
{ return(skill_targets_allied_assault(fd, src)); }
12,223,931✔
2570

2571
template<> std::vector<CardStatus*>& skill_targets<Skill::rally>(Field* fd, CardStatus* src)
7,482,594✔
2572
{ return(skill_targets_allied_assault(fd, src)); }
7,482,594✔
2573

2574
template<> std::vector<CardStatus*>& skill_targets<Skill::enrage>(Field* fd, CardStatus* src)
13,105,610✔
2575
{ return(skill_targets_allied_assault(fd, src)); }
13,105,610✔
2576

2577
template<> std::vector<CardStatus*>& skill_targets<Skill::entrap>(Field* fd, CardStatus* src)
5,595,505✔
2578
{ return(skill_targets_allied_assault(fd, src)); }
5,595,505✔
2579

2580
template<> std::vector<CardStatus*>& skill_targets<Skill::rush>(Field* fd, CardStatus* src)
×
2581
{ return(skill_targets_allied_assault(fd, src)); }
×
2582

2583
template<> std::vector<CardStatus*>& skill_targets<Skill::siege>(Field* fd, CardStatus* src)
3,219,093✔
2584
{ return(skill_targets_hostile_structure(fd, src)); }
3,219,093✔
2585

2586
template<> std::vector<CardStatus*>& skill_targets<Skill::strike>(Field* fd, CardStatus* src)
14,970,309✔
2587
{ return(skill_targets_hostile_assault(fd, src)); }
709,082✔
2588

2589
template<> std::vector<CardStatus*>& skill_targets<Skill::sunder>(Field* fd, CardStatus* src)
6,026,151✔
2590
{ return(skill_targets_hostile_assault(fd, src)); }
6,026,151✔
2591

2592
template<> std::vector<CardStatus*>& skill_targets<Skill::weaken>(Field* fd, CardStatus* src)
8,005,706✔
2593
{ return(skill_targets_hostile_assault(fd, src)); }
8,005,706✔
2594

2595
template<> std::vector<CardStatus*>& skill_targets<Skill::mimic>(Field* fd, CardStatus* src)
434,088✔
2596
{ return(skill_targets_hostile_assault(fd, src)); }
434,088✔
2597

2598
    template<Skill::Skill skill_id>
2599
inline bool check_and_perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s, bool is_evadable
138,725,565✔
2600
#ifndef NQUEST
2601
        , bool & has_counted_quest
2602
#endif
2603
        )
2604
{
2605
    if (__builtin_expect(skill_check<skill_id>(fd, dst, src), true))
138,725,565✔
2606
    {
2607
#ifndef NQUEST
2608
        if (src->m_player == 0 && ! has_counted_quest)
2609
        {
2610
            fd->inc_counter(QuestType::skill_use, skill_id, dst->m_card->m_id);
2611
            has_counted_quest = true;
2612
        }
2613
#endif
2614
        if (is_evadable && (dst->m_evaded < dst->skill(Skill::evade)))
138,725,565✔
2615
        {
2616
            ++ dst->m_evaded;
17,241,881✔
2617
            _DEBUG_MSG(1, "%s %s on %s but it evades\n",
19,207,337✔
2618
                    status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2619
                    status_description(dst).c_str());
2620
            return(false);
17,241,881✔
2621
        }
2622
        _DEBUG_MSG(1, "%s %s on %s\n",
147,200,424✔
2623
                status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2624
                status_description(dst).c_str());
2625
        perform_skill<skill_id>(fd, src, dst, s);
121,483,684✔
2626
        if (s.c > 0)
121,483,684✔
2627
        {
2628
            src->m_skill_cd[skill_id] = s.c;
1,453,005✔
2629
        }
2630
        // Skill: Tribute
2631
        if (skill_check<Skill::tribute>(fd, dst, src)
143,073,337✔
2632
                // only activation helpful skills can be tributed (* except Evolve, Enhance, and Rush)
2633
                && is_activation_helpful_skill(s.id) && (s.id != Skill::evolve) && (s.id != Skill::enhance) && (s.id != Skill::rush)
21,589,653✔
2634
                && (dst->m_tributed < dst->skill(Skill::tribute))
8,391,863✔
2635
                && skill_check<skill_id>(fd, src, src))
110,185,744✔
2636
        {
2637
            ++ dst->m_tributed;
966,480✔
2638
            _DEBUG_MSG(1, "%s tributes %s back to %s\n",
966,480✔
2639
                    status_description(dst).c_str(), skill_short_description(fd->cards, s).c_str(),
2640
                    status_description(src).c_str());
2641
            perform_skill<skill_id>(fd, src, src, s);
966,480✔
2642
        }
2643
        return(true);
121,483,684✔
2644
    }
2645
    _DEBUG_MSG(1, "(CANCELLED) %s %s on %s\n",
×
2646
            status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2647
            status_description(dst).c_str());
2648
    return(false);
2649
}
2650
template<enum CardType::CardType def_cardtype>
2651
void perform_bge_devour(Field* fd, CardStatus* att_status, CardStatus* def_status)
17,373,405✔
2652
{
2653
// Passive BGE: Devour
2654
                unsigned leech_value;
2655
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::devour], false)
10,922,008✔
2656
                        && ((leech_value = att_status->skill(Skill::leech) + att_status->skill(Skill::refresh)) > 0)
47,064✔
2657
                        && (def_cardtype == CardType::assault))
10,922,008✔
2658
                {
2659
                    unsigned bge_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::devour] > 0)
1,986✔
2660
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::devour]
993✔
2661
                        : 4;
2662
                    unsigned bge_value = (leech_value - 1) / bge_denominator + 1;
993✔
2663
                    if (! att_status->m_sundered)
993✔
2664
                    {
2665
                        _DEBUG_MSG(1, "Devour: %s gains %u attack\n",
2,925✔
2666
                                status_description(att_status).c_str(), bge_value);
2667
                        att_status->m_perm_attack_buff += bge_value;
975✔
2668
                    }
2669
                    _DEBUG_MSG(1, "Devour: %s extends max hp / heals itself for %u\n",
2,979✔
2670
                            status_description(att_status).c_str(), bge_value);
2671
                    att_status->ext_hp(bge_value);
993✔
2672
                }
2673
}
10,922,008✔
2674
template<enum CardType::CardType def_cardtype>
2675
void perform_bge_heroism(Field* fd, CardStatus* att_status, CardStatus* def_status)
17,373,405✔
2676
{
2677
// Passive BGE: Heroism
2678
                unsigned valor_value;
2679
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::heroism], false)
17,373,405✔
2680
                        && ((valor_value = att_status->skill(Skill::valor) + att_status->skill(Skill::bravery)) > 0)
23,444✔
2681
                        && !att_status->m_sundered
2,470✔
2682
                        && (def_cardtype == CardType::assault) && (def_status->m_hp <= 0))
10,924,469✔
2683
                {
2684
                    _DEBUG_MSG(1, "Heroism: %s gain %u attack\n",
1,929✔
2685
                            status_description(att_status).c_str(), valor_value);
2686
                    att_status->m_perm_attack_buff += valor_value;
643✔
2687
                }
2688
                }
10,922,008✔
2689
/**
2690
 * @brief Perform Mark skill, increases Mark-counter.
2691
 * 
2692
 * @param fd     Field
2693
 * @param att_status Attacker
2694
 * @param def_status Defender
2695
 */
2696
void perform_mark(Field* fd, CardStatus* att_status, CardStatus* def_status)
17,373,405✔
2697
{
2698
    // Bug fix? 2023-04-03 mark should come after berserk
2699
    // Increase Mark-counter
2700
    unsigned mark_base = att_status->skill(Skill::mark);
17,373,405✔
2701
    if(mark_base && skill_check<Skill::mark>(fd,att_status,def_status)) {
17,373,405✔
2702
        _DEBUG_MSG(1, "%s marks %s for %u\n",
30,215✔
2703
                status_description(att_status).c_str(), status_description(def_status).c_str(), mark_base);
30,215✔
2704
        def_status->m_marked += mark_base;
30,215✔
2705
    }
2706
}
17,373,405✔
2707
/**
2708
 * @brief Perform Berserk skill and Passive BGE: EnduringRage
2709
 * 
2710
 * Increases attack by berserk value if not sundered.
2711
 * 
2712
 * @param fd     Field
2713
 * @param att_status Attacker
2714
 * @param def_status Defender
2715
 */
2716
void perform_berserk(Field* fd, CardStatus* att_status, CardStatus* def_status)
17,373,405✔
2717
{
2718
            // Skill: Berserk
2719
            unsigned berserk_value = att_status->skill(Skill::berserk);
17,373,405✔
2720
            if ( !att_status->m_sundered && (berserk_value > 0))
17,373,405✔
2721
            {
2722
                // perform_skill_berserk
2723
                att_status->m_perm_attack_buff += berserk_value;
6,929,370✔
2724
#ifndef NQUEST
2725
                if (att_status->m_player == 0)
2726
                {
2727
                    fd->inc_counter(QuestType::skill_use, Skill::berserk);
2728
                }
2729
#endif
2730

2731
                // Passive BGE: EnduringRage
2732
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::enduringrage], false))
6,929,370✔
2733
                {
2734
                    unsigned bge_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::enduringrage] > 0)
21,640✔
2735
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::enduringrage]
21,640✔
2736
                        : 2;
21,640✔
2737
                    unsigned bge_value = (berserk_value - 1) / bge_denominator + 1;
21,640✔
2738
                    _DEBUG_MSG(1, "EnduringRage: %s heals and protects itself for %u\n",
21,640✔
2739
                            status_description(att_status).c_str(), bge_value);
21,640✔
2740
                    att_status->add_hp(bge_value);
21,640✔
2741
                    att_status->m_protected += bge_value;
21,640✔
2742
                }
2743
            }
2744

2745

2746
}
17,373,405✔
2747
/**
2748
 * @brief Poisoning of a attacking card
2749
 * 
2750
 * @param fd  Field
2751
 * @param att_status Attacking card
2752
 * @param def_status Defending card
2753
 */
2754
void perform_poison(Field* fd, CardStatus* att_status, CardStatus* def_status)
21,234,598✔
2755
{
2756
    if(__builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
21,234,598✔
2757
    {
2758
        if (is_alive(att_status) && def_status->has_skill(Skill::poison))
21,234,598✔
2759
        {
2760
            unsigned poison_value = def_status->skill(Skill::poison);
5,538✔
2761
            _DEBUG_MSG(1, "%s gets poisoned by %u from %s\n",
5,538✔
2762
                            status_description(att_status).c_str(), poison_value,
2763
                            status_description(def_status).c_str());
5,538✔
2764
            att_status->m_poisoned += poison_value; 
5,538✔
2765
        }
2766
    }
2767
}
21,234,598✔
2768

2769
void perform_corrosive(Field* fd, CardStatus* att_status, CardStatus* def_status)
21,234,598✔
2770
{
2771
    // Skill: Corrosive
2772
    unsigned corrosive_value = def_status->skill(Skill::corrosive);
21,234,598✔
2773
    if (corrosive_value > att_status->m_corroded_rate)
21,234,598✔
2774
    {
2775
        // perform_skill_corrosive
2776
        _DEBUG_MSG(1, "%s corrodes %s by %u\n",
42,964✔
2777
                status_description(def_status).c_str(),
2778
                status_description(att_status).c_str(), corrosive_value);
42,964✔
2779
        att_status->m_corroded_rate = corrosive_value;
42,964✔
2780
    }
2781
}
21,234,598✔
2782

2783
template<enum CardType::CardType def_cardtype>
2784
void perform_counter(Field* fd, CardStatus* att_status, CardStatus* def_status)
21,978,643✔
2785
{
2786
            // Enemy Skill: Counter
2787
            if (def_status->has_skill(Skill::counter))
21,978,643✔
2788
            {
2789
                // perform_skill_counter
2790
                unsigned counter_dmg(counter_damage(fd, att_status, def_status));
2,132,771✔
2791
#ifndef NQUEST
2792
                if (def_status->m_player == 0)
2793
                {
2794
                    fd->inc_counter(QuestType::skill_use, Skill::counter);
2795
                    fd->inc_counter(QuestType::skill_damage, Skill::counter, 0, counter_dmg);
2796
                }
2797
#endif
2798
                _DEBUG_MSG(1, "%s takes %u counter damage from %s\n",
3,192,573✔
2799
                        status_description(att_status).c_str(), counter_dmg,
2800
                        status_description(def_status).c_str());
2801
                remove_hp(fd, att_status, counter_dmg);
2,132,771✔
2802
                prepend_on_death(fd);
2,132,771✔
2803
                resolve_skill(fd);
2,132,771✔
2804

2805
                // Passive BGE: Counterflux
2806
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::counterflux], false)
2,132,771✔
2807
                        && (def_cardtype == CardType::assault) && is_alive(def_status))
1,281,008✔
2808
                {
2809
                    unsigned flux_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::counterflux] > 0)
8,378✔
2810
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::counterflux]
4,189✔
2811
                        : 4;
2812
                    unsigned flux_value = (def_status->skill(Skill::counter) - 1) / flux_denominator + 1;
4,189✔
2813
                    _DEBUG_MSG(1, "Counterflux: %s heals itself and berserks for %u\n",
12,567✔
2814
                            status_description(def_status).c_str(), flux_value);
2815
                    def_status->add_hp(flux_value);
4,189✔
2816
                    if (! def_status->m_sundered)
4,189✔
2817
                    { def_status->m_perm_attack_buff += flux_value; }
3,839✔
2818
                }
2819
            }
2820
}
21,978,643✔
2821
bool check_and_perform_enhance(Field* fd, CardStatus* src, bool early)
162,315,136✔
2822
{
2823
      if(!is_active(src))return false; // active
162,315,136✔
2824
      if(!src->has_skill(Skill::enhance))return false; // enhance Skill
100,526,706✔
2825
      for(auto ss : src->m_card->m_skills)
117,878,840✔
2826
      {
2827
          if(ss.id != Skill::enhance)continue;
88,408,980✔
2828
          if(early ^ (ss.s == Skill::allegiance || ss.s == Skill::absorb ||ss.s == Skill::stasis || ss.s == Skill::bravery))continue; //only specified skills are 'early'
29,934,872✔
2829
          skill_table[ss.id](fd,src,ss);
14,734,930✔
2830
      }
2831
      return true;
29,469,860✔
2832
}
2833
bool check_and_perform_early_enhance(Field* fd, CardStatus* src)
81,157,568✔
2834
{
2835
      return check_and_perform_enhance(fd,src,true);
81,157,568✔
2836
}
2837
bool check_and_perform_later_enhance(Field* fd, CardStatus* src)
81,157,568✔
2838
{
2839
      return check_and_perform_enhance(fd,src,false);
81,157,568✔
2840
}
2841
/**
2842
 * @brief Perform drain skill
2843
 * 
2844
 * @param fd Field
2845
 * @param att_status Attacker status
2846
 * @param def_status Defender status
2847
 * @param att_dmg damage dealt by attacker
2848
 */
2849
template<>
2850
void PerformAttack::perform_swipe_drain<CardType::assault>(Field* fd, CardStatus* att_status, CardStatus* def_status,unsigned att_dmg) {
15,032,911✔
2851
        unsigned swipe_value = att_status->skill(Skill::swipe);
15,032,911✔
2852
        unsigned drain_value = att_status->skill(Skill::drain);
15,032,911✔
2853
        if (swipe_value || drain_value)
15,032,911✔
2854
        {
2855
            Storage<CardStatus>& def_assaults(fd->tip->assaults);
1,410,463✔
2856
            bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
1,410,463✔
2857
            auto drain_total_dmg = att_dmg;
1,410,463✔
2858
            unsigned adj_size = 1 + (unsigned)(critical_reach);
1,410,463✔
2859
            unsigned host_idx = def_status->m_index;
1,410,463✔
2860
            unsigned from_idx = safe_minus(host_idx, adj_size);
1,410,463✔
2861
            unsigned till_idx = std::min(host_idx + adj_size, safe_minus(def_assaults.size(), 1));
2,365,374✔
2862
            for (; from_idx <= till_idx; ++ from_idx)
3,908,621✔
2863
            {
2864
                if (from_idx == host_idx) { continue; }
2,498,158✔
2865
                CardStatus* adj_status = &def_assaults[from_idx];
1,087,695✔
2866
                if (!is_alive(adj_status)) { continue; }
1,087,695✔
2867
                _DEBUG_ASSERT(adj_status->m_card->m_type == CardType::assault); //only assaults
883,688✔
2868
                //unsigned swipe_dmg = safe_minus(
2869
                //    swipe_value + drain_value + def_status->m_enfeebled,
2870
                //    def_status->protected_value());
2871
                unsigned remaining_dmg = remove_absorption(fd,adj_status,swipe_value + drain_value + adj_status->m_enfeebled);
883,688✔
2872
                remaining_dmg = safe_minus(remaining_dmg,adj_status->protected_value());
883,688✔
2873
                _DEBUG_MSG(1, "%s swipes %s for %u damage\n",
883,688✔
2874
                        status_description(att_status).c_str(),
2875
                        status_description(adj_status).c_str(), remaining_dmg);
883,688✔
2876

2877
                remove_hp(fd, adj_status, remaining_dmg);
883,688✔
2878
                drain_total_dmg += remaining_dmg;
883,688✔
2879
            }
2880
            if (drain_value && skill_check<Skill::drain>(fd, att_status, nullptr))
1,410,463✔
2881
            {
2882
                _DEBUG_MSG(1, "%s drains %u hp\n",
67,072✔
2883
                        status_description(att_status).c_str(), drain_total_dmg);
67,072✔
2884
                att_status->add_hp(drain_total_dmg);
67,072✔
2885
            }
2886
            prepend_on_death(fd);
1,410,463✔
2887
            resolve_skill(fd);
1,410,463✔
2888
        }
2889
}
15,032,911✔
2890
/**
2891
 * @brief Perform Hunt skill
2892
 * 
2893
 * @param fd Field 
2894
 * @param att_status Attacker status
2895
 * @param def_status Defender status
2896
 */
2897
void perform_hunt(Field* fd, CardStatus* att_status, CardStatus* def_status) {
21,990,668✔
2898
    unsigned hunt_value = att_status->skill(Skill::hunt);
21,990,668✔
2899
        if(hunt_value)
21,990,668✔
2900
        {
2901
            CardStatus* hunted_status{select_first_enemy_assault(fd)};
32,633✔
2902
            if (hunted_status != nullptr)
32,633✔
2903
            {
2904
                unsigned remaining_dmg = remove_absorption(fd,hunted_status,hunt_value + hunted_status->m_enfeebled);
22,345✔
2905
                remaining_dmg = safe_minus(remaining_dmg,hunted_status->protected_value());
22,345✔
2906
                _DEBUG_MSG(1, "%s hunts %s for %u damage\n",
22,345✔
2907
                        status_description(att_status).c_str(),
2908
                        status_description(hunted_status).c_str(), remaining_dmg);
22,345✔
2909

2910
                remove_hp(fd, hunted_status, remaining_dmg);
22,345✔
2911

2912
                prepend_on_death(fd);
22,345✔
2913
                resolve_skill(fd);
22,345✔
2914
            }
2915
        }
2916
}
21,990,668✔
2917
/**
2918
 * @brief Perform Subdue skill
2919
 * 
2920
 * @param fd Field
2921
 * @param att_status Attacker status
2922
 * @param def_status Defender status
2923
 */
2924
void perform_subdue(Field* fd, CardStatus* att_status, CardStatus* def_status)
23,370,655✔
2925
{
2926
                // Skill: Subdue
2927
                unsigned subdue_value = def_status->skill(Skill::subdue);
23,370,655✔
2928
                if (__builtin_expect(subdue_value, false))
23,370,655✔
2929
                {
2930
                    _DEBUG_MSG(1, "%s subdues %s by %u\n",
849,053✔
2931
                            status_description(def_status).c_str(),
2932
                            status_description(att_status).c_str(), subdue_value);
849,053✔
2933
                    att_status->m_subdued += subdue_value;
849,053✔
2934
                    //fix negative attack
2935
                    if(att_status->calc_attack_power()<0)
849,053✔
2936
                    {
2937
                        att_status->m_temp_attack_buff -= att_status->calc_attack_power();
3,276✔
2938
                    }
2939
                    if (att_status->m_hp > att_status->max_hp())
967,367✔
2940
                    {
2941
                        _DEBUG_MSG(1, "%s loses %u HP due to subdue (max hp: %u)\n",
152,222✔
2942
                                status_description(att_status).c_str(),
2943
                                (att_status->m_hp - att_status->max_hp()),
2944
                                att_status->max_hp());
152,222✔
2945
                        att_status->m_hp = att_status->max_hp();
266,689✔
2946
                    }
2947
                }
2948
}
23,370,655✔
2949
bool check_and_perform_valor(Field* fd, CardStatus* src)
14,556,559✔
2950
{
2951
    unsigned valor_value = src->skill(Skill::valor);
14,556,559✔
2952
    if (valor_value && !src->m_sundered && skill_check<Skill::valor>(fd, src, nullptr))
29,113,118✔
2953
    {
2954
        _DEBUG_ASSERT(src->m_card->m_type == CardType::assault); //only assaults
45,500✔
2955
        unsigned opponent_player = opponent(src->m_player);
45,500✔
2956
        const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ?
45,500✔
2957
            &fd->players[opponent_player]->assaults[src->m_index] :
20,545✔
2958
            nullptr;
45,500✔
2959
        if (dst == nullptr || dst->m_hp <= 0)
20,545✔
2960
        {
2961
            _DEBUG_MSG(1, "%s loses Valor (no blocker)\n", status_description(src).c_str());
24,955✔
2962
            return false;
24,955✔
2963
        }
2964
        else if (dst->attack_power() <= src->attack_power())
20,545✔
2965
        {
2966
            _DEBUG_MSG(1, "%s loses Valor (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str());
2,325✔
2967
            return false;
2,325✔
2968
        }
2969
#ifndef NQUEST
2970
        if (src->m_player == 0)
2971
        {
2972
            fd->inc_counter(QuestType::skill_use, Skill::valor);
2973
        }
2974
#endif
2975
        _DEBUG_MSG(1, "%s activates Valor %u\n", status_description(src).c_str(), valor_value);
18,220✔
2976
        src->m_perm_attack_buff += valor_value;
18,220✔
2977
        return true;
18,220✔
2978
    }
2979
    return false;
2980
}
2981

2982
bool check_and_perform_bravery(Field* fd, CardStatus* src)
62,909,996✔
2983
{
2984
    unsigned bravery_value = src->skill(Skill::bravery);
62,909,996✔
2985
    if (bravery_value && !src->m_sundered && skill_check<Skill::bravery>(fd, src, nullptr))
125,819,992✔
2986
    {
2987
        _DEBUG_ASSERT(src->m_card->m_type == CardType::assault); //only assaults
1,863,145✔
2988
        unsigned opponent_player = opponent(src->m_player);
1,863,145✔
2989
        const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ?
1,863,145✔
2990
            &fd->players[opponent_player]->assaults[src->m_index] :
1,017,106✔
2991
            nullptr;
1,863,145✔
2992
        if (dst == nullptr || dst->m_hp <= 0)
1,017,106✔
2993
        {
2994
            _DEBUG_MSG(1, "%s loses Bravery (no blocker)\n", status_description(src).c_str());
846,039✔
2995
            return false;
846,039✔
2996
        }
2997
        else if (dst->attack_power() <= src->attack_power())
1,017,106✔
2998
        {
2999
            _DEBUG_MSG(1, "%s loses Bravery (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str());
747,921✔
3000
            return false;
747,921✔
3001
        }
3002
#ifndef NQUEST
3003
        if (src->m_player == 0)
3004
        {
3005
            fd->inc_counter(QuestType::skill_use, Skill::bravery);
3006
        }
3007
#endif
3008
        _DEBUG_MSG(1, "%s activates Bravery %u\n", status_description(src).c_str(), bravery_value);
269,185✔
3009
        src->m_perm_attack_buff += bravery_value;
269,185✔
3010

3011
        //BGE: superheroism
3012
        if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::superheroism], false))
269,185✔
3013
        {
3014
            unsigned bge_value = bravery_value * fd->bg_effects[fd->tapi][PassiveBGE::superheroism];
1,006✔
3015
            const SkillSpec ss_heal{Skill::heal, bge_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, true, 0,};
1,006✔
3016
            _DEBUG_MSG(1, "%s activates SuperHeroism: %s\n", status_description(src).c_str(),
1,006✔
3017
                    skill_description(fd->cards, ss_heal).c_str());
1,006✔
3018
            //fd->skill_queue.emplace(fd->skill_queue.begin()+0, src, ss_heal);
3019
            skill_table[Skill::heal](fd,src,ss_heal); //Probably better to perform the skill direct instead of queuing it
1,006✔
3020
        }
3021

3022
        return true;
269,185✔
3023
    }
3024
    return false;
3025
}
3026

3027
bool check_and_perform_inhibit(Field* fd, CardStatus* att_status,CardStatus* def_status)
34,295,426✔
3028
{
3029
      unsigned inhibit_value = att_status->skill(Skill::inhibit);
34,295,426✔
3030
      if (inhibit_value > def_status->m_inhibited && skill_check<Skill::inhibit>(fd, att_status, def_status))
37,254,280✔
3031
      {
3032
        _DEBUG_MSG(1, "%s inhibits %s by %u\n",
2,958,854✔
3033
                status_description(att_status).c_str(),
3034
                status_description(def_status).c_str(), inhibit_value);
2,958,854✔
3035
        def_status->m_inhibited = inhibit_value;
2,958,854✔
3036
        return true;
2,958,854✔
3037
      }
3038
      return false;
3039
}
3040
bool check_and_perform_sabotage(Field* fd, CardStatus* att_status, CardStatus* def_status)
34,295,426✔
3041
{
3042
    unsigned sabotage_value = att_status->skill(Skill::sabotage);
34,295,426✔
3043
    if (sabotage_value > def_status->m_sabotaged && skill_check<Skill::sabotage>(fd, att_status, def_status))
34,727,619✔
3044
    {
3045
        _DEBUG_MSG(1, "%s sabotages %s by %u\n",
432,193✔
3046
                status_description(att_status).c_str(),
3047
                status_description(def_status).c_str(), sabotage_value);
432,193✔
3048
        def_status->m_sabotaged = sabotage_value;
432,193✔
3049
        return true;
432,193✔
3050
    }
3051
    return false;
3052
}
3053
bool check_and_perform_disease(Field* fd, CardStatus* att_status,CardStatus* def_status)
34,295,426✔
3054
{
3055
    unsigned disease_base = att_status->skill(Skill::disease);
34,295,426✔
3056
    if(disease_base && skill_check<Skill::disease>(fd, att_status, def_status)) {
34,568,150✔
3057
        _DEBUG_MSG(1, "%s diseases %s for %u\n",
272,724✔
3058
        status_description(att_status).c_str(), status_description(def_status).c_str(), disease_base);
272,724✔
3059
        def_status->m_diseased += disease_base;
272,724✔
3060
        return true;
272,724✔
3061
    }
3062
    return false;
3063
}
3064

3065
CardStatus* check_and_perform_summon(Field* fd, CardStatus* src)
13,910,461✔
3066
{
3067
    unsigned summon_card_id = src->m_card->m_skill_value[Skill::summon];
13,910,461✔
3068
    if (summon_card_id)
13,910,461✔
3069
    {
3070
        const Card* summoned_card(fd->cards.by_id(summon_card_id));
3,434,491✔
3071
        _DEBUG_MSG(1, "%s summons %s\n", status_description(src).c_str(), summoned_card->m_name.c_str());
3,434,491✔
3072
        CardStatus* summoned_status = nullptr;
3,434,491✔
3073
        switch (summoned_card->m_type)
3,434,491✔
3074
        {
3075
            case CardType::assault:
1,188,843✔
3076
                summoned_status = PlayCard(summoned_card, fd, src->m_player, src).op<CardType::assault>(true);
1,188,843✔
3077
                return summoned_status;
1,188,843✔
3078
            case CardType::structure:
2,245,648✔
3079
                summoned_status = PlayCard(summoned_card, fd, src->m_player, src).op<CardType::structure>(true);
2,245,648✔
3080
                return summoned_status;
2,245,648✔
3081
            default:
×
3082
                _DEBUG_MSG(0, "Unknown card type: #%u %s: %u\n",
×
3083
                        summoned_card->m_id, card_description(fd->cards, summoned_card).c_str(),
3084
                        summoned_card->m_type);
×
3085
                _DEBUG_ASSERT(false);
×
3086
                __builtin_unreachable();
3087
        }
3088
    }
3089
    return nullptr;
3090
}
3091

3092

3093
    template<Skill::Skill skill_id>
3094
size_t select_targets(Field* fd, CardStatus* tsrc, const SkillSpec& s)
126,562,788✔
3095
{
3096
    size_t n_candidates;
3097
    CardStatus* src;
3098
    if(fd->fixes[Fix::revenge_on_death] && s.s2 == Skill::revenge)
126,562,788✔
3099
    {
3100
            _DEBUG_MSG(2,"FIX ON DEATH REVENGE SELECTION")
33,966✔
3101
            src = &fd->players[(tsrc->m_player+1)%2]->commander; // selection like enemy commander
33,966✔
3102
    }
33,966✔
3103
    else
3104
    {
3105
            src = tsrc;
3106
    }
3107
    switch (skill_id)
3108
    {
3109
        case Skill::mortar:
2,999,095✔
3110
            n_candidates = select_fast<Skill::siege>(fd, src, skill_targets<Skill::siege>(fd, src), s);
2,999,095✔
3111
            if (n_candidates == 0)
2,999,095✔
3112
            {
3113
                n_candidates = select_fast<Skill::strike>(fd, src, skill_targets<Skill::strike>(fd, src), s);
709,082✔
3114
            }
3115
            break;
3116

3117
        default:
123,563,693✔
3118
            n_candidates = select_fast<skill_id>(fd, src, skill_targets<skill_id>(fd, src), s);
123,563,693✔
3119
            break;
3120
    }
3121

3122
    // (false-loop)
3123
    unsigned n_selected = n_candidates;
127,126,755✔
3124
    do
126,562,788✔
3125
    {
3126
        // no candidates
3127
        if (n_candidates == 0)
124,272,775✔
3128
        { break; }
3129

3130
        // show candidates (debug)
3131
        _DEBUG_SELECTION("%s", skill_names[skill_id].c_str());
78,065,986✔
3132

3133
        // analyze targets count / skill
3134
        unsigned n_targets = s.n > 0 ? s.n : 1;
76,910,460✔
3135
        if (s.all || n_targets >= n_candidates || skill_id == Skill::mend || skill_id == Skill::fortify)  // target all or mend
76,910,460✔
3136
        { break; }
3137

3138
        // shuffle & trim
3139
        for (unsigned i = 0; i < n_targets; ++i)
42,478,172✔
3140
        {
3141
            std::swap(fd->selection_array[i], fd->selection_array[fd->rand(i, n_candidates - 1)]);
21,439,196✔
3142
        }
3143
        fd->selection_array.resize(n_targets);
21,038,976✔
3144
        if (n_targets > 1)
21,038,976✔
3145
        {
3146
            std::sort(fd->selection_array.begin(), fd->selection_array.end(),
274,424✔
3147
                    [](const CardStatus * a, const CardStatus * b) { return a->m_index < b->m_index; });
662,077✔
3148
        }
3149
        n_selected = n_targets;
3150

3151
    } while (false); // (end)
3152

3153
    return n_selected;
126,562,788✔
3154
}
3155

3156
    template<Skill::Skill skill_id>
3157
void perform_targetted_allied_fast(Field* fd, CardStatus* src, const SkillSpec& s)
80,072,545✔
3158
{
3159
    select_targets<skill_id>(fd, src, s);
80,072,545✔
3160
    unsigned num_inhibited = 0;
80,072,545✔
3161
#ifndef NQUEST
3162
    bool has_counted_quest = false;
3163
#endif
3164
    bool src_overloaded = src->m_overloaded;
80,072,545✔
3165
    std::vector<CardStatus*> selection_array = fd->selection_array;
80,072,545✔
3166
    for (CardStatus * dst: selection_array)
165,646,033✔
3167
    {
3168
        if (dst->m_inhibited > 0 && !src_overloaded)
85,573,488✔
3169
        {
3170
            _DEBUG_MSG(1, "%s %s on %s but it is inhibited\n",
5,336,396✔
3171
                    status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
3172
                    status_description(dst).c_str());
3173
            -- dst->m_inhibited;
4,877,686✔
3174
            ++ num_inhibited;
4,877,686✔
3175
            continue;
4,877,686✔
3176
        }
4,877,686✔
3177
        check_and_perform_skill<skill_id>(fd, src, dst, s, false
80,695,802✔
3178
#ifndef NQUEST
3179
                , has_counted_quest
3180
#endif
3181
                );
3182
    }
3183

3184
    // Passive BGE: Divert
3185
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::divert], false)
80,072,545✔
3186
            && (num_inhibited > 0))
80,072,545✔
3187
    {
3188
        SkillSpec diverted_ss = s;
5,642✔
3189
        diverted_ss.y = allfactions;
5,642✔
3190
        diverted_ss.n = 1;
5,642✔
3191
        diverted_ss.all = false;
5,642✔
3192
        for (unsigned i = 0; i < num_inhibited; ++ i)
11,318✔
3193
        {
3194
            select_targets<skill_id>(fd, &fd->tip->commander, diverted_ss);
5,676✔
3195
            std::vector<CardStatus*> selection_array = fd->selection_array;
5,676✔
3196
            for (CardStatus * dst: selection_array)
9,510✔
3197
            {
3198
                if (dst->m_inhibited > 0)
3,834✔
3199
                {
3200
                    _DEBUG_MSG(1, "%s %s (Diverted) on %s but it is inhibited\n",
642✔
3201
                            status_description(src).c_str(), skill_short_description(fd->cards, diverted_ss).c_str(),
3202
                            status_description(dst).c_str());
3203
                    -- dst->m_inhibited;
214✔
3204
                    continue;
214✔
3205
                }
214✔
3206
                _DEBUG_MSG(1, "%s %s (Diverted) on %s\n",
10,860✔
3207
                        status_description(src).c_str(), skill_short_description(fd->cards, diverted_ss).c_str(),
3208
                        status_description(dst).c_str());
3209
                perform_skill<skill_id>(fd, src, dst, diverted_ss);
3,639✔
3210
            }
3211
        }
3212
    }
3213
}
80,072,545✔
3214

3215
void perform_targetted_allied_fast_rush(Field* fd, CardStatus* src, const SkillSpec& s)
×
3216
{
3217
    if (src->m_card->m_type == CardType::commander)
×
3218
    {  // Passive BGE skills are casted as by commander
3219
        perform_targetted_allied_fast<Skill::rush>(fd, src, s);
×
3220
        return;
×
3221
    }
3222
    if (src->m_rush_attempted)
×
3223
    {
3224
        _DEBUG_MSG(2, "%s does not check Rush again.\n", status_description(src).c_str());
×
3225
        return;
×
3226
    }
3227
    _DEBUG_MSG(1, "%s attempts to activate Rush.\n", status_description(src).c_str());
×
3228
    perform_targetted_allied_fast<Skill::rush>(fd, src, s);
×
3229
    src->m_rush_attempted = true;
×
3230
}
3231

3232
    template<Skill::Skill skill_id>
3233
void perform_targetted_hostile_fast(Field* fd, CardStatus* src, const SkillSpec& s)
46,484,567✔
3234
{
3235
    select_targets<skill_id>(fd, src, s);
46,484,567✔
3236
    std::vector<CardStatus *> paybackers;
46,484,567✔
3237
#ifndef NQUEST
3238
    bool has_counted_quest = false;
3239
#endif
3240
    const bool has_turningtides = (fd->bg_effects[fd->tapi][PassiveBGE::turningtides] && (skill_id == Skill::weaken || skill_id == Skill::sunder));
46,484,567✔
3241
    unsigned turningtides_value(0), old_attack(0);
46,484,567✔
3242

3243
    // apply skill to each target(dst)
3244
    unsigned selection_array_len = fd->selection_array.size();
46,484,567✔
3245
    std::vector<CardStatus*> selection_array = fd->selection_array;
46,484,567✔
3246
    for (CardStatus * dst: selection_array)
104,496,628✔
3247
    {
3248
        // TurningTides
3249
        if (__builtin_expect(has_turningtides, false))
15,685,900✔
3250
        {
3251
            old_attack = dst->attack_power();
11,148✔
3252
        }
3253

3254
        // check & apply skill to target(dst)
3255
        if (check_and_perform_skill<skill_id>(fd, src, dst, s, ! (src->m_overloaded || (__builtin_expect(fd->fixes[Fix::dont_evade_mimic_selection],true) &&  skill_id == Skill::mimic))
58,292,340✔
3256
#ifndef NQUEST
3257
                    , has_counted_quest
3258
#endif
3259
                    ))
3260
        {
3261
            // TurningTides: get max attack decreasing
3262
            if (__builtin_expect(has_turningtides, false))
10,305,139✔
3263
            {
3264
                turningtides_value = std::max(turningtides_value, safe_minus(old_attack, dst->attack_power()));
25,637✔
3265
            }
3266

3267
            // Payback/Revenge: collect paybackers/revengers
3268
            unsigned payback_value = dst->skill(Skill::payback) + dst->skill(Skill::revenge);
40,770,180✔
3269
            if ((s.id != Skill::mimic) && (dst->m_paybacked < payback_value) && skill_check<Skill::payback>(fd, dst, src))
40,770,180✔
3270
            {
3271
                paybackers.reserve(selection_array_len);
1,074,597✔
3272
                paybackers.push_back(dst);
1,074,597✔
3273
            }
3274
        }
3275
    }
3276

3277
    // apply TurningTides
3278
    if (__builtin_expect(has_turningtides, false) && (turningtides_value > 0))
14,031,857✔
3279
    {
3280
        SkillSpec ss_rally{Skill::rally, turningtides_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, s.all, 0,};
8,393✔
3281
        _DEBUG_MSG(1, "TurningTides %u!\n", turningtides_value);
16,786✔
3282
        perform_targetted_allied_fast<Skill::rally>(fd, &fd->players[src->m_player]->commander, ss_rally);
8,393✔
3283
    }
3284

3285
    prepend_on_death(fd);  // skills
46,484,567✔
3286

3287
    // Payback/Revenge
3288
    for (CardStatus * pb_status: paybackers)
47,559,164✔
3289
    {
3290
        turningtides_value = 0;
1,074,597✔
3291

3292
        // apply Revenge
3293
        if (pb_status->skill(Skill::revenge))
1,074,597✔
3294
        {
3295
            unsigned revenged_count(0);
3296
            for (unsigned case_index(0); case_index < 3; ++ case_index)
4,296,660✔
3297
            {
3298
                CardStatus * target_status;
3299
#ifndef NDEBUG
3300
                const char * target_desc;
3301
#endif
3302
                switch (case_index)
3,222,495✔
3303
                {
3304
                    // revenge to left
3305
                    case 0:
1,074,165✔
3306
                        if (!(target_status = fd->left_assault(src))) { continue; }
281,005✔
3307
#ifndef NDEBUG
3308
                        target_desc = "left";
3309
#endif
3310
                        break;
3311

3312
                        // revenge to core
3313
                    case 1:
3314
                        target_status = src;
3315
#ifndef NDEBUG
3316
                        target_desc = "core";
3317
#endif
3318
                        break;
3319

3320
                        // revenge to right
3321
                    case 2:
1,074,165✔
3322
                        if (!(target_status = fd->right_assault(src))) { continue; }
2,499,487✔
3323
#ifndef NDEBUG
3324
                        target_desc = "right";
3325
#endif
3326
                        break;
3327

3328
                        // wtf?
3329
                    default:
3330
                        __builtin_unreachable();
3331
                }
3332

3333
                // skip illegal target
3334
                if (!skill_predicate<skill_id>(fd, target_status, target_status, s))
2,696,478✔
3335
                {
3336
                    continue;
169,069✔
3337
                }
3338

3339
                // skip dead target
3340
                if (!is_alive(target_status))
2,527,409✔
3341
                {
3342
#ifndef NDEBUG
3343
                    _DEBUG_MSG(1, "(CANCELLED: target unit dead) %s Revenge (to %s) %s on %s\n",
×
3344
                            status_description(pb_status).c_str(), target_desc,
3345
                            skill_short_description(fd->cards, s).c_str(), status_description(target_status).c_str());
3346
#endif
3347
                    continue;
×
3348
                }
×
3349

3350
                // TurningTides
3351
                if (__builtin_expect(has_turningtides, false))
105,220✔
3352
                {
3353
                    old_attack = target_status->attack_power();
806✔
3354
                }
3355

3356
                // apply revenged skill
3357
#ifndef NDEBUG
3358
                _DEBUG_MSG(1, "%s Revenge (to %s) %s on %s\n",
2,708,071✔
3359
                        status_description(pb_status).c_str(), target_desc,
3360
                        skill_short_description(fd->cards, s).c_str(), status_description(target_status).c_str());
3361
#endif
3362
                perform_skill<skill_id>(fd, pb_status, target_status, s);
2,527,409✔
3363
                ++ revenged_count;
2,527,409✔
3364

3365
                // revenged TurningTides: get max attack decreasing
3366
                if (__builtin_expect(has_turningtides, false))
105,220✔
3367
                {
3368
                    turningtides_value = std::max(turningtides_value, safe_minus(old_attack, target_status->attack_power()));
2,089✔
3369
                }
3370
            }
3371
            if (revenged_count)
1,074,165✔
3372
            {
3373
                // consume remaining payback/revenge
3374
                ++ pb_status->m_paybacked;
1,052,463✔
3375

3376
                // apply TurningTides
3377
                if (__builtin_expect(has_turningtides, false) && (turningtides_value > 0))
56,163✔
3378
                {
3379
                    SkillSpec ss_rally{Skill::rally, turningtides_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
424✔
3380
                    _DEBUG_MSG(1, "Paybacked TurningTides %u!\n", turningtides_value);
848✔
3381
                    perform_targetted_allied_fast<Skill::rally>(fd, &fd->players[pb_status->m_player]->commander, ss_rally);
424✔
3382
                }
3383
            }
3384
        }
3385
        // apply Payback
3386
        else
3387
        {
3388
            // skip illegal target(src)
3389
            if (!skill_predicate<skill_id>(fd, src, src, s))
432✔
3390
            {
3391
                continue;
130✔
3392
            }
3393

3394
            // skip dead target(src)
3395
            if (!is_alive(src))
302✔
3396
            {
3397
                _DEBUG_MSG(1, "(CANCELLED: src unit dead) %s Payback %s on %s\n",
×
3398
                        status_description(pb_status).c_str(), skill_short_description(fd->cards, s).c_str(),
3399
                        status_description(src).c_str());
3400
                continue;
×
3401
            }
×
3402

3403
            // TurningTides
3404
            if (__builtin_expect(has_turningtides, false))
99✔
3405
            {
3406
                old_attack = src->attack_power();
×
3407
            }
3408

3409
            // apply paybacked skill
3410
            _DEBUG_MSG(1, "%s Payback %s on %s\n",
906✔
3411
                    status_description(pb_status).c_str(), skill_short_description(fd->cards, s).c_str(), status_description(src).c_str());
3412
            perform_skill<skill_id>(fd, pb_status, src, s);
302✔
3413
            ++ pb_status->m_paybacked;
302✔
3414

3415
            // handle paybacked TurningTides
3416
            if (__builtin_expect(has_turningtides, false))
99✔
3417
            {
3418
                turningtides_value = std::max(turningtides_value, safe_minus(old_attack, src->attack_power()));
×
3419
                if (turningtides_value > 0)
×
3420
                {
3421
                    SkillSpec ss_rally{Skill::rally, turningtides_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
×
3422
                    _DEBUG_MSG(1, "Paybacked TurningTides %u!\n", turningtides_value);
×
3423
                    perform_targetted_allied_fast<Skill::rally>(fd, &fd->players[pb_status->m_player]->commander, ss_rally);
×
3424
                }
3425
            }
3426
        }
3427
    }
3428

3429
    prepend_on_death(fd,true);  // paybacked skills
46,484,567✔
3430
}
46,484,567✔
3431

3432
//------------------------------------------------------------------------------
3433
inline unsigned evaluate_brawl_score(Field* fd, unsigned player)
×
3434
{
3435
    const auto & p = fd->players;
×
3436
    return 55
×
3437
        // - (10 - p[player]->deck->cards.size())
3438
        // + (10 - p[opponent(player)]->deck->cards.size())
3439
        + p[opponent(player)]->total_cards_destroyed
×
3440
        + p[player]->deck->shuffled_cards.size()
×
3441
        - (unsigned)((fd->turn+7)/8);
×
3442
}
3443

3444
inline unsigned evaluate_war_score(Field* fd, unsigned player)
×
3445
{
3446
    return 208 - ((unsigned)(fd->turn)/2)*4;
×
3447
}
3448
int evaluate_card(Field* fd,const Card* cs);
3449
int evaluate_skill(Field* fd,const Card* c , SkillSpec* ss)
×
3450
{
3451
        // TODO optimize this
3452
        int tvalue = ss->x;
×
3453

3454
        if(ss->card_id != 0)tvalue += 1*evaluate_card(fd,card_by_id_safe(fd->cards,ss->card_id));
×
3455
        tvalue += 10*(ss->id==Skill::flurry);
×
3456
        tvalue += 10*(ss->id==Skill::jam);
×
3457
        tvalue += 5*(ss->id==Skill::overload);
×
3458
        tvalue += 2*(ss->id==Skill::flying);
×
3459
        tvalue += 2*(ss->id==Skill::evolve);
×
3460
        tvalue += 2*(ss->id==Skill::wall);
×
3461
        tvalue += 5*(ss->id==Skill::tribute);
×
3462

3463
        tvalue *= 1.+1.5*(ss->id==Skill::flurry);
×
3464
        tvalue *= 1.+1.5*(ss->id==Skill::drain);
×
3465
        tvalue *= 1.+1.5*(ss->id==Skill::mortar);
×
3466
        tvalue *= 1.+1.5*(ss->id==Skill::scavenge);
×
3467
        tvalue *= 1.+1.5*(ss->id==Skill::disease);
×
3468

3469
        tvalue *= 1.+1.3*(ss->id==Skill::rally);
×
3470
        tvalue *= 1.+1.3*(ss->id==Skill::strike);
×
3471

3472
        tvalue *= 1.+1.2*(ss->id==Skill::avenge);
×
3473
        tvalue *= 1.+1.1*(ss->id==Skill::sunder);
×
3474
        tvalue *= 1.+1.1*(ss->id==Skill::venom);
×
3475

3476
        tvalue *= 1.+1.0*(ss->id==Skill::evade);
×
3477
        tvalue *= 1.+1.0*(ss->id==Skill::enfeeble);
×
3478

3479
        tvalue *= 1.+0.2*(ss->id==Skill::protect);
×
3480

3481
        tvalue *= 1.+0.2*(ss->id==Skill::fortify);
×
3482
        tvalue *= 1.+0.5*(ss->id==Skill::mend);
×
3483

3484
        tvalue *= 1.+0.4*(ss->id==Skill::jam);
×
3485
        tvalue *= 1.+0.4*(ss->id==Skill::overload);
×
3486
        //tvalue *= 1.+0.4*(ss->id==Skill::rupture);
3487
        tvalue *= 1.+0.4*(ss->id==Skill::bravery);
×
3488
        tvalue *= 1.+0.4*(ss->id==Skill::entrap);
×
3489
        tvalue *= 1.+0.4*(ss->id==Skill::heal);
×
3490

3491

3492
        tvalue *= 1.+0.3*(ss->id==Skill::revenge);
×
3493
        tvalue *= 1.+0.3*(ss->id==Skill::enrage);
×
3494

3495

3496
        //tvalue *= 1.+2.1*(ss->id==Skill::hunt);
3497
        tvalue *= 1.+0.1*(ss->id==Skill::mark);
×
3498
        tvalue *= 1.+0.1*(ss->id==Skill::coalition);
×
3499
        tvalue *= 1.+0.1*(ss->id==Skill::legion);
×
3500
        //tvalue *= 1.+1.1*(ss->id==Skill::barrier);
3501
        tvalue *= 1.+0.1*(ss->id==Skill::pierce);
×
3502
        tvalue *= 1.+0.1*(ss->id==Skill::armor);
×
3503
        //tvalue *= 1.+0.1*(ss->id==Skill::swipe);
3504
        //tvalue *= 1.+0.1*(ss->id==Skill::berserk);
3505
        tvalue *= 1.-0.1*(ss->id==Skill::weaken);
×
3506

3507

3508

3509
        tvalue *= 1.-0.5 *(ss->id==Skill::sabotage); //sucks
×
3510
        tvalue *= 1.-0.5 *(ss->id==Skill::inhibit); //sucks
×
3511
        tvalue *= 1.-0.5 *(ss->id==Skill::corrosive); //sucks
×
3512
        tvalue *= 1.-0.5 *(ss->id==Skill::payback); //sucks
×
3513
        tvalue *= 1.-0.5 *(ss->id==Skill::leech); //sucks
×
3514

3515

3516
        tvalue *= 1.+1*ss->all;
×
3517
        tvalue *= 1.-1./5.*ss->all*(ss->y!=0);
×
3518
        tvalue *= 1.+1*std::min<int>(3,ss->n);
×
3519
        tvalue *= 1.-1./3.* ((c->m_skill_trigger[ss->id] == Skill::Trigger::death) + (c->m_skill_trigger[ss->id] == Skill::Trigger::play));
×
3520
        tvalue *= 1./(2.+ss->c);
×
3521
        //if(tvalue == 0) std::cout << ss->id << " "<<tvalue << std::endl;
3522
        //if(tvalue > 10000) std::cout << ss->id <<" "<< tvalue << std::endl;
3523
        return 0.9*tvalue; // 0.85
×
3524
}
3525
int evaluate_card(Field* fd,const Card* cs)
×
3526
{
3527
        int value = 0;
×
3528
        value += cs->m_health;
×
3529
        value += 2*cs->m_attack;
×
3530
        for( auto ss : cs->m_skills) {
×
3531
                value += evaluate_skill(fd,cs,&ss);
×
3532
        }
3533
        int denom_scale = 1+cs->m_delay*0;
×
3534
        //if(value > 10000) std::cout << cs->m_name << value << std::endl;
3535
        return value /denom_scale;
×
3536
}
3537
int evaluate_cardstatus(Field* fd,CardStatus* cs)
×
3538
{
3539
        int value = 0;
×
3540
        value += cs->m_hp;
×
3541
        value += 2*cs->attack_power();
×
3542
        value += cs->protected_value();
×
3543
        for( auto ss : cs->m_card->m_skills) {
×
3544
                value += evaluate_skill(fd,cs->m_card,&ss);
×
3545
        }
3546
        value -= (cs->m_enfeebled);
×
3547
        int denom_scale = 1+cs->m_delay*0;
×
3548
#ifdef DEBUG
3549
        if(value > 10000) std::cout << cs->m_card->m_name << value <<std::endl;
3550
#endif
3551
        return value /denom_scale;
×
3552
}
3553
// calculate a value for current field, high values are better for fd->tap
3554
// dead commander -> the player gets zero value
3555
int evaluate_field(Field* fd)
×
3556
{
3557
        int value = 0;
×
3558

3559
        int scale = is_alive(&fd->tap->commander);
×
3560
        auto& assaults(fd->tap->assaults);
×
3561
        auto& structures(fd->tap->structures);
×
3562

3563

3564
        value += 0.5*scale * evaluate_cardstatus(fd,&fd->tap->commander);
×
3565
        for(unsigned index(0); index < assaults.size();++index)
×
3566
        {
3567
                value += scale * evaluate_cardstatus(fd,&assaults[index]);
×
3568
        }
3569
        for(unsigned index(0); index < structures.size();++index)
×
3570
        {
3571
                value += scale * evaluate_cardstatus(fd,&structures[index]);
×
3572
        }
3573

3574
        scale = is_alive(&fd->tip->commander);
×
3575
        auto& eassaults(fd->tip->assaults);
×
3576
        auto& estructures(fd->tip->structures);
×
3577
        value -= 0.5*scale * evaluate_cardstatus(fd,&fd->tip->commander);
×
3578
        for(unsigned index(0); index < eassaults.size();++index)
×
3579
        {
3580
                value -= (scale * evaluate_cardstatus(fd,&eassaults[index]));
×
3581
        }
3582
        for(unsigned index(0); index < estructures.size();++index)
×
3583
        {
3584
                value -= (scale * evaluate_cardstatus(fd,&estructures[index]));
×
3585
        }
3586
        return value;
×
3587
}
3588

3589

3590
Results<uint64_t> evaluate_sim_result(Field* fd, bool single_turn_both)
1,396,373✔
3591
{
3592
    typedef unsigned points_score_type;
1,396,373✔
3593
    const auto & p = fd->players;
1,396,373✔
3594
    unsigned raid_damage = 0;
1,396,373✔
3595
#ifndef NQUEST
3596
    unsigned quest_score = 0;
3597
#endif
3598

3599
    if(single_turn_both)
1,396,373✔
3600
    {
3601
        bool sign = evaluate_field(fd)<0;
×
3602
        unsigned val = evaluate_field(fd) *(1-2*sign);
×
3603
        return {!is_alive(&fd->players[1]->commander),sign,!is_alive(&fd->players[0]->commander),val,1};
×
3604
    }
3605
    switch (fd->optimization_mode)
1,396,373✔
3606
    {
3607
        case OptimizationMode::raid:
×
3608
            raid_damage = 15
×
3609
                + (p[1]->total_nonsummon_cards_destroyed)
×
3610
                - (10 * p[1]->commander.m_hp / p[1]->commander.max_hp());
×
3611
            break;
×
3612
#ifndef NQUEST
3613
        case OptimizationMode::quest:
3614
            if (fd->quest.quest_type == QuestType::card_survival)
3615
            {
3616
                for (const auto & status: p[0]->assaults.m_indirect)
3617
                { fd->quest_counter += (fd->quest.quest_key == status->m_card->m_id); }
3618
                for (const auto & status: p[0]->structures.m_indirect)
3619
                { fd->quest_counter += (fd->quest.quest_key == status->m_card->m_id); }
3620
                for (const auto & card: p[0]->deck->shuffled_cards)
3621
                { fd->quest_counter += (fd->quest.quest_key == card->m_id); }
3622
            }
3623
            quest_score = fd->quest.must_fulfill ? (fd->quest_counter >= fd->quest.quest_value ? fd->quest.quest_score : 0) : std::min<unsigned>(fd->quest.quest_score, fd->quest.quest_score * fd->quest_counter / fd->quest.quest_value);
3624
            _DEBUG_MSG(1, "Quest: %u / %u = %u%%.\n", fd->quest_counter, fd->quest.quest_value, quest_score);
3625
            break;
3626
#endif
3627
        default:
3628
            break;
3629
    }
3630
    // you lose
3631
    if(!is_alive(&fd->players[0]->commander))
1,396,373✔
3632
    {
3633
        _DEBUG_MSG(1, "You lose.\n");
168,016✔
3634
        switch (fd->optimization_mode)
168,016✔
3635
        {
3636
            case OptimizationMode::raid: return {0, 0, 1, (points_score_type)raid_damage,1};
×
3637
            case OptimizationMode::brawl: return {0, 0, 1, (points_score_type) 5,1};
×
3638
            case OptimizationMode::brawl_defense:
×
3639
                                          {
×
3640
                                              unsigned enemy_brawl_score = evaluate_brawl_score(fd, 1);
×
3641
                                              unsigned max_score = max_possible_score[(size_t)OptimizationMode::brawl_defense];
×
3642
                                              if(enemy_brawl_score> max_score)
×
3643
                                                std::cerr << "WARNING: enemy_brawl_score > max_possible_brawl_score" << std::endl;
×
3644
                                              return {0, 0, 1, (points_score_type)safe_minus(max_score , enemy_brawl_score),1};
×
3645
                                          }
3646
            case OptimizationMode::war: return {0,0,1, (points_score_type) 20,1};
×
3647
            case OptimizationMode::war_defense:
×
3648
                                        {
×
3649
                                            unsigned enemy_war_score = evaluate_war_score(fd, 1);
×
3650
                                            unsigned max_score = max_possible_score[(size_t)OptimizationMode::war_defense];
×
3651
                                            if(enemy_war_score> max_score)
×
3652
                                                std::cerr << "WARNING: enemy_war_score > max_possible_war_score" << std::endl;
×
3653
                                            return {0, 0, 1, (points_score_type)safe_minus(max_score , enemy_war_score),1};
×
3654
                                        }
3655
#ifndef NQUEST
3656
            case OptimizationMode::quest: return {0, 0, 1, (points_score_type)(fd->quest.must_win ? 0 : quest_score),1};
3657
#endif
3658
            default: return {0, 0, 1, 0,1};
168,016✔
3659
        }
3660
    }
3661
    // you win
3662
    if(!is_alive(&fd->players[1]->commander))
1,228,357✔
3663
    {
3664
        _DEBUG_MSG(1, "You win.\n");
1,207,246✔
3665
        switch (fd->optimization_mode)
1,207,246✔
3666
        {
3667
            case OptimizationMode::brawl:
×
3668
                {
×
3669
                    unsigned brawl_score = evaluate_brawl_score(fd, 0);
×
3670
                    return {1, 0, 0, (points_score_type)brawl_score,1};
×
3671
                }
3672
            case OptimizationMode::brawl_defense:
×
3673
                {
×
3674
                    unsigned max_score = max_possible_score[(size_t)OptimizationMode::brawl_defense];
×
3675
                    unsigned min_score = min_possible_score[(size_t)OptimizationMode::brawl_defense];
×
3676
                    return {1, 0, 0, (points_score_type)(max_score - min_score),1};
×
3677
                }
3678
            case OptimizationMode::campaign:
×
3679
                {
×
3680
                    unsigned total_dominions_destroyed = (p[0]->deck->alpha_dominion != nullptr) - p[0]->structures.count(is_it_dominion);
×
3681
                    unsigned campaign_score = 100 - 10 * (p[0]->total_nonsummon_cards_destroyed - total_dominions_destroyed);
×
3682
                    return {1, 0, 0, (points_score_type)campaign_score,1};
×
3683
                }
3684
            case OptimizationMode::war:
×
3685
                {
×
3686
                    unsigned war_score = evaluate_war_score(fd, 0);
×
3687
                    return {1,0,0, (points_score_type) war_score,1};
×
3688
                }
3689
            case OptimizationMode::war_defense:
×
3690
                {
×
3691
                    unsigned max_score = max_possible_score[(size_t)OptimizationMode::war_defense];
×
3692
                    unsigned min_score = min_possible_score[(size_t)OptimizationMode::war_defense];
×
3693
                    return {1, 0, 0, (points_score_type)(max_score - min_score),1};
×
3694
                }
3695
#ifndef NQUEST
3696
            case OptimizationMode::quest: return {1, 0, 0, (points_score_type)(fd->quest.win_score + quest_score),1};
3697
#endif
3698
            default:
1,207,246✔
3699
                                          return {1, 0, 0, 100,1};
1,207,246✔
3700
        }
3701
    }
3702
    if (fd->turn > turn_limit)
21,111✔
3703
    {
3704
        _DEBUG_MSG(1, "Stall after %u turns.\n", turn_limit);
21,111✔
3705
        switch (fd->optimization_mode)
21,111✔
3706
        {
3707
            case OptimizationMode::defense: return {0, 1, 0, 100,1};
×
3708
            case OptimizationMode::raid: return {0, 1, 0, (points_score_type)raid_damage,1};
×
3709
            case OptimizationMode::brawl: return {0, 1, 0, 5,1};
×
3710
            case OptimizationMode::brawl_defense:
×
3711
                                          {
×
3712
                                              unsigned max_score = max_possible_score[(size_t)OptimizationMode::brawl_defense];
×
3713
                                              unsigned min_score = min_possible_score[(size_t)OptimizationMode::brawl_defense];
×
3714
                                              return {1, 0, 0, (points_score_type)(max_score - min_score),1};
×
3715
                                          }
3716
            case OptimizationMode::war: return {0,1,0, (points_score_type) 20,1};
×
3717
            case OptimizationMode::war_defense:
×
3718
                                        {
×
3719
                                            unsigned max_score = max_possible_score[(size_t)OptimizationMode::war_defense];
×
3720
                                            unsigned min_score = min_possible_score[(size_t)OptimizationMode::war_defense];
×
3721
                                            return {1, 0, 0, (points_score_type)(max_score - min_score),1};
×
3722
                                        }
3723
#ifndef NQUEST
3724
            case OptimizationMode::quest: return {0, 1, 0, (points_score_type)(fd->quest.must_win ? 0 : quest_score),1};
3725
#endif
3726
            default: return {0, 1, 0, 0,1};
21,111✔
3727
        }
3728
    }
3729

3730
    // Huh? How did we get here?
3731
    assert(false);
×
3732
    return {0, 0, 0, 0,1};
3733
}
3734

3735
//------------------------------------------------------------------------------
3736
//turns_both sets the number of turns to sim before exiting before winner exists.
3737
Results<uint64_t> play(Field* fd,bool skip_init, bool skip_preplay,unsigned turns_both)
1,396,373✔
3738
{
3739
    if(!skip_init){ //>>> start skip init
1,396,373✔
3740
        fd->players[0]->commander.m_player = 0;
1,396,373✔
3741
        fd->players[1]->commander.m_player = 1;
1,396,373✔
3742
        fd->tapi = fd->gamemode == surge ? 1 : 0;
1,396,373✔
3743
        fd->tipi = opponent(fd->tapi);
1,396,373✔
3744
        fd->tap = fd->players[fd->tapi];
1,396,373✔
3745
        fd->tip = fd->players[fd->tipi];
1,396,373✔
3746
        fd->end = false;
1,396,373✔
3747

3748
        // Play dominion & fortresses
3749
        for (unsigned _(0), ai(fd->tapi); _ < 2; ++_)
4,189,119✔
3750
        {
3751
            if (fd->players[ai]->deck->alpha_dominion)
2,792,746✔
3752
            { PlayCard(fd->players[ai]->deck->alpha_dominion, fd, ai, &fd->players[ai]->commander).op<CardType::structure>(); }
1,515,134✔
3753
            for (const Card* played_card: fd->players[ai]->deck->shuffled_forts)
5,585,492✔
3754
            {
3755

3756
                switch (played_card->m_type)
×
3757
                {
3758
                    case CardType::assault:
×
3759
                        PlayCard(played_card, fd, ai, &fd->players[ai]->commander).op<CardType::assault>();
×
3760
                        break;
×
3761
                    case CardType::structure:
×
3762
                        PlayCard(played_card, fd, ai, &fd->players[ai]->commander).op<CardType::structure>();
×
3763
                        break;
×
3764
                    case CardType::commander:
×
3765
                    case CardType::num_cardtypes:
×
3766
                        _DEBUG_MSG(0, "Unknown card type: #%u %s: %u\n",
×
3767
                                played_card->m_id, card_description(fd->cards, played_card).c_str(), played_card->m_type);
×
3768
                        assert(false);
×
3769
                        break;
3770
                }
3771
            }
3772
            resolve_skill(fd);
2,792,746✔
3773
            std::swap(fd->tapi, fd->tipi);
2,792,746✔
3774
            std::swap(fd->tap, fd->tip);
2,792,746✔
3775
            ai = opponent(ai);
2,792,746✔
3776
        }
3777
    }//>>> end skip init
3778
    unsigned both_turn_limit = fd->turn+2*turns_both;
1,396,373✔
3779
    while(__builtin_expect(fd->turn <= turn_limit && !fd->end && (turns_both==0 || fd->turn < both_turn_limit), true))
19,603,889✔
3780
    {
3781
        if(!skip_preplay){ //>>> start skip init
19,582,778✔
3782

3783
            fd->current_phase = Field::playcard_phase;
19,582,778✔
3784
            // Initialize stuff, remove dead cards
3785
            _DEBUG_MSG(1, "------------------------------------------------------------------------\n"
19,582,778✔
3786
                    "TURN %u begins for %s\n", fd->turn, status_description(&fd->tap->commander).c_str());
19,582,778✔
3787

3788
            // reduce timers & perform triggered skills (like Summon)
3789
            fd->prepare_action();
19,582,778✔
3790
            turn_start_phase(fd); // summon may postpone skills to be resolved
19,582,778✔
3791
            resolve_skill(fd); // resolve postponed skills recursively
19,582,778✔
3792
            fd->finalize_action();
19,582,778✔
3793

3794
            //bool bge_megamorphosis = fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis];
3795

3796
        }//>>> end skip init
3797
        else { skip_preplay = false;}
3798
        // Play a card
3799
        const Card* played_card(fd->tap->deck->next(fd));
19,582,778✔
3800
        if (played_card)
19,582,778✔
3801
        {
3802

3803
            // Begin 'Play Card' phase action
3804
            fd->prepare_action();
17,707,218✔
3805

3806
            // Play selected card
3807
            //CardStatus* played_status = nullptr;
3808
            switch (played_card->m_type)
17,707,218✔
3809
            {
3810
                case CardType::assault:
16,879,826✔
3811
                    PlayCard(played_card, fd, fd->tapi, &fd->tap->commander).op<CardType::assault>();
16,879,826✔
3812
                    break;
16,879,826✔
3813
                case CardType::structure:
827,392✔
3814
                    PlayCard(played_card, fd, fd->tapi, &fd->tap->commander).op<CardType::structure>();
827,392✔
3815
                    break;
827,392✔
3816
                case CardType::commander:
×
3817
                case CardType::num_cardtypes:
×
3818
                    _DEBUG_MSG(0, "Unknown card type: #%u %s: %u\n",
×
3819
                            played_card->m_id, card_description(fd->cards, played_card).c_str(), played_card->m_type);
×
3820
                    assert(false);
×
3821
                    break;
3822
            }
3823
            resolve_skill(fd); // resolve postponed skills recursively
17,707,218✔
3824
            //status_description(played_status)
3825
            //_DEBUG_MSG(3,"Card played: %s", status_description(played_status).c_str());
3826
            // End 'Play Card' phase action
3827
            fd->finalize_action();
17,707,218✔
3828

3829

3830

3831
        }
3832
        if (__builtin_expect(fd->end, false)) { break; }
19,582,778✔
3833

3834
        //-------------------------------------------------
3835
        // Phase: (Later-) Enhance, Inhibit, Sabotage, Disease
3836
        //-------------------------------------------------
3837
        //Skill: Enhance
3838
        //Perform later enhance for commander
3839
        if(!fd->fixes[Fix::enhance_early]) {
19,582,778✔
3840
        check_and_perform_later_enhance(fd,&fd->tap->commander);
×
3841
        auto& structures(fd->tap->structures);
×
3842
        for(unsigned index(0); index < structures.size(); ++index)
×
3843
        {
3844
            CardStatus * status = &structures[index];
×
3845
            //enhance everything else after card was played
3846
            check_and_perform_later_enhance(fd,status);
×
3847
        }
3848
        }
3849
        //Perform Inhibit, Sabotage, Disease
3850
        auto& assaults(fd->tap->assaults);
19,582,778✔
3851
        for(unsigned index(0); index < assaults.size(); ++index)
76,163,328✔
3852
        {
3853
            CardStatus * att_status = &assaults[index];
56,580,550✔
3854
            if(att_status->m_index >= fd->tip->assaults.size())continue; //skip no enemy
56,580,550✔
3855
            auto def_status = &fd->tip->assaults[att_status->m_index];
34,295,602✔
3856
            if(!is_alive(def_status))continue; //skip dead
34,295,602✔
3857

3858
            check_and_perform_inhibit(fd,att_status,def_status);
34,295,426✔
3859
            check_and_perform_sabotage(fd,att_status,def_status);
34,295,426✔
3860
            check_and_perform_disease(fd,att_status,def_status);
34,295,426✔
3861
        }
3862
        //-------------------------------------------------
3863

3864
        // Evaluate Passive BGE Heroism skills
3865
        if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::heroism], false))
19,582,778✔
3866
        {
3867
            for (CardStatus * dst: fd->tap->assaults.m_indirect)
242,394✔
3868
            {
3869
                unsigned bge_value = (dst->skill(Skill::valor) + dst->skill(Skill::bravery)+ 1) / 2;
150,302✔
3870
                if (bge_value <= 0)
150,302✔
3871
                { continue; }
132,600✔
3872
                SkillSpec ss_protect{Skill::protect, bge_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
17,815✔
3873
                if (dst->m_inhibited > 0)
17,815✔
3874
                {
3875
                    _DEBUG_MSG(1, "Heroism: %s on %s but it is inhibited\n",
113✔
3876
                            skill_short_description(fd->cards, ss_protect).c_str(), status_description(dst).c_str());
113✔
3877
                    -- dst->m_inhibited;
113✔
3878

3879
                    // Passive BGE: Divert
3880
                    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::divert], false))
113✔
3881
                    {
3882
                        SkillSpec diverted_ss = ss_protect;
×
3883
                        diverted_ss.y = allfactions;
×
3884
                        diverted_ss.n = 1;
×
3885
                        diverted_ss.all = false;
×
3886
                        // for (unsigned i = 0; i < num_inhibited; ++ i)
3887
                        {
×
3888
                            select_targets<Skill::protect>(fd, &fd->tip->commander, diverted_ss);
×
3889
                            std::vector<CardStatus*> selection_array = fd->selection_array;
×
3890
                            for (CardStatus * dst: selection_array)
×
3891
                            {
3892
                                if (dst->m_inhibited > 0)
×
3893
                                {
3894
                                    _DEBUG_MSG(1, "Heroism: %s (Diverted) on %s but it is inhibited\n",
×
3895
                                            skill_short_description(fd->cards, diverted_ss).c_str(), status_description(dst).c_str());
×
3896
                                    -- dst->m_inhibited;
×
3897
                                    continue;
×
3898
                                }
×
3899
                                _DEBUG_MSG(1, "Heroism: %s (Diverted) on %s\n",
×
3900
                                        skill_short_description(fd->cards, diverted_ss).c_str(), status_description(dst).c_str());
×
3901
                                perform_skill<Skill::protect>(fd, &fd->tap->commander, dst, diverted_ss);  // XXX: the caster
×
3902
                            }
3903
                        }
×
3904
                    }
3905
                    continue;
113✔
3906
                }
113✔
3907
#ifndef NQUEST
3908
                bool has_counted_quest = false;
3909
#endif
3910
                check_and_perform_skill<Skill::protect>(fd, &fd->tap->commander, dst, ss_protect, false
17,702✔
3911
#ifndef NQUEST
3912
                        , has_counted_quest
3913
#endif
3914
                        );
3915
            }
3916
        }
3917

3918
        // Evaluate activation BGE skills
3919
        fd->current_phase = Field::bge_phase;
19,582,778✔
3920
        for (const auto & bg_skill: fd->bg_skills[fd->tapi])
19,674,806✔
3921
        {
3922
            fd->prepare_action();
92,028✔
3923
            _DEBUG_MSG(2, "Evaluating BG skill %s\n", skill_description(fd->cards, bg_skill).c_str());
92,028✔
3924
            fd->skill_queue.emplace_back(&fd->tap->commander, bg_skill);
92,028✔
3925
            resolve_skill(fd);
92,028✔
3926
            fd->finalize_action();
92,028✔
3927
        }
3928
        if (__builtin_expect(fd->end, false)) { break; }
19,582,778✔
3929

3930
        // Evaluate commander
3931
        fd->current_phase = Field::commander_phase;
19,582,778✔
3932
        evaluate_skills<CardType::commander>(fd, &fd->tap->commander, fd->tap->commander.m_card->m_skills);
19,582,778✔
3933
        if (__builtin_expect(fd->end, false)) { break; }
19,582,778✔
3934

3935
        // Evaluate structures
3936
        fd->current_phase = Field::structures_phase;
19,582,778✔
3937
        for (fd->current_ci = 0; !fd->end && (fd->current_ci < fd->tap->structures.size()); ++fd->current_ci)
45,621,240✔
3938
        {
3939
            CardStatus* current_status(&fd->tap->structures[fd->current_ci]);
26,038,462✔
3940
            if (!is_active(current_status))
26,038,462✔
3941
            {
3942
                _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str());
8,619,914✔
3943
            }
3944
            else
3945
            {
3946
                evaluate_skills<CardType::structure>(fd, current_status, current_status->m_card->m_skills);
17,418,548✔
3947
            }
3948
        }
3949

3950
        // Evaluate assaults
3951
        fd->current_phase = Field::assaults_phase;
19,582,778✔
3952
        fd->bloodlust_value = 0;
19,582,778✔
3953
        for (fd->current_ci = 0; !fd->end && (fd->current_ci < fd->tap->assaults.size()); ++fd->current_ci)
70,643,505✔
3954
        {
3955
            CardStatus* current_status(&fd->tap->assaults[fd->current_ci]);
52,435,989✔
3956
            bool attacked = false;
52,435,989✔
3957
            if (!is_active(current_status))
52,435,989✔
3958
            {
3959
                _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str());
27,180,845✔
3960
                // Passive BGE: HaltedOrders
3961
                /*
3962
                unsigned inhibit_value;
3963
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::haltedorders], false)
3964
                        && (current_status->m_delay > 0) // still frozen
3965
                        && (fd->current_ci < fd->tip->assaults.size()) // across slot isn't empty
3966
                        && is_alive(&fd->tip->assaults[fd->current_ci]) // across assault is alive
3967
                        && ((inhibit_value = current_status->skill(Skill::inhibit))
3968
                            > fd->tip->assaults[fd->current_ci].m_inhibited)) // inhibit/re-inhibit(if higher)
3969
                        {
3970
                            CardStatus* across_status(&fd->tip->assaults[fd->current_ci]);
3971
                            _DEBUG_MSG(1, "Halted Orders: %s inhibits %s by %u\n",
3972
                                    status_description(current_status).c_str(),
3973
                                    status_description(across_status).c_str(), inhibit_value);
3974
                            across_status->m_inhibited = inhibit_value;
3975
                        }
3976
                        */
3977
            }
3978
            else
3979
            {
3980
                if (current_status->m_protected_stasis)
25,255,144✔
3981
                {
3982
                    _DEBUG_MSG(1, "%s loses Stasis protection (activated)\n",
2,324,763✔
3983
                            status_description(current_status).c_str());
3984
                }
3985
                current_status->m_protected_stasis = 0;
25,255,144✔
3986
                fd->assault_bloodlusted = false;
25,255,144✔
3987
                current_status->m_step = CardStep::attacking;
25,255,144✔
3988
                evaluate_skills<CardType::assault>(fd, current_status, current_status->m_card->m_skills, &attacked);
25,255,144✔
3989
                if (__builtin_expect(fd->end, false)) { break; }
25,255,144✔
3990
                if (__builtin_expect(!is_alive(current_status), false)) { continue; }
23,879,882✔
3991
            }
3992

3993
            current_status->m_step = CardStep::attacked;
50,291,310✔
3994
        }
3995
        fd->current_phase = Field::end_phase;
19,582,778✔
3996
        turn_end_phase(fd);
19,582,778✔
3997
        if (__builtin_expect(fd->end, false)) { break; }
19,582,778✔
3998
        _DEBUG_MSG(1, "TURN %u ends for %s\n", fd->turn, status_description(&fd->tap->commander).c_str());
18,207,516✔
3999
        std::swap(fd->tapi, fd->tipi);
18,207,516✔
4000
        std::swap(fd->tap, fd->tip);
18,207,516✔
4001
        ++fd->turn;
18,207,516✔
4002
    }
4003

4004
    return evaluate_sim_result(fd,turns_both!= 0);
1,396,373✔
4005
}
4006

4007
//------------------------------------------------------------------------------
4008
void fill_skill_table()
78✔
4009
{
4010
    memset(skill_table, 0, sizeof skill_table);
78✔
4011
    skill_table[Skill::mortar] = perform_targetted_hostile_fast<Skill::mortar>;
78✔
4012
    skill_table[Skill::enfeeble] = perform_targetted_hostile_fast<Skill::enfeeble>;
78✔
4013
    skill_table[Skill::enhance] = perform_targetted_allied_fast<Skill::enhance>;
78✔
4014
    skill_table[Skill::evolve] = perform_targetted_allied_fast<Skill::evolve>;
78✔
4015
    skill_table[Skill::heal] = perform_targetted_allied_fast<Skill::heal>;
78✔
4016
    skill_table[Skill::jam] = perform_targetted_hostile_fast<Skill::jam>;
78✔
4017
    skill_table[Skill::mend] = perform_targetted_allied_fast<Skill::mend>;
78✔
4018
    skill_table[Skill::fortify] = perform_targetted_allied_fast<Skill::fortify>;
78✔
4019
    skill_table[Skill::overload] = perform_targetted_allied_fast<Skill::overload>;
78✔
4020
    skill_table[Skill::protect] = perform_targetted_allied_fast<Skill::protect>;
78✔
4021
    skill_table[Skill::rally] = perform_targetted_allied_fast<Skill::rally>;
78✔
4022
    skill_table[Skill::enrage] = perform_targetted_allied_fast<Skill::enrage>;
78✔
4023
    skill_table[Skill::entrap] = perform_targetted_allied_fast<Skill::entrap>;
78✔
4024
    skill_table[Skill::rush] = perform_targetted_allied_fast_rush;
78✔
4025
    skill_table[Skill::siege] = perform_targetted_hostile_fast<Skill::siege>;
78✔
4026
    skill_table[Skill::strike] = perform_targetted_hostile_fast<Skill::strike>;
78✔
4027
    skill_table[Skill::sunder] = perform_targetted_hostile_fast<Skill::sunder>;
78✔
4028
    skill_table[Skill::weaken] = perform_targetted_hostile_fast<Skill::weaken>;
78✔
4029
    skill_table[Skill::mimic] = perform_targetted_hostile_fast<Skill::mimic>;
78✔
4030
}
78✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc