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

APN-Pucky / tyrant_optimize / 20460432036

26 Sep 2025 11:25AM UTC coverage: 70.326% (-0.07%) from 70.399%
20460432036

push

github

web-flow
Update bges.txt (#95)

Updated the BGEs with the new values uploaded to the game on 2025-09-25

4773 of 6787 relevant lines covered (70.33%)

9371006.06 hits per line

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

84.02
/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); }
16,366,290✔
39
inline bool is_alive(CardStatus* c) { return (c->m_hp > 0); }
15,905,309✔
40
inline bool can_act(CardStatus* c) { return is_alive(c) && !c->m_jammed; }
418,000,882✔
41
inline bool is_active(CardStatus* c) { return can_act(c) && (c->m_delay == 0); }
348,173,097✔
42
inline bool is_active_next_turn(CardStatus* c) { return can_act(c) && (c->m_delay <= 1); }
49,529,956✔
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)));}
256,476✔
44
// Can be healed / repaired
45
inline bool can_be_healed(CardStatus* c) { return is_alive(c) && (c->m_hp < c->max_hp()); }
35,532✔
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)
55,144,614✔
60
{
61
    return status->description();
21,949,325✔
62
}
63
//------------------------------------------------------------------------------
64
template <typename CardsIter, typename Functor>
65
inline unsigned Field::make_selection_array(CardsIter first, CardsIter last, Functor f)
120,927,290✔
66
{
67
    this->selection_array.clear();
120,927,290✔
68
    for(auto c = first; c != last; ++c)
490,915,882✔
69
    {
70
        if (f(*c))
737,280,045✔
71
        {
72
            this->selection_array.push_back(*c);
191,907,166✔
73
        }
74
    }
75
    return(this->selection_array.size());
120,927,290✔
76
}
77
inline CardStatus * Field::left_assault(const CardStatus * status)
1,066,589✔
78
{
79
    return left_assault(status, 1);
4,018,581✔
80
}
81
inline CardStatus * Field::left_assault(const CardStatus * status, const unsigned n)
1,066,589✔
82
{
83
    auto & assaults = this->players[status->m_player]->assaults;
1,066,589✔
84
    if (status->m_index >= n)
1,066,589✔
85
    {
86
        auto left_status = &assaults[status->m_index - n];
820,119✔
87
        if (is_alive(left_status))
820,119✔
88
        {
89
            return left_status;
90
        }
91
    }
92
    return nullptr;
93
}
94
inline CardStatus * Field::right_assault(const CardStatus * status)
1,066,589✔
95
{
96
    return right_assault(status, 1);
3,991,048✔
97
}
98
inline CardStatus * Field::right_assault(const CardStatus * status, const unsigned n)
1,066,589✔
99
{
100
    auto & assaults = this->players[status->m_player]->assaults;
1,066,589✔
101
    if ((status->m_index + n) < assaults.size())
1,066,589✔
102
    {
103
        auto right_status = &assaults[status->m_index + n];
821,681✔
104
        if (is_alive(right_status))
821,681✔
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()
100,881,922✔
123
{
124
    damaged_units_to_times.clear();
201,763,844✔
125
}
126

127
//------------------------------------------------------------------------------
128
inline void Field::finalize_action()
99,566,262✔
129
{
130
    for (auto unit_it = damaged_units_to_times.begin(); unit_it != damaged_units_to_times.end(); ++ unit_it)
99,839,489✔
131
    {
132
        if (__builtin_expect(!unit_it->second, false))
273,227✔
133
        { continue; }
×
134
        CardStatus * dmg_status = unit_it->first;
273,227✔
135
        if (__builtin_expect(!is_alive(dmg_status), false))
273,227✔
136
        { continue; }
116,527✔
137
        unsigned barrier_base = dmg_status->skill(Skill::barrier);
156,700✔
138
        if (barrier_base)
156,700✔
139
        {
140
            unsigned protect_value = barrier_base * unit_it->second;
156,700✔
141
            _DEBUG_MSG(1, "%s protects itself for %u (barrier %u x %u damage taken)\n",
156,700✔
142
                    status_description(dmg_status).c_str(), protect_value, barrier_base, unit_it->second);
156,700✔
143
            dmg_status->m_protected += protect_value;
156,700✔
144
        }
145
    }
146
}
99,566,262✔
147

148
//------------------------------------------------------------------------------
149
inline unsigned CardStatus::skill_base_value(Skill::Skill skill_id) const
1,238,806,889✔
150
{
151
    return m_card->m_skill_value[skill_id + m_primary_skill_offset[skill_id]]
1,238,806,889✔
152
        + (skill_id == Skill::berserk ? m_enraged : 0)
×
153
        + (skill_id == Skill::counter ? m_entrapped : 0)
35,865,933✔
154
        ;
155
}
156
//------------------------------------------------------------------------------
157
inline unsigned CardStatus::skill(Skill::Skill skill_id) const
784,352,115✔
158
{
159
    return (is_activation_skill_with_x(skill_id)
784,352,115✔
160
            ? safe_minus(skill_base_value(skill_id), m_sabotaged)
161
            : skill_base_value(skill_id))
786,453,518✔
162
        + enhanced(skill_id);
786,453,518✔
163
}
164
//------------------------------------------------------------------------------
165
inline bool CardStatus::has_skill(Skill::Skill skill_id) const
452,552,801✔
166
{
167
    return skill_base_value(skill_id);
53,034✔
168
}
169
//------------------------------------------------------------------------------
170
inline unsigned CardStatus::enhanced(Skill::Skill skill_id) const
891,627,275✔
171
{
172
    return m_enhanced_value[skill_id + m_primary_skill_offset[skill_id]];
156,700✔
173
}
174
//------------------------------------------------------------------------------
175
inline unsigned CardStatus::protected_value() const
53,371,350✔
176
{
177
    return m_protected + m_protected_stasis;
53,371,350✔
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
98,507,509✔
187
{
188
    return (m_card->m_health + safe_minus(m_perm_health_buff, m_subdued));
94,805,402✔
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)
21,871,479✔
197
{
198
    value = remove_disease(this,value);
21,871,479✔
199
    return (m_hp = std::min(m_hp + value, max_hp()));
38,935,433✔
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)
13,245,264✔
209
{
210
    value = remove_disease(this,value);
13,245,264✔
211
    m_perm_health_buff += value;
13,245,264✔
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);
13,245,264✔
215
}
216
//------------------------------------------------------------------------------
217
inline void CardStatus::set(const Card* card)
24,234,962✔
218
{
219
    this->set(*card);
24,234,962✔
220
}
221
//------------------------------------------------------------------------------
222
inline void CardStatus::set(const Card& card)
24,234,962✔
223
{
224
    m_card = &card;
24,234,962✔
225
    m_index = 0;
24,234,962✔
226
    m_action_index=0;
24,234,962✔
227
    m_player = 0;
24,234,962✔
228
    m_delay = card.m_delay;
24,234,962✔
229
    m_hp = card.m_health;
24,234,962✔
230
    m_absorption = 0;
24,234,962✔
231
    m_step = CardStep::none;
24,234,962✔
232
    m_perm_health_buff = 0;
24,234,962✔
233
    m_perm_attack_buff = 0;
24,234,962✔
234
    m_temp_attack_buff = 0;
24,234,962✔
235
    m_corroded_rate = 0;
24,234,962✔
236
    m_corroded_weakened = 0;
24,234,962✔
237
    m_subdued = 0;
24,234,962✔
238
    m_enfeebled = 0;
24,234,962✔
239
    m_evaded = 0;
24,234,962✔
240
    m_inhibited = 0;
24,234,962✔
241
    m_sabotaged = 0;
24,234,962✔
242
    m_jammed = false;
24,234,962✔
243
    m_overloaded = false;
24,234,962✔
244
    m_paybacked = 0;
24,234,962✔
245
    m_tributed = 0;
24,234,962✔
246
    m_poisoned = 0;
24,234,962✔
247
    m_protected = 0;
24,234,962✔
248
    m_protected_stasis = 0;
24,234,962✔
249
    m_enraged = 0;
24,234,962✔
250
    m_entrapped = 0;
24,234,962✔
251
    m_marked = 0;
24,234,962✔
252
    m_diseased = 0;
24,234,962✔
253
    m_rush_attempted = false;
24,234,962✔
254
    m_sundered = false;
24,234,962✔
255
    //APN
256
    m_summoned = false;
24,234,962✔
257

258
    std::memset(m_primary_skill_offset, 0, sizeof m_primary_skill_offset);
24,234,962✔
259
    std::memset(m_evolved_skill_offset, 0, sizeof m_evolved_skill_offset);
24,234,962✔
260
    std::memset(m_enhanced_value, 0, sizeof m_enhanced_value);
24,234,962✔
261
    std::memset(m_skill_cd, 0, sizeof m_skill_cd);
24,234,962✔
262
}
24,234,962✔
263
//------------------------------------------------------------------------------
264
/**
265
 * @brief Calculate the attack power of the CardStatus.
266
 * 
267
 * @return unsigned 
268
 */
269
inline unsigned CardStatus::attack_power() const
188,800,376✔
270
{
271
    signed attack = calc_attack_power();
188,800,376✔
272
    if(__builtin_expect(attack <0,false))
188,800,376✔
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);
188,800,376✔
277
    return (unsigned)attack;
188,800,376✔
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
189,648,886✔
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."
189,648,886✔
293
    {
294
        return
189,648,886✔
295
        (signed)safe_minus(
379,297,772✔
296
                m_card->m_attack + safe_minus(m_perm_attack_buff, m_subdued)+ m_temp_attack_buff,
189,648,886✔
297
                m_corroded_weakened
189,648,886✔
298
                );
189,648,886✔
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)
108,546✔
318
{
319
    const auto cardIter = cards.cards_by_id.find(card_id);
108,546✔
320
    if (cardIter == cards.cards_by_id.end()) { return "UnknownCard.id[" + tuo::to_string(card_id) + "]"; }
108,546✔
321
    return cardIter->second->m_name;
217,092✔
322
}
323
//------------------------------------------------------------------------------
324
std::string card_description(const Cards& cards, const Card* c)
2,514,722✔
325
{
326
    std::string desc;
2,514,722✔
327
    desc = c->m_name;
2,514,722✔
328
    switch(c->m_type)
2,514,722✔
329
    {
330
        case CardType::assault:
2,077,351✔
331
            desc += ": " + tuo::to_string(c->m_attack) + "/" + tuo::to_string(c->m_health) + "/" + tuo::to_string(c->m_delay);
10,386,755✔
332
            break;
2,077,351✔
333
        case CardType::structure:
436,807✔
334
            desc += ": " + tuo::to_string(c->m_health) + "/" + tuo::to_string(c->m_delay);
1,747,228✔
335
            break;
436,807✔
336
        case CardType::commander:
564✔
337
            desc += ": hp:" + tuo::to_string(c->m_health);
1,692✔
338
            break;
564✔
339
        case CardType::num_cardtypes:
×
340
            assert(false);
×
341
            break;
342
    }
343
    if (c->m_rarity >= 4) { desc += " " + rarity_names[c->m_rarity]; }
4,220,099✔
344
    if (c->m_faction != allfactions) { desc += " " + faction_names[c->m_faction]; }
5,029,444✔
345
    for (auto& skill: c->m_skills_on_play) { desc += ", " + skill_description(cards, skill, Skill::Trigger::play); }
2,647,606✔
346
    for (auto& skill: c->m_skills) { desc += ", " + skill_description(cards, skill, Skill::Trigger::activate); }
16,632,674✔
347
    //APN
348
    for (auto& skill: c->m_skills_on_attacked) { desc += ", " + skill_description(cards, skill, Skill::Trigger::attacked); }
3,079,910✔
349
    for (auto& skill: c->m_skills_on_death) { desc += ", " + skill_description(cards, skill, Skill::Trigger::death); }
2,689,766✔
350
    return desc;
2,514,722✔
351
}
×
352
//------------------------------------------------------------------------------
353
std::string CardStatus::description() const
55,144,614✔
354
{
355
    std::string desc = "P" + tuo::to_string(m_player) + " ";
165,433,842✔
356
    switch(m_card->m_type)
55,144,614✔
357
    {
358
        case CardType::commander: desc += "Commander "; break;
17,015,103✔
359
        case CardType::assault: desc += "Assault " + tuo::to_string(m_index) + " "; break;
126,999,428✔
360
        case CardType::structure: desc += "Structure " + tuo::to_string(m_index) + " "; break;
25,518,616✔
361
        case CardType::num_cardtypes: assert(false); break;
×
362
    }
363
    desc += "[" + m_card->m_name;
110,289,228✔
364
    switch (m_card->m_type)
55,144,614✔
365
    {
366
        case CardType::assault:
31,749,857✔
367
            desc += " att:[[" + tuo::to_string(m_card->m_attack) + "(base)";
126,999,428✔
368
            if (m_perm_attack_buff)
31,749,857✔
369
            {
370
                desc += "+[" + tuo::to_string(m_perm_attack_buff) + "(perm)";
28,941,588✔
371
                if (m_subdued) { desc += "-" + tuo::to_string(m_subdued) + "(subd)"; }
8,490,732✔
372
                desc += "]";
7,235,397✔
373
            }
374
            if (m_corroded_weakened) { desc += "-" + tuo::to_string(m_corroded_weakened) + "(corr)"; }
33,184,550✔
375
            desc += "]";
31,749,857✔
376
            if (m_temp_attack_buff) { desc += (m_temp_attack_buff > 0 ? "+" : "") + tuo::to_string(m_temp_attack_buff) + "(temp)"; }
48,806,138✔
377
            desc += "]=" + tuo::to_string(attack_power());
95,249,571✔
378
        case CardType::structure:
55,144,614✔
379
        case CardType::commander:
55,144,614✔
380
            desc += " hp:" + tuo::to_string(m_hp);
165,433,842✔
381
            if(m_absorption)desc += " absorb:" + tuo::to_string(m_absorption);
60,853,334✔
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,804,712✔
388
    // Status w/o value
389
    if (m_jammed) { desc += ", jammed"; }
55,144,614✔
390
    if (m_overloaded) { desc += ", overloaded"; }
55,144,614✔
391
    if (m_sundered) { desc += ", sundered"; }
55,144,614✔
392
    if (m_summoned) { desc+= ", summoned"; }
55,144,614✔
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) + ")"; }
57,192,202✔
395
    if (m_subdued) { desc += ", subdued " + tuo::to_string(m_subdued); }
57,287,088✔
396
    if (m_enfeebled) { desc += ", enfeebled " + tuo::to_string(m_enfeebled); }
60,407,270✔
397
    if (m_inhibited) { desc += ", inhibited " + tuo::to_string(m_inhibited); }
58,156,590✔
398
    if (m_sabotaged) { desc += ", sabotaged " + tuo::to_string(m_sabotaged); }
58,329,378✔
399
    if (m_poisoned) { desc += ", poisoned " + tuo::to_string(m_poisoned); }
56,926,092✔
400
    if (m_protected) { desc += ", protected " + tuo::to_string(m_protected); }
76,114,636✔
401
    if (m_protected_stasis) { desc += ", stasis " + tuo::to_string(m_protected_stasis); }
59,970,878✔
402
    if (m_enraged) { desc += ", enraged " + tuo::to_string(m_enraged); }
61,284,326✔
403
    if (m_entrapped) { desc += ", entrapped " + tuo::to_string(m_entrapped); }
74,641,072✔
404
    if (m_marked) { desc += ", marked " + tuo::to_string(m_marked); }
55,449,496✔
405
    if (m_diseased) { desc += ", diseased " + tuo::to_string(m_diseased); }
56,245,780✔
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};
55,144,614✔
409
    for (const Skill::Trigger& trig: s_triggers)
275,723,070✔
410
    {
411
        std::vector<SkillSpec> card_skills(
220,578,456✔
412
                (trig == Skill::Trigger::play) ? m_card->m_skills_on_play :
220,578,456✔
413
                (trig == Skill::Trigger::activate) ? m_card->m_skills :
165,433,842✔
414
                //APN
415
                (trig == Skill::Trigger::attacked) ? m_card->m_skills_on_attacked :
110,289,228✔
416
                (trig == Skill::Trigger::death) ? m_card->m_skills_on_death :
55,144,614✔
417
                std::vector<SkillSpec>());
220,578,456✔
418

419
        // emulate Berserk/Counter by status Enraged/Entrapped unless such skills exist (only for normal skill triggering)
420
        if (trig == Skill::Trigger::activate)
220,578,456✔
421
        {
422
            if (m_enraged && !std::count_if(card_skills.begin(), card_skills.end(), [](const SkillSpec ss) { return (ss.id == Skill::berserk); }))
58,214,470✔
423
            {
424
                SkillSpec ss{Skill::berserk, m_enraged, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
3,028,421✔
425
                card_skills.emplace_back(ss);
3,028,421✔
426
            }
427
            if (m_entrapped && !std::count_if(card_skills.begin(), card_skills.end(), [](const SkillSpec ss) { return (ss.id == Skill::counter); }))
64,892,843✔
428
            {
429
                SkillSpec ss{Skill::counter, m_entrapped, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
9,544,769✔
430
                card_skills.emplace_back(ss);
9,544,769✔
431
            }
432
        }
433
        for (const auto& ss : card_skills)
397,852,054✔
434
        {
435
            std::string skill_desc;
177,273,598✔
436
            if (m_evolved_skill_offset[ss.id]) { skill_desc += "->" + skill_names[ss.id + m_evolved_skill_offset[ss.id]]; }
177,663,652✔
437
            if (m_enhanced_value[ss.id]) { skill_desc += " +" + tuo::to_string(m_enhanced_value[ss.id]); }
180,157,554✔
438
            if (!skill_desc.empty())
177,273,598✔
439
            {
440
                desc += ", " + (
3,664,064✔
441
                        (trig == Skill::Trigger::play) ? "(On Play)" :
3,664,064✔
442
                        (trig == Skill::Trigger::attacked) ? "(On Attacked)" :
1,832,032✔
443
                        (trig == Skill::Trigger::death) ? "(On Death)" :
1,832,032✔
444
                        std::string("")) + skill_names[ss.id] + skill_desc;
9,160,160✔
445
            }
446
        }
177,273,598✔
447
    }
220,578,456✔
448
    return desc + "]";
55,144,614✔
449
}
55,144,614✔
450
//------------------------------------------------------------------------------
451
void Hand::reset(std::mt19937& re)
2,672,624✔
452
{
453
    assaults.reset();
2,672,624✔
454
    structures.reset();
2,672,624✔
455
    deck->shuffle(re);
2,672,624✔
456
    commander.set(deck->shuffled_commander);
2,672,624✔
457
    total_cards_destroyed = 0;
2,672,624✔
458
    total_nonsummon_cards_destroyed = 0;
2,672,624✔
459
    if (commander.skill(Skill::stasis))
2,672,624✔
460
    {
461
        stasis_faction_bitmap |= (1u << commander.m_card->m_faction);
×
462
    }
463
}
2,672,624✔
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)
51,765,817✔
473
{
474
    return((player + 1) % 2);
51,765,817✔
475
}
476

477
//------------------------------------------------------------------------------
478
SkillSpec apply_evolve(const SkillSpec& s, signed offset)
268,693✔
479
{
480
    SkillSpec evolved_s = s;
268,693✔
481
    evolved_s.id = static_cast<Skill::Skill>(evolved_s.id + offset);
268,693✔
482
    return(evolved_s);
268,693✔
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)
162,407✔
495
{
496
    SkillSpec sabotaged_s = s;
162,407✔
497
    sabotaged_s.x -= std::min(sabotaged_s.x, sabotaged_value);
×
498
    return(sabotaged_s);
162,407✔
499
}
500

501
//------------------------------------------------------------------------------
502
inline void resolve_scavenge(Storage<CardStatus>& store)
38,310,864✔
503
{
504
    for(auto status : store.m_indirect)
146,077,796✔
505
    {
506
        if(!is_alive(status))continue;
107,766,932✔
507
        unsigned scavenge_value = status->skill(Skill::scavenge);
94,196,590✔
508
        if(!scavenge_value)continue;
94,196,590✔
509

510
        _DEBUG_MSG(1, "%s activates Scavenge %u\n",
6,400,080✔
511
                status_description(status).c_str(), scavenge_value);
6,400,080✔
512
        status->ext_hp(scavenge_value);
6,400,080✔
513
    }
514
}
38,310,864✔
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)
123,373,410✔
523
{
524
    if (fd->killed_units.empty())
123,373,410✔
525
        return;
526
    bool skip_all_except_summon = fd->fixes[Fix::death_from_bge] && (fd->current_phase == Field::bge_phase);
9,447,371✔
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,447,371✔
532
    unsigned stacked_poison_value = 0;
9,447,371✔
533
    unsigned last_index = 99999;
9,447,371✔
534
    CardStatus* left_virulence_victim = nullptr;
9,447,371✔
535
    for (auto status: fd->killed_units)
19,025,087✔
536
    {
537
        // Skill: Scavenge
538
        // Any unit dies => perm-hp-buff
539
        if (__builtin_expect(!skip_all_except_summon, true))
9,577,716✔
540
        {
541
            resolve_scavenge(fd->players[0]->assaults);
9,577,716✔
542
            resolve_scavenge(fd->players[1]->assaults);
9,577,716✔
543
            resolve_scavenge(fd->players[0]->structures);
9,577,716✔
544
            resolve_scavenge(fd->players[1]->structures);
9,577,716✔
545
        }
546

547
        if ((status->m_card->m_type == CardType::assault) && (!skip_all_except_summon))
9,577,716✔
548
        {
549
            // Skill: Avenge
550
            const unsigned host_idx = status->m_index;
8,600,790✔
551
            unsigned from_idx, till_idx;
8,600,790✔
552
            //scan all assaults for Avenge
553
            from_idx = 0;
8,600,790✔
554
            till_idx = assaults.size() - 1;
8,600,790✔
555
            for (; from_idx <= till_idx; ++ from_idx)
33,886,928✔
556
            {
557
                if (from_idx == host_idx) { continue; }
25,286,138✔
558
                CardStatus* adj_status = &assaults[from_idx];
16,685,348✔
559
                if (!is_alive(adj_status)) { continue; }
16,685,348✔
560
                unsigned avenge_value = adj_status->skill(Skill::avenge);
14,596,257✔
561
                if (!avenge_value) { continue; }
14,596,257✔
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,262,884✔
566
                {
567
                    avenge_value = (avenge_value + 1) / 2;
1,792,164✔
568
                }
569
                _DEBUG_MSG(1, "%s activates %sAvenge %u\n",
3,420,822✔
570
                        status_description(adj_status).c_str(),
571
                        (std::abs((signed)from_idx - (signed)host_idx) > 1 ? "Half-" : ""),
572
                         avenge_value);
3,262,884✔
573
                if (!adj_status->m_sundered)
3,262,884✔
574
                { adj_status->m_perm_attack_buff += avenge_value; }
3,056,570✔
575
                adj_status->ext_hp(avenge_value);
3,262,884✔
576
            }
577

578
            // Passive BGE: Virulence
579
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::virulence], false))
8,600,790✔
580
            {
581
                if (status->m_index != last_index + 1)
34,358✔
582
                {
583
                    stacked_poison_value = 0;
33,926✔
584
                    left_virulence_victim = nullptr;
33,926✔
585
                    if (status->m_index > 0)
33,926✔
586
                    {
587
                        auto left_status = &assaults[status->m_index - 1];
6,964✔
588
                        if (is_alive(left_status))
6,964✔
589
                        {
590
                            left_virulence_victim = left_status;
34,358✔
591
                        }
592
                    }
593
                }
594
                if (status->m_poisoned > 0)
34,358✔
595
                {
596
                    if (left_virulence_victim != nullptr)
1,548✔
597
                    {
598
                        _DEBUG_MSG(1, "Virulence: %s spreads left poison +%u to %s\n",
142✔
599
                                status_description(status).c_str(), status->m_poisoned,
600
                                status_description(left_virulence_victim).c_str());
142✔
601
                        left_virulence_victim->m_poisoned += status->m_poisoned;
142✔
602
                    }
603
                    stacked_poison_value += status->m_poisoned;
1,548✔
604
                    _DEBUG_MSG(1, "Virulence: %s spreads right poison +%u = %u\n",
1,548✔
605
                            status_description(status).c_str(), status->m_poisoned, stacked_poison_value);
606
                }
607
                if (status->m_index + 1 < assaults.size())
34,358✔
608
                {
609
                    auto right_status = &assaults[status->m_index + 1];
10,917✔
610
                    if (is_alive(right_status))
10,917✔
611
                    {
612
                        _DEBUG_MSG(1, "Virulence: spreads stacked poison +%u to %s\n",
10,050✔
613
                                stacked_poison_value, status_description(right_status).c_str());
10,050✔
614
                        right_status->m_poisoned += stacked_poison_value;
10,050✔
615
                    }
616
                }
617
                last_index = status->m_index;
34,358✔
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,577,716✔
624
        {
625
            if (fd->bg_effects[fd->tapi][PassiveBGE::revenge] < 0)
73,121✔
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,};
73,121✔
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,};
73,121✔
629
            CardStatus* commander = &fd->players[status->m_player]->commander;
73,121✔
630
            _DEBUG_MSG(2, "Revenge: Preparing (head) skills  %s and %s\n",
73,121✔
631
                    skill_description(fd->cards, ss_heal).c_str(),
632
                    skill_description(fd->cards, ss_rally).c_str());
73,121✔
633
            fd->skill_queue.emplace(fd->skill_queue.begin()+0, commander, ss_heal);
73,121✔
634
            fd->skill_queue.emplace(fd->skill_queue.begin()+1, commander, ss_rally); // +1: keep ss_heal at first place
73,121✔
635
        }
636

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

656
//------------------------------------------------------------------------------
657
void(*skill_table[Skill::num_skills])(Field*, CardStatus* src, const SkillSpec&);
658
void resolve_skill(Field* fd)
177,318,398✔
659
{
660
    while (!fd->skill_queue.empty())
286,964,538✔
661
    {
662
        auto skill_instance(fd->skill_queue.front());
109,646,140✔
663
        auto& status(std::get<0>(skill_instance));
109,646,140✔
664
        const auto& ss(std::get<1>(skill_instance));
109,646,140✔
665
        fd->skill_queue.pop_front();
109,646,140✔
666
        if (__builtin_expect(status->m_card->m_skill_trigger[ss.id] == Skill::Trigger::activate, true))
109,646,140✔
667
        {
668
            if (!is_alive(status))
105,448,579✔
669
            {
670
                _DEBUG_MSG(2, "%s failed to %s because it is dead.\n",
3,904✔
671
                        status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
3,904✔
672
                continue;
3,012,441✔
673
            }
3,904✔
674
            if (status->m_jammed)
105,444,675✔
675
            {
676
                _DEBUG_MSG(2, "%s failed to %s because it is Jammed.\n",
1,505✔
677
                        status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
1,505✔
678
                continue;
1,505✔
679
            }
1,505✔
680
        }
681

682
        // is summon? (non-activation skill)
683
        if (ss.id == Skill::summon)
109,640,731✔
684
        {
685
            check_and_perform_summon(fd, status);
2,922,366✔
686
            continue;
2,922,366✔
687
        }
688
        _DEBUG_ASSERT(is_activation_skill(ss.id) || ss.id == Skill::enhance); // enhance is no trigger, but  queues the skill
106,810,751✔
689

690
        SkillSpec modified_s = ss;
106,718,365✔
691

692
        // apply evolve
693
        signed evolved_offset = status->m_evolved_skill_offset[modified_s.id];
106,718,365✔
694
        if (evolved_offset != 0)
106,718,365✔
695
        { modified_s = apply_evolve(modified_s, evolved_offset); }
268,693✔
696

697
        // apply sabotage (only for X-based activation skills)
698
        unsigned sabotaged_value = status->m_sabotaged;
106,718,365✔
699
        if ((sabotaged_value > 0) && is_activation_skill_with_x(modified_s.id))
106,718,365✔
700
        { modified_s = apply_sabotage(modified_s, sabotaged_value); }
240,148✔
701

702
        // apply enhance
703
        unsigned enhanced_value = status->enhanced(modified_s.id);
106,718,365✔
704
        if (enhanced_value > 0)
106,718,365✔
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)
106,718,365✔
709
        {
710
            _DEBUG_MSG(2, "%s failed to %s because its X value is zeroed (sabotaged).\n",
84,666✔
711
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
84,666✔
712
            continue;
84,666✔
713
        }
84,666✔
714
        else { skill_table[modified_s.id](fd, status, modified_s); }
106,633,699✔
715
    }
716
}
177,318,398✔
717

718
void apply_corrosion(CardStatus * status)
20,950,879✔
719
{
720
      if (status->m_corroded_rate)
20,950,879✔
721
      {
722
        unsigned v = std::min(status->m_corroded_rate, status->attack_power());
104,931✔
723
        unsigned corrosion = std::min(v, status->m_card->m_attack
209,862✔
724
                + status->m_perm_attack_buff - status->m_corroded_weakened);
104,931✔
725
        _DEBUG_MSG(1, "%s loses Attack by %u (+corrosion %u).\n", status_description(status).c_str(), v, corrosion);
104,931✔
726
        status->m_corroded_weakened += corrosion;
104,931✔
727
      }
728
}
20,950,879✔
729
void remove_corrosion(CardStatus * status)
2,834,126✔
730
{
731
       if (status->m_corroded_rate)
2,834,126✔
732
       {
733
           _DEBUG_MSG(1, "%s loses Status corroded.\n", status_description(status).c_str());
4,074✔
734
           status->m_corroded_rate = 0;
4,074✔
735
           status->m_corroded_weakened = 0;
4,074✔
736
       }
737
}
2,834,126✔
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)
59,599,657✔
750
{
751
    _DEBUG_ASSERT(status);
59,599,657✔
752
    unsigned num_actions(1);
753
    for (unsigned action_index(0); action_index < num_actions; ++ action_index)
123,564,424✔
754
    {
755
        status->m_action_index = action_index;
65,280,427✔
756
        fd->prepare_action();
65,280,427✔
757
        _DEBUG_ASSERT(fd->skill_queue.size() == 0);
65,280,427✔
758
        for (auto & ss: skills)
251,969,949✔
759
        {
760
            if (!is_activation_skill(ss.id)) { continue; }
267,307,186✔
761
            if (status->m_skill_cd[ss.id] > 0) { continue; }
106,071,858✔
762
            _DEBUG_MSG(2, "Evaluating %s skill %s\n",
105,209,951✔
763
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
764
            fd->skill_queue.emplace_back(status, ss);
105,209,951✔
765
            resolve_skill(fd);
105,209,951✔
766
        }
767
        if (type == CardType::assault)
768
        {
769
            // Attack
770
            if (can_act(status))
25,100,665✔
771
            {
772
                if (attack_phase(fd))
24,926,051✔
773
                {
774
                    *attacked = true;
22,266,539✔
775
                    if (__builtin_expect(fd->end, false)) { break; }
22,266,539✔
776
                    //Apply corrosion
777
                    apply_corrosion(status);
20,950,879✔
778
                }
779
                else
780
                {
781
                  // Remove Corrosion
782
                  remove_corrosion(status);
2,659,512✔
783
                }
784
            }
785
            else
786
            {
787
                _DEBUG_MSG(2, "%s cannot take attack.\n", status_description(status).c_str());
174,614✔
788
                // Remove Corrosion
789
                remove_corrosion(status);
174,614✔
790
            }
791
        }
792
        fd->finalize_action();
63,964,767✔
793
        // Flurry
794
        if (can_act(status) && status->has_skill(Skill::flurry) && (status->m_skill_cd[Skill::flurry] == 0))
129,498,540✔
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,438,981✔
803
                    status_description(status).c_str(), status->skill_base_value(Skill::flurry));
804
            num_actions += status->skill_base_value(Skill::flurry);
2,308,068✔
805
            for (const auto & ss : skills)
9,229,128✔
806
            {
807
                Skill::Skill evolved_skill_id = static_cast<Skill::Skill>(ss.id + status->m_evolved_skill_offset[ss.id]);
6,921,060✔
808
                if (evolved_skill_id == Skill::flurry)
6,921,060✔
809
                {
810
                    status->m_skill_cd[ss.id] = ss.c;
2,308,068✔
811
                }
812
            }
813
        }
814
    }
815
}
59,599,657✔
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_) :
21,562,338✔
827
        card{card_},
21,562,338✔
828
        fd{fd_},
21,562,338✔
829
        status{nullptr},
21,562,338✔
830
        storage{nullptr},
21,562,338✔
831
        actor_index{ai_},
21,562,338✔
832
        actor_status{as_}
21,562,338✔
833
    {}
834

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

841
    template <enum CardType::CardType type>
842
        CardStatus* op(bool summoned)
21,562,338✔
843
        {
844
            setStorage<type>();
21,562,338✔
845
            placeCard<type>(summoned);
21,562,338✔
846

847
            unsigned played_faction_mask(0);
21,562,338✔
848
            unsigned same_faction_cards_count(0);
21,562,338✔
849
            bool bge_megamorphosis = fd->bg_effects[status->m_player][PassiveBGE::megamorphosis];
21,562,338✔
850
            //played_status = status;
851
            //played_card = card;
852
            if(__builtin_expect(fd->fixes[Fix::barrier_each_turn],true) && status->has_skill(Skill::barrier)){
21,562,338✔
853
                _DEBUG_MSG(1,"%s gets barrier protection %u per turn\n",status_description(status).c_str(),status->skill(Skill::barrier));
572,607✔
854
                status->m_protected += status->skill(Skill::barrier);
459,321✔
855
            }
856
            if (status->m_delay == 0)
21,562,338✔
857
            {
858
                    check_and_perform_bravery(fd,status);
1,469,967✔
859
                check_and_perform_valor(fd, status);
1,469,967✔
860
            }
861
            
862

863
            //refresh/init absorb
864
            if(status->has_skill(Skill::absorb))
21,562,338✔
865
            {
866
                status->m_absorption = status->skill_base_value(Skill::absorb);
1,164,949✔
867
            }
868

869

870
            // 1. Evaluate skill Allegiance & count assaults with same faction (structures will be counted later)
871
            // 2. Passive BGE Cold Sleep
872
            for (CardStatus* status_i : fd->players[status->m_player]->assaults.m_indirect)
83,749,835✔
873
            {
874
                if (status_i == status || !is_alive(status_i)) { continue; } // except itself
62,187,497✔
875
                //std::cout << status_description(status_i).c_str();
876
                _DEBUG_ASSERT(is_alive(status_i));
44,890,841✔
877
                if (bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction))
44,890,841✔
878
                {
879
                    ++ same_faction_cards_count;
18,506,338✔
880
                    unsigned allegiance_value = status_i->skill(Skill::allegiance);
18,506,338✔
881
                    if (__builtin_expect(allegiance_value, false) && !status->m_summoned)
18,506,338✔
882
                    {
883
                        _DEBUG_MSG(1, "%s activates Allegiance %u\n", status_description(status_i).c_str(), allegiance_value);
1,208,135✔
884
                        if (! status_i->m_sundered)
1,119,431✔
885
                        { status_i->m_perm_attack_buff += allegiance_value; }
1,076,178✔
886
                        status_i->ext_hp(allegiance_value);
1,119,431✔
887
                    }
888
                }
889
                if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::coldsleep], false)
44,890,841✔
890
                        && status_i->m_protected_stasis && can_be_healed(status_i))
107,078,338✔
891
                {
892
                    unsigned bge_value = (status_i->m_protected_stasis + 1) / 2;
2,256✔
893
                    _DEBUG_MSG(1, "Cold Sleep: %s heals itself for %u\n", status_description(status_i).c_str(), bge_value);
6,768✔
894
                    status_i->add_hp(bge_value);
2,256✔
895
                }
896
            }
897

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

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

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

937
            // summarize stasis when:
938
            //  1. Passive BGE Megamorphosis is enabled
939
            //  2. current faction is marked for it
940
            if ((card->m_delay > 0) && (card->m_type == CardType::assault)
20,092,371✔
941
                    && __builtin_expect(bge_megamorphosis || (fd->players[status->m_player]->stasis_faction_bitmap & played_faction_mask), false))
37,429,490✔
942
            {
943
                unsigned stacked_stasis = (bge_megamorphosis || (fd->players[status->m_player]->commander.m_card->m_faction == card->m_faction))
7,244,474✔
944
                    ? fd->players[status->m_player]->commander.skill(Skill::stasis)
13,475,241✔
945
                    : 0u;
946
#ifndef NDEBUG
947
                if (stacked_stasis > 0)
6,230,767✔
948
                {
949
                    _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
×
950
                            faction_names[card->m_faction].c_str(), stacked_stasis,
951
                            status_description(&fd->players[status->m_player]->commander).c_str(), stacked_stasis);
952
                }
953
#endif
954
                for (CardStatus * status_i : fd->players[status->m_player]->structures.m_indirect)
13,370,840✔
955
                {
956
                    if ((bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)) && is_alive(status_i))
6,083,271✔
957
                    {
958
                        stacked_stasis += status_i->skill(Skill::stasis);
1,864,357✔
959
#ifndef NDEBUG
960
                        if (status_i->skill(Skill::stasis) > 0)
1,864,357✔
961
                        {
962
                            _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
6,083,271✔
963
                                    faction_names[card->m_faction].c_str(), status_i->skill(Skill::stasis),
964
                                    status_description(status_i).c_str(), stacked_stasis);
965
                        }
966
#endif
967
                    }
968
                }
969
                for (CardStatus * status_i : fd->players[status->m_player]->assaults.m_indirect)
28,271,020✔
970
                {
971
                    if ((bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)) && is_alive(status_i))
20,983,451✔
972
                    {
973
                        stacked_stasis += status_i->skill(Skill::stasis);
16,494,431✔
974
#ifndef NDEBUG
975
                        if (status_i->skill(Skill::stasis) > 0)
16,494,431✔
976
                        {
977
                            _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
7,309,640✔
978
                                    faction_names[card->m_faction].c_str(), status_i->skill(Skill::stasis),
979
                                    status_description(status_i).c_str(), stacked_stasis);
980
                        }
981
#endif
982
                        if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::temporalbacklash] && status_i->skill(Skill::counter), false))
16,494,431✔
983
                        {
984
                            stacked_stasis += (status_i->skill(Skill::counter) + 1) / 2;
3,021✔
985
#ifndef NDEBUG
986
                            _DEBUG_MSG(2, "Temporal Backlash: + Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
20,986,472✔
987
                                    faction_names[card->m_faction].c_str(), (status_i->skill(Skill::counter) + 1) / 2,
988
                                    status_description(status_i).c_str(), stacked_stasis);
989
#endif
990
                        }
991
                    }
992
                }
993
                status->m_protected_stasis = stacked_stasis;
7,287,569✔
994
#ifndef NDEBUG
995
                if (stacked_stasis > 0)
7,287,569✔
996
                {
997
                    _DEBUG_MSG(1, "%s gains %u stasis protection\n",
6,039,870✔
998
                            status_description(status).c_str(), stacked_stasis);
999
                }
1000
#endif
1001
                // no more stasis for current faction: do unmark (if no Passive BGE Megamorphosis)
1002
                if (__builtin_expect(!bge_megamorphosis, true) && __builtin_expect(!stacked_stasis, false))
7,287,569✔
1003
                {
1004
                    fd->players[status->m_player]->stasis_faction_bitmap &= ~played_faction_mask;
1,482,061✔
1005
                    _DEBUG_MSG(1, "- Stasis [%s]: no more units with stasis from %s\n",
1,491,509✔
1006
                            faction_names[card->m_faction].c_str(),status_description(&fd->players[status->m_player]->commander).c_str());
1007
                }
1008

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

1022

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

1039
            return status;
21,562,338✔
1040
        }
1041

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

1047
    template <enum CardType::CardType type>
1048
        void placeCard(bool summoned)
21,562,338✔
1049
        {
1050
            status = &storage->add_back();
21,562,338✔
1051
            status->set(card);
21,562,338✔
1052
            status->m_index = storage->size() - 1;
21,562,338✔
1053
            status->m_player = actor_index;
21,562,338✔
1054
            status->m_field = fd;
21,562,338✔
1055
            status->m_summoned = summoned;
21,562,338✔
1056

1057
            // reduce delay for summoned card by tower (structure) for normal (non-triggered) summon skill
1058
            if (summoned && __builtin_expect(fd->fixes[Fix::reduce_delay_summoned_by_tower], true)
21,562,338✔
1059
                && actor_status->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate
3,276,505✔
1060
                && actor_status->m_card->m_type == CardType::structure
357,498✔
1061
                && status->m_delay > 0)
8,418✔
1062
            {
1063
                --status->m_delay;
8,418✔
1064

1065
                _DEBUG_MSG(1, "%s reduces its timer (as summoned by tower)\n", status_description(status).c_str());
16,836✔
1066
            }
1067

1068
#ifndef NQUEST
1069
            if (actor_index == 0)
1070
            {
1071
                if (card->m_type == CardType::assault)
1072
                {
1073
                    fd->inc_counter(QuestType::faction_assault_card_use, card->m_faction);
1074
                }
1075
                fd->inc_counter(QuestType::type_card_use, type);
1076
            }
1077
#endif
1078
            _DEBUG_MSG(1, "%s plays %s %u [%s]\n",
24,072,703✔
1079
                    status_description(actor_status).c_str(), cardtype_names[type].c_str(),
1080
                    static_cast<unsigned>(storage->size() - 1), card_description(fd->cards, card).c_str());
1081
        }
21,562,338✔
1082
};
1083
// assault
1084
    template <>
1085
void PlayCard::setStorage<CardType::assault>()
17,200,510✔
1086
{
1087
    storage = &fd->players[actor_index]->assaults;
17,200,510✔
1088
}
×
1089
// structure
1090
    template <>
1091
void PlayCard::setStorage<CardType::structure>()
4,361,828✔
1092
{
1093
    storage = &fd->players[actor_index]->structures;
4,361,828✔
1094
}
×
1095

1096
// Check if a skill actually proc'ed.
1097
    template<Skill::Skill skill_id>
1098
inline bool skill_check(Field* fd, CardStatus* c, CardStatus* ref)
243,856,151✔
1099
{
1100
    return is_defensive_skill(skill_id) || is_alive(c);
2,764,641✔
1101
}
1102

1103
    template<>
1104
inline bool skill_check<Skill::heal>(Field* fd, CardStatus* c, CardStatus* ref)
71,939,506✔
1105
{
1106
    return can_be_healed(c);
38,589✔
1107
}
1108

1109
    template<>
1110
inline bool skill_check<Skill::mend>(Field* fd, CardStatus* c, CardStatus* ref)
160,087✔
1111
{
1112
    return can_be_healed(c);
×
1113
}
1114

1115
    template<>
1116
inline bool skill_check<Skill::rally>(Field* fd, CardStatus* c, CardStatus* ref)
39,025,782✔
1117
{
1118
    return !c->m_sundered;
39,025,782✔
1119
}
1120

1121
    template<>
1122
inline bool skill_check<Skill::overload>(Field* fd, CardStatus* c, CardStatus* ref)
945,924✔
1123
{
1124
    return is_active(c) && !c->m_overloaded && !has_attacked(c);
730,325✔
1125
}
1126

1127
    template<>
1128
inline bool skill_check<Skill::jam>(Field* fd, CardStatus* c, CardStatus* ref)
10,927,963✔
1129
{
1130
    if(__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::ironwill], false) && c->has_skill(Skill::armor))return false;
10,927,963✔
1131
    // active player performs Jam
1132
    if (fd->tapi == ref->m_player)
10,925,648✔
1133
    { return is_active_next_turn(c); }
10,897,265✔
1134

1135

1136
    // inactive player performs Jam
1137
    return will_activate_this_turn(c);
28,383✔
1138
}
1139

1140
    template<>
1141
inline bool skill_check<Skill::leech>(Field* fd, CardStatus* c, CardStatus* ref)
31,715✔
1142
{
1143
    return can_be_healed(c) || (fd->fixes[Fix::leech_increase_max_hp] && is_alive(c));
26,908✔
1144
}
1145

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

1152
    template<>
1153
inline bool skill_check<Skill::payback>(Field* fd, CardStatus* c, CardStatus* ref)
2,741,124✔
1154
{
1155
    return (ref->m_card->m_type == CardType::assault);
2,741,124✔
1156
}
1157

1158
    template<>
1159
inline bool skill_check<Skill::revenge>(Field* fd, CardStatus* c, CardStatus* ref)
1160
{
1161
    return skill_check<Skill::payback>(fd, c, ref);
1162
}
1163

1164
    template<>
1165
inline bool skill_check<Skill::tribute>(Field* fd, CardStatus* c, CardStatus* ref)
118,524,994✔
1166
{
1167
    return (ref->m_card->m_type == CardType::assault) && (c != ref);
118,524,994✔
1168
}
1169

1170
    template<>
1171
inline bool skill_check<Skill::refresh>(Field* fd, CardStatus* c, CardStatus* ref)
2,816,453✔
1172
{
1173
    return can_be_healed(c);
2,816,453✔
1174
}
1175

1176
    template<>
1177
inline bool skill_check<Skill::drain>(Field* fd, CardStatus* c, CardStatus* ref)
763,281✔
1178
{
1179
    return can_be_healed(c);
763,281✔
1180
}
1181

1182
    template<>
1183
inline bool skill_check<Skill::mark>(Field* fd, CardStatus* c, CardStatus* ref)
57,842✔
1184
{
1185
    return (ref->m_card->m_type == CardType::assault);
57,842✔
1186
}
1187

1188
    template<>
1189
inline bool skill_check<Skill::disease>(Field* fd, CardStatus* c, CardStatus* ref)
165,767✔
1190
{
1191
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
165,767✔
1192
}
1193
    template<>
1194
inline bool skill_check<Skill::inhibit>(Field* fd, CardStatus* c, CardStatus* ref)
2,811,727✔
1195
{
1196
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
2,811,727✔
1197
}
1198
    template<>
1199
inline bool skill_check<Skill::sabotage>(Field* fd, CardStatus* c, CardStatus* ref)
618,343✔
1200
{
1201
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
618,343✔
1202
}
1203
inline unsigned remove_disease(CardStatus* status, unsigned heal)
35,116,743✔
1204
{
1205
    unsigned remaining_heal(heal);
35,116,743✔
1206
    if(__builtin_expect(status->m_diseased == 0,true))
35,116,743✔
1207
    {
1208
        //skip
1209
    }
1210
    else if (heal > status->m_diseased)
68,096✔
1211
    {
1212
        _DEBUG_MSG(1, "%s disease-blocked %u heal\n", status_description(status).c_str(), status->m_diseased);
17,188✔
1213
        remaining_heal = heal - status->m_diseased;
17,188✔
1214
        status->m_diseased = 0;
17,188✔
1215
    }
1216
    else
1217
    {
1218
        _DEBUG_MSG(1, "%s disease-blocked %u heal\n", status_description(status).c_str(), heal);
50,908✔
1219
        status->m_diseased -= heal;
50,908✔
1220
        remaining_heal = 0;
50,908✔
1221
    }
1222
    return remaining_heal;
35,116,743✔
1223
}
1224

1225
// Field is currently not needed for remove_absorption, but is here for similiar structure as remove_hp
1226
inline unsigned remove_absorption(Field* fd, CardStatus* status, unsigned dmg)
25,147,944✔
1227
{
1228
    return remove_absorption(status,dmg);
25,147,944✔
1229
}
1230
/**
1231
 * @brief Remove absorption from a CardStatus
1232
 * 
1233
 * @param status CardStatus to remove absorption from
1234
 * @param dmg damage to remove absorption from
1235
 * @return unsigned remaining damage after absorption is removed
1236
 */
1237
inline unsigned remove_absorption(CardStatus* status, unsigned dmg)
25,147,944✔
1238
{
1239
    unsigned remaining_dmg(dmg);
25,147,944✔
1240
    if(__builtin_expect(status->m_absorption == 0,true))
25,147,944✔
1241
    {
1242
        //skip
1243
    }
1244
    else if(dmg > status->m_absorption)
2,528,398✔
1245
    {
1246
        _DEBUG_MSG(1, "%s absorbs %u damage\n", status_description(status).c_str(), status->m_absorption);
359,838✔
1247
        remaining_dmg = dmg - status->m_absorption;
359,838✔
1248
        status->m_absorption = 0;
359,838✔
1249
    }
1250
    else
1251
    {
1252
        _DEBUG_MSG(1, "%s absorbs %u damage\n", status_description(status).c_str(), dmg);
2,168,560✔
1253
        status->m_absorption -= dmg;
2,168,560✔
1254
        remaining_dmg = 0;
2,168,560✔
1255
    }
1256
    return remaining_dmg;
25,147,944✔
1257
}
1258

1259
void remove_hp(Field* fd, CardStatus* status, unsigned dmg)
36,743,797✔
1260
{
1261
    if (__builtin_expect(!dmg, false)) { return; }
36,743,797✔
1262
    _DEBUG_ASSERT(is_alive(status));
26,144,995✔
1263
    _DEBUG_MSG(2, "%s takes %u damage\n", status_description(status).c_str(), dmg);
26,144,995✔
1264
    status->m_hp = safe_minus(status->m_hp, dmg);
26,144,995✔
1265
    if (fd->current_phase < Field::end_phase && status->has_skill(Skill::barrier))
26,144,995✔
1266
    {
1267
        ++fd->damaged_units_to_times[status];
277,128✔
1268
        _DEBUG_MSG(2, "%s damaged %u times\n",
277,128✔
1269
                status_description(status).c_str(), fd->damaged_units_to_times[status]);
1270
    }
1271
    if (status->m_hp == 0)
26,144,995✔
1272
    {
1273
#ifndef NQUEST
1274
        if (status->m_player == 1)
1275
        {
1276
            if (status->m_card->m_type == CardType::assault)
1277
            {
1278
                fd->inc_counter(QuestType::faction_assault_card_kill, status->m_card->m_faction);
1279
            }
1280
            fd->inc_counter(QuestType::type_card_kill, status->m_card->m_type);
1281
        }
1282
#endif
1283
        _DEBUG_MSG(1, "%s dies\n", status_description(status).c_str());
9,577,716✔
1284
        _DEBUG_ASSERT(status->m_card->m_type != CardType::commander);
9,577,716✔
1285
        fd->killed_units.push_back(status);
9,577,716✔
1286
        ++fd->players[status->m_player]->total_cards_destroyed;
9,577,716✔
1287
        if(!status->m_summoned)++fd->players[status->m_player]->total_nonsummon_cards_destroyed;
9,577,716✔
1288
        if (__builtin_expect((status->m_player == 0) && (fd->players[0]->deck->vip_cards.count(status->m_card->m_id)), false))
12,581,344✔
1289
        {
1290
            fd->players[0]->commander.m_hp = 0;
×
1291
            fd->end = true;
×
1292
        }
1293
    }
1294
}
1295

1296
inline bool is_it_dead(CardStatus& c)
146,366,462✔
1297
{
1298
    if (c.m_hp == 0) // yes it is
146,366,462✔
1299
    {
1300
        _DEBUG_MSG(1, "Dead and removed: %s\n", status_description(&c).c_str());
9,577,716✔
1301
        return true;
9,577,716✔
1302
    }
1303
    return false; // nope still kickin'
1304
}
1305

1306
inline bool is_it_dominion(CardStatus* c)
×
1307
{
1308
    return (c->m_card->m_category == CardCategory::dominion_alpha);
×
1309
}
1310

1311
inline void remove_dead(Storage<CardStatus>& storage)
74,752,672✔
1312
{
1313
    storage.remove(is_it_dead);
74,752,672✔
1314
}
1315

1316
void cooldown_skills(CardStatus * status)
48,833,866✔
1317
{
1318
    for (const auto & ss : status->m_card->m_skills)
188,820,299✔
1319
    {
1320
        if (status->m_skill_cd[ss.id] > 0)
139,986,433✔
1321
        {
1322
            _DEBUG_MSG(2, "%s reduces timer (%u) of skill %s\n",
3,321,504✔
1323
                    status_description(status).c_str(), status->m_skill_cd[ss.id], skill_names[ss.id].c_str());
3,321,504✔
1324
            -- status->m_skill_cd[ss.id];
3,321,504✔
1325
        }
1326
    }
1327
}
48,833,866✔
1328
/**
1329
 * Handle:
1330
 * Absorb, (Activation)Summon, Bravery, (Initial)Valor, Inhibit, Sabotage, Disease, Enhance, (Cooldown/Every X) Reductions
1331
 * 
1332
 * Does not handle these skills for newly summoned units ( i.e. valor, barrier)
1333
 **/
1334
void turn_start_phase_update(Field*fd, CardStatus * status)
58,504,617✔
1335
{
1336
            //apply Absorb + Triggered\{Valor} Enhances
1337
            check_and_perform_early_enhance(fd,status);
117,009,234✔
1338
            if(fd->fixes[Fix::enhance_early])check_and_perform_later_enhance(fd,status);
58,504,617✔
1339
            //refresh absorb
1340
            if(status->has_skill(Skill::absorb))
58,504,617✔
1341
            {
1342
                status->m_absorption = status->skill_base_value(Skill::absorb);
3,729,840✔
1343
            }
1344
            if(__builtin_expect(fd->fixes[Fix::barrier_each_turn],true) && status->has_skill(Skill::barrier)){
58,504,617✔
1345
                _DEBUG_MSG(1,"%s gets barrier protection %u per turn\n",status_description(status).c_str(),status->skill(Skill::barrier));
1,203,468✔
1346
                status->m_protected += status->skill(Skill::barrier);
1,203,468✔
1347
            }
1348
            check_and_perform_bravery(fd,status);
58,504,617✔
1349
            if (status->m_delay > 0)
58,504,617✔
1350
            {
1351
                _DEBUG_MSG(1, "%s reduces its timer\n", status_description(status).c_str());
28,358,919✔
1352
                --status->m_delay;
28,358,919✔
1353
                if (status->m_delay == 0)
28,358,919✔
1354
                {
1355
                    check_and_perform_valor(fd, status);
12,487,202✔
1356
                    if (status->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate)
12,487,202✔
1357
                    {
1358
                        check_and_perform_summon(fd, status);
10,208,935✔
1359
                    }
1360
                }
1361
            }
1362
            else
1363
            {
1364
                cooldown_skills(status);
30,145,698✔
1365
            }
1366
}
58,504,617✔
1367

1368
void turn_start_phase(Field* fd)
18,688,168✔
1369
{
1370
    // Active player's commander card:
1371
    cooldown_skills(&fd->tap->commander);
18,688,168✔
1372
    //grab assaults before new ones get summoned
1373
    auto& assaults(fd->tap->assaults);
18,688,168✔
1374
    unsigned end(assaults.size());
18,688,168✔
1375

1376
    //Perform early enhance for commander
1377
    check_and_perform_early_enhance(fd,&fd->tap->commander);
37,376,336✔
1378
    if(fd->fixes[Fix::enhance_early])check_and_perform_later_enhance(fd,&fd->tap->commander);
18,688,168✔
1379

1380
    // Active player's structure cards:
1381
    // update index
1382
    // reduce delay; reduce skill cooldown
1383
    {
18,688,168✔
1384
        auto& structures(fd->tap->structures);
18,688,168✔
1385
        for(unsigned index(0); index < structures.size(); ++index)
40,575,051✔
1386
        {
1387
            CardStatus * status = &structures[index];
21,886,883✔
1388
            status->m_index = index;
21,886,883✔
1389
            turn_start_phase_update(fd,status);
21,886,883✔
1390
        }
1391
    }
1392
    // Active player's assault cards:
1393
    // update index
1394
    // reduce delay; reduce skill cooldown
1395
    {
1396
        for(unsigned index(0); index < end; ++index)
55,305,902✔
1397
        {
1398
            CardStatus * status = &assaults[index];
36,617,734✔
1399
            status->m_index = index;
36,617,734✔
1400
            turn_start_phase_update(fd,status);
36,617,734✔
1401
        }
1402
    }
1403
    // Defending player's structure cards:
1404
    // update index
1405
    {
18,688,168✔
1406
        auto& structures(fd->tip->structures);
18,688,168✔
1407
        for(unsigned index(0), end(structures.size()); index < end; ++index)
41,383,440✔
1408
        {
1409
            CardStatus& status(structures[index]);
22,695,272✔
1410
            status.m_index = index;
22,695,272✔
1411
        }
1412
    }
1413
    // Defending player's assault cards:
1414
    // update index
1415
    {
18,688,168✔
1416
        auto& assaults(fd->tip->assaults);
18,688,168✔
1417
        for(unsigned index(0), end(assaults.size()); index < end; ++index)
63,743,401✔
1418
        {
1419
            CardStatus& status(assaults[index]);
45,055,233✔
1420
            status.m_index = index;
45,055,233✔
1421
        }
1422
    }
1423
}
18,688,168✔
1424

1425
void turn_end_phase(Field* fd)
18,688,168✔
1426
{
1427
    // Inactive player's assault cards:
1428
    {
18,688,168✔
1429
        auto& assaults(fd->tip->assaults);
18,688,168✔
1430
        for(unsigned index(0), end(assaults.size()); index < end; ++ index)
63,843,055✔
1431
        {
1432
            CardStatus& status(assaults[index]);
45,154,887✔
1433
            if (status.m_hp <= 0)
45,154,887✔
1434
            {
1435
                continue;
7,752,147✔
1436
            }
1437
            status.m_enfeebled = 0;
37,402,740✔
1438
            status.m_protected = 0;
37,402,740✔
1439
            std::memset(status.m_primary_skill_offset, 0, sizeof status.m_primary_skill_offset);
37,402,740✔
1440
            std::memset(status.m_evolved_skill_offset, 0, sizeof status.m_evolved_skill_offset);
37,402,740✔
1441
            std::memset(status.m_enhanced_value, 0, sizeof status.m_enhanced_value);
37,402,740✔
1442
            status.m_evaded = 0;  // so far only useful in Inactive turn
37,402,740✔
1443
            status.m_paybacked = 0;  // ditto
37,402,740✔
1444
            status.m_entrapped = 0;
37,402,740✔
1445
        }
1446
    }
1447
    // Inactive player's structure cards:
1448
    {
18,688,168✔
1449
        auto& structures(fd->tip->structures);
18,688,168✔
1450
        for(unsigned index(0), end(structures.size()); index < end; ++ index)
41,386,939✔
1451
        {
1452
            CardStatus& status(structures[index]);
22,698,771✔
1453
            if (status.m_hp <= 0)
22,698,771✔
1454
            {
1455
                continue;
976,500✔
1456
            }
1457
            // reset the structure's (barrier) protect
1458
            status.m_protected = 0;
21,722,271✔
1459
            status.m_evaded = 0;  // so far only useful in Inactive turn
21,722,271✔
1460
        }
1461
    }
1462

1463
    // Active player's assault cards:
1464
    {
18,688,168✔
1465
        auto& assaults(fd->tap->assaults);
18,688,168✔
1466
        for(unsigned index(0), end(assaults.size()); index < end; ++ index)
72,406,317✔
1467
        {
1468
            CardStatus& status(assaults[index]);
53,718,149✔
1469
            if (status.m_hp <= 0)
53,718,149✔
1470
            {
1471
                continue;
828,252✔
1472
            }
1473
            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
52,889,897✔
1474

1475
            if (refresh_value && skill_check<Skill::refresh>(fd, &status, nullptr))
52,889,897✔
1476
            {
1477
                _DEBUG_MSG(1, "%s refreshes %u health\n", status_description(&status).c_str(), refresh_value);
123,103✔
1478
                status.add_hp(refresh_value);
123,103✔
1479
            }
1480
            if (status.m_poisoned > 0)
52,889,897✔
1481
            {
1482
                if(! __builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
114,450✔
1483
                {
1484
                    unsigned poison_dmg = remove_absorption(fd,&status,status.m_poisoned + status.m_enfeebled);
×
1485
                    poison_dmg = safe_minus(poison_dmg, status.protected_value());
×
1486
                    if (poison_dmg > 0)
×
1487
                    {
1488
#ifndef NQUEST
1489
                    if (status.m_player == 1)
1490
                    {
1491
                        fd->inc_counter(QuestType::skill_damage, Skill::poison, 0, poison_dmg);
1492
                    }
1493
#endif
1494
                        _DEBUG_MSG(1, "%s takes poison damage %u\n", status_description(&status).c_str(), poison_dmg);
×
1495
                        remove_hp(fd, &status, poison_dmg);  // simultaneous
×
1496
                    }
1497
                }
1498
                else {
1499
                    unsigned poison_dmg = status.m_poisoned;
114,450✔
1500
                    _DEBUG_MSG(1, "%s takes poison damage %u\n", status_description(&status).c_str(), poison_dmg);
114,450✔
1501
                    remove_hp(fd, &status, poison_dmg);  // simultaneous
114,450✔
1502
                    status.m_poisoned = status.m_poisoned - (poison_dmg+1)/2;
114,450✔
1503
                }
1504
            }
1505
            // end of the opponent's next turn for enemy units
1506
            status.m_temp_attack_buff = 0;
52,889,897✔
1507
            status.m_jammed = false;
52,889,897✔
1508
            status.m_enraged = 0;
52,889,897✔
1509
            status.m_sundered = false;
52,889,897✔
1510
            status.m_inhibited = 0;
52,889,897✔
1511
            status.m_sabotaged = 0;
52,889,897✔
1512
            status.m_tributed = 0;
52,889,897✔
1513
            status.m_overloaded = false;
52,889,897✔
1514
            status.m_step = CardStep::none;
52,889,897✔
1515
        }
1516
    }
1517
    // Active player's structure cards:
1518
    // nothing so far
1519

1520
    prepend_on_death(fd);  // poison
18,688,168✔
1521
    resolve_skill(fd);
18,688,168✔
1522
    remove_dead(fd->tap->assaults);
18,688,168✔
1523
    remove_dead(fd->tap->structures);
18,688,168✔
1524
    remove_dead(fd->tip->assaults);
18,688,168✔
1525
    remove_dead(fd->tip->structures);
18,688,168✔
1526
}
18,688,168✔
1527

1528
//---------------------- $50 attack by assault card implementation -------------
1529
/**
1530
 * @brief Return the damage dealt to the attacker (att) by defender (def) through counter skill
1531
 * The counter is increased by the attacker's enfeebled value, while the attacker's protected value is subtracted from the damage.
1532
 * The absorption is removed from the damage before the protected value is subtracted.
1533
 * 
1534
 * @param fd Field pointer 
1535
 * @param att attacker CardStatus
1536
 * @param def defender CardStatus
1537
 * @return unsigned 
1538
 */
1539
inline unsigned counter_damage(Field* fd, CardStatus* att, CardStatus* def)
2,098,382✔
1540
{
1541
    _DEBUG_ASSERT(att->m_card->m_type == CardType::assault);
2,098,382✔
1542
    _DEBUG_ASSERT(def->skill(Skill::counter) > 0); // counter skill must be present otherwise enfeeble is wrongly applied
2,098,382✔
1543

1544
    return safe_minus(remove_absorption(fd,att,def->skill(Skill::counter) + att->m_enfeebled), att->protected_value());
2,098,382✔
1545
}
1546

1547
inline CardStatus* select_first_enemy_wall(Field* fd)
7,829,762✔
1548
{
1549
    for(unsigned i(0); i < fd->tip->structures.size(); ++i)
14,773,628✔
1550
    {
1551
        CardStatus* c(&fd->tip->structures[i]);
7,736,998✔
1552
        if (c->has_skill(Skill::wall) && is_alive(c)) { return c; }
7,736,998✔
1553
    }
1554
    return nullptr;
1555
}
1556

1557
inline CardStatus* select_first_enemy_assault(Field* fd)
1558
{
1559
    for(unsigned i(0); i < fd->tip->assaults.size(); ++i)
47,978✔
1560
    {
1561
        CardStatus* c(&fd->tip->assaults[i]);
37,912✔
1562
        if (is_alive(c)) { return c; }
37,912✔
1563
    }
1564
    return nullptr;
1565
}
1566

1567

1568
inline bool alive_assault(Storage<CardStatus>& assaults, unsigned index)
22,266,539✔
1569
{
1570
    return(assaults.size() > index && is_alive(&assaults[index]));
15,905,309✔
1571
}
1572

1573
void remove_commander_hp(Field* fd, CardStatus& status, unsigned dmg)
7,034,856✔
1574
{
1575
    _DEBUG_ASSERT(is_alive(&status));
7,034,856✔
1576
    _DEBUG_ASSERT(status.m_card->m_type == CardType::commander);
7,034,856✔
1577
    _DEBUG_MSG(2, "%s takes %u damage\n", status_description(&status).c_str(), dmg);
7,034,856✔
1578
    status.m_hp = safe_minus(status.m_hp, dmg);
7,034,856✔
1579
    if (status.m_hp == 0)
7,034,856✔
1580
    {
1581
        _DEBUG_MSG(1, "%s dies\n", status_description(&status).c_str());
1,315,660✔
1582
        fd->end = true;
1,315,660✔
1583
    }
1584
}
7,034,856✔
1585

1586
//------------------------------------------------------------------------------
1587
// implementation of one attack by an assault card, against either an enemy
1588
// assault card, the first enemy wall, or the enemy commander.
1589
struct PerformAttack
1590
{
1591
    Field* fd;
1592
    CardStatus* att_status;
1593
    CardStatus* def_status;
1594
    unsigned att_dmg;
1595

1596
    PerformAttack(Field* fd_, CardStatus* att_status_, CardStatus* def_status_) :
22,266,539✔
1597
        fd(fd_), att_status(att_status_), def_status(def_status_), att_dmg(0)
22,266,539✔
1598
    {}
1599
    /**
1600
     * @brief Perform the attack
1601
     * 
1602
     * New Evaluation order:
1603
     *  Flying
1604
     *  Subdue
1605
     *  Attack Damage
1606
     *  On-Attacked Skills
1607
     *  Counter
1608
     *  Leech
1609
     *  Drain
1610
     *  Berserk
1611
     *  Mark
1612
     * 
1613
     * Old Evaluation order:
1614
     *  check flying
1615
     *  modify damage
1616
     *  deal damage
1617
     *  assaults only: (poison,inihibit, sabotage)
1618
     *  on-attacked skills
1619
     *  counter, berserk
1620
     *  assaults only: (leech if still alive)
1621
     *  bge
1622
     *  subdue
1623
     * 
1624
     * @tparam def_cardtype 
1625
     * @return unsigned 
1626
     */
1627
    template<enum CardType::CardType def_cardtype>
1628
        unsigned op()
22,266,539✔
1629
        {
1630
            // Bug fix? 2023-04-03 a card with zero attack power can not attack and won't trigger subdue
1631
            // Confirmed subdue behaviour by MK 2023-07-12
1632
            if(att_status->attack_power()== 0) { return 0; }
22,266,539✔
1633

1634

1635
            if(__builtin_expect(def_status->has_skill(Skill::flying),false) && fd->flip()) {
22,266,539✔
1636
                _DEBUG_MSG(1, "%s flies away from %s\n",
101,318✔
1637
                        status_description(def_status).c_str(),
1638
                        status_description(att_status).c_str());
1639
                return 0;
50,659✔
1640
            }
1641

1642
            if ( __builtin_expect(fd->fixes[Fix::subdue_before_attack],true)) {
22,215,880✔
1643
                perform_subdue(fd, att_status, def_status);
22,215,880✔
1644
                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."
22,215,880✔
1645
                { 
1646
                    return 0; 
1647
                }
1648
            }
1649
            
1650
            // APN Bug fix for subdue not reducing attack damage
1651
            // https://github.com/APN-Pucky/tyrant_optimize/issues/79
1652
            unsigned pre_modifier_dmg = att_status->attack_power();
22,211,011✔
1653

1654
            modify_attack_damage<def_cardtype>(pre_modifier_dmg);
22,211,011✔
1655

1656
            if(att_dmg) {
22,211,011✔
1657
                // Deal the attack damage
1658
                attack_damage<def_cardtype>();
18,516,259✔
1659

1660
                // Game Over -> return
1661
                if (__builtin_expect(fd->end, false)) { return att_dmg; }
18,516,259✔
1662
                damage_dependant_pre_oa<def_cardtype>();
10,688,271✔
1663
            }
1664
            // on attacked does also trigger if the attack damage is blocked to zero
1665
            on_attacked<def_cardtype>();
20,895,351✔
1666

1667
            // only if alive attacker
1668
            if (is_alive(att_status) && (__builtin_expect(fd->fixes[Fix::counter_without_damage],true) || att_dmg) )
20,895,351✔
1669
            {
1670
                perform_counter<def_cardtype>(fd, att_status, def_status);    
20,883,382✔
1671
            }
1672
            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"
20,895,351✔
1673
            {
1674
                perform_corrosive(fd, att_status, def_status);
20,170,018✔
1675
            }
1676
            if (is_alive(att_status) && att_dmg)
20,895,351✔
1677
            {
1678
                // Bug fix? 2023-04-03 leech after counter but before drain/berserk
1679
                // Skill: Leech
1680
                do_leech<def_cardtype>();
10,531,662✔
1681
            }
1682

1683
            // Bug fix? 2023-04-03 drain now part of the PerformAttack.op() instead of attack_phase, like hunt.
1684
            // perform swipe/drain
1685
            perform_swipe_drain<def_cardtype>(fd, att_status, def_status, att_dmg);
20,895,351✔
1686

1687
            if (is_alive(att_status) && att_dmg) {
20,895,351✔
1688
                perform_berserk(fd, att_status, def_status);
16,536,535✔
1689
            }
1690
            if (is_alive(att_status) )  {
20,895,351✔
1691
                perform_poison(fd, att_status, def_status);
20,170,018✔
1692
            }
1693

1694
            if (is_alive(att_status) && att_dmg) {
20,895,351✔
1695
                perform_mark(fd, att_status, def_status);
16,536,535✔
1696

1697
                perform_bge_heroism<def_cardtype>(fd, att_status, def_status);
16,536,535✔
1698
                perform_bge_devour<def_cardtype>(fd, att_status, def_status);
16,536,535✔
1699

1700
                if ( __builtin_expect(!fd->fixes[Fix::subdue_before_attack],false)) {
16,536,535✔
1701
                    perform_subdue(fd, att_status, def_status);
×
1702
                }
1703
            }
1704
            // perform hunt
1705
            perform_hunt(fd, att_status, def_status);
20,895,351✔
1706

1707
            return att_dmg;
20,895,351✔
1708
        }
1709

1710
        /**
1711
         * @brief Modify attack damage
1712
         * This setts att_dmg in PerformAttack.
1713
         * 
1714
         * @tparam CardType::CardType 
1715
         * @param pre_modifier_dmg base damage
1716
         */
1717
    template<enum CardType::CardType>
1718
        void modify_attack_damage(unsigned pre_modifier_dmg)
22,211,011✔
1719
        {
1720
            _DEBUG_ASSERT(att_status->m_card->m_type == CardType::assault);
22,211,011✔
1721
            att_dmg = pre_modifier_dmg;
22,211,011✔
1722
            if (att_dmg == 0)
22,211,011✔
1723
            { return; }
×
1724
#ifndef NDEBUG
1725
            std::string desc;
22,211,011✔
1726
#endif
1727
            auto& att_assaults = fd->tap->assaults; // (active) attacker assaults
22,211,011✔
1728
            auto& def_assaults = fd->tip->assaults; // (inactive) defender assaults
22,211,011✔
1729
            unsigned legion_value = 0;
22,211,011✔
1730
            unsigned coalition_value = 0;
22,211,011✔
1731

1732
            // Enhance damage (if additional damage isn't prevented)
1733
            if (! att_status->m_sundered)
22,211,011✔
1734
            {
1735
                // Skill: Mark
1736
                unsigned marked_value = def_status->m_marked;
20,943,941✔
1737
                if(marked_value)
20,943,941✔
1738
                {
1739
#ifndef NDEBUG
1740
                    if (debug_print > 0) { desc += "+" + tuo::to_string(marked_value) + "(mark)"; }
47,159✔
1741
#endif
1742
                    att_dmg += marked_value;
11,957✔
1743
                }
1744
                // Skill: Legion
1745
                unsigned legion_base = att_status->skill(Skill::legion);
20,943,941✔
1746
                if (__builtin_expect(legion_base, false))
20,943,941✔
1747
                {
1748
                    unsigned itr_idx, till_idx;
1749
                    bool bge_megamorphosis = fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis] && !fd->fixes[Fix::legion_under_mega];
2,560,015✔
1750
                    //scan all assaults for Global Legion
1751
                    itr_idx = 0;
2,560,015✔
1752
                    till_idx = att_assaults.size() - 1;
2,560,015✔
1753
                    for (; itr_idx <= till_idx; ++ itr_idx)
16,936,787✔
1754
                    {
1755
                        if(itr_idx == att_status->m_index)continue; //legion doesn't count itself, unlike coalition
14,376,772✔
1756
                        legion_value += is_alive(&att_assaults[itr_idx])
23,610,121✔
1757
                          && (bge_megamorphosis || (att_assaults[itr_idx].m_card->m_faction == att_status->m_card->m_faction));
11,816,757✔
1758
                    }
1759
                    if (legion_value)
2,560,015✔
1760
                    {
1761
                        legion_value *= legion_base;
2,401,410✔
1762
#ifndef NDEBUG
1763
                        if (debug_print > 0) { desc += "+" + tuo::to_string(legion_value) + "(legion)"; }
2,664,417✔
1764
#endif
1765
                        att_dmg += legion_value;
2,401,410✔
1766
                    }
1767
                }
1768

1769
                // Skill: Coalition
1770
                unsigned coalition_base = att_status->skill(Skill::coalition);
20,943,941✔
1771
                if (__builtin_expect(coalition_base, false))
20,943,941✔
1772
                {
1773
                    uint8_t factions_bitmap = 0;
1,349,564✔
1774
                    for (CardStatus * status : att_assaults.m_indirect)
6,990,123✔
1775
                    {
1776
                        if (! is_alive(status)) { continue; }
5,640,559✔
1777
                        factions_bitmap |= (1 << (status->m_card->m_faction));
5,634,243✔
1778
                    }
1779
                    _DEBUG_ASSERT(factions_bitmap);
1,349,564✔
1780
                    unsigned uniq_factions = byte_bits_count(factions_bitmap);
1,349,564✔
1781
                    coalition_value = coalition_base * uniq_factions;
1,349,564✔
1782
#ifndef NDEBUG
1783
                    if (debug_print > 0) { desc += "+" + tuo::to_string(coalition_value) + "(coalition/x" + tuo::to_string(uniq_factions) + ")"; }
1,885,592✔
1784
#endif
1785
                    att_dmg += coalition_value;
1,349,564✔
1786
                }
1787

1788
                // Skill: Rupture
1789
                unsigned rupture_value = att_status->skill(Skill::rupture);
20,943,941✔
1790
                if (rupture_value > 0)
20,943,941✔
1791
                {
1792
#ifndef NDEBUG
1793
                    if (debug_print > 0) { desc += "+" + tuo::to_string(rupture_value) + "(rupture)"; }
×
1794
#endif
1795
                    att_dmg += rupture_value;
×
1796
                }
1797

1798
                // Skill: Venom
1799
                unsigned venom_value = att_status->skill(Skill::venom);
20,943,941✔
1800
                if (venom_value > 0 && def_status->m_poisoned > 0)
20,943,941✔
1801
                {
1802
#ifndef NDEBUG
1803
                    if (debug_print > 0) { desc += "+" + tuo::to_string(venom_value) + "(venom)"; }
83,986✔
1804
#endif
1805
                    att_dmg += venom_value;
33,733✔
1806
                }
1807

1808
                // Passive BGE: Bloodlust
1809
                if (fd->bloodlust_value > 0)
20,943,941✔
1810
                {
1811
#ifndef NDEBUG
1812
                    if (debug_print > 0) { desc += "+" + tuo::to_string(fd->bloodlust_value) + "(bloodlust)"; }
206,492✔
1813
#endif
1814
                    att_dmg += fd->bloodlust_value;
51,623✔
1815
                }
1816

1817
                // State: Enfeebled
1818
                if (def_status->m_enfeebled > 0)
20,943,941✔
1819
                {
1820
#ifndef NDEBUG
1821
                    if (debug_print > 0) { desc += "+" + tuo::to_string(def_status->m_enfeebled) + "(enfeebled)"; }
2,879,088✔
1822
#endif
1823
                    att_dmg += def_status->m_enfeebled;
1,965,204✔
1824
                }
1825
            }
1826
            // prevent damage
1827
#ifndef NDEBUG
1828
            std::string reduced_desc;
22,211,011✔
1829
#endif
1830
            unsigned reduced_dmg(0); // damage reduced by armor, protect and in turn again canceled by pierce, etc.
22,211,011✔
1831
            unsigned armor_value = 0;
22,211,011✔
1832
            // Armor
1833
            if (def_status->m_card->m_type == CardType::assault) {
22,211,011✔
1834
                // Passive BGE: Fortification (adj step -> 1 (1 left, host, 1 right)
1835
                unsigned adj_size = (unsigned)(fd->bg_effects[fd->tapi][PassiveBGE::fortification]);
14,383,023✔
1836
                unsigned host_idx = def_status->m_index;
14,383,023✔
1837
                unsigned from_idx = safe_minus(host_idx, adj_size);
14,383,023✔
1838
                unsigned till_idx = std::min(host_idx + adj_size, safe_minus(def_assaults.size(), 1));
26,849,154✔
1839
                for (; from_idx <= till_idx; ++ from_idx)
28,793,183✔
1840
                {
1841
                    CardStatus* adj_status = &def_assaults[from_idx];
14,410,160✔
1842
                    if (!is_alive(adj_status)) { continue; }
14,410,160✔
1843
                    armor_value = std::max(armor_value, adj_status->skill(Skill::armor));
18,382,234✔
1844
                }
1845
            }
1846
            if (armor_value > 0)
22,211,011✔
1847
            {
1848
#ifndef NDEBUG
1849
                if(debug_print > 0) { reduced_desc += tuo::to_string(armor_value) + "(armor)"; }
4,309,554✔
1850
#endif
1851
                reduced_dmg += armor_value;
1852
            }
1853
            if (def_status->protected_value() > 0)
22,211,011✔
1854
            {
1855
#ifndef NDEBUG
1856
                if(debug_print > 0) { reduced_desc += (reduced_desc.empty() ? "" : "+") + tuo::to_string(def_status->protected_value()) + "(protected)"; }
11,424,417✔
1857
#endif
1858
                reduced_dmg += def_status->protected_value();
8,902,287✔
1859
            }
1860
            unsigned pierce_value = att_status->skill(Skill::pierce);
22,211,011✔
1861
            if (reduced_dmg > 0 && pierce_value > 0)
22,211,011✔
1862
            {
1863
#ifndef NDEBUG
1864
                if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(pierce_value) + "(pierce)"; }
545,443✔
1865
#endif
1866
                reduced_dmg = safe_minus(reduced_dmg, pierce_value);
22,211,011✔
1867
            }
1868
            unsigned rupture_value = att_status->skill(Skill::rupture);
22,211,011✔
1869
            if (reduced_dmg > 0 && rupture_value > 0)
22,211,011✔
1870
            {
1871
#ifndef NDEBUG
1872
                if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(rupture_value) + "(rupture)"; }
×
1873
#endif
1874
                reduced_dmg = safe_minus(reduced_dmg, rupture_value);
22,211,011✔
1875
            }
1876
            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."
22,211,011✔
1877
            {
1878
                unsigned corrosive_value = def_status->m_corroded_rate;
22,211,011✔
1879
                if (reduced_dmg > 0 && corrosive_value > 0)
22,211,011✔
1880
                {
1881
#ifndef NDEBUG
1882
                    if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(corrosive_value) + "(corrosive)"; }
33,879✔
1883
#endif
1884
                    reduced_dmg = safe_minus(reduced_dmg, corrosive_value);
22,211,011✔
1885
                }
1886
            }
1887
            att_dmg = safe_minus(att_dmg, reduced_dmg);
22,211,011✔
1888
#ifndef NDEBUG
1889
            if (debug_print > 0)
22,211,011✔
1890
            {
1891
                if(!reduced_desc.empty()) { desc += "-[" + reduced_desc + "]"; }
4,707,312✔
1892
                if(!desc.empty()) { desc += "=" + tuo::to_string(att_dmg); }
5,293,976✔
1893
                else { assert(att_dmg == pre_modifier_dmg); }
1,660,337✔
1894
                _DEBUG_MSG(1, "%s attacks %s for %u%s damage\n",
5,743,100✔
1895
                        status_description(att_status).c_str(),
1896
                        status_description(def_status).c_str(), pre_modifier_dmg, desc.c_str());
1897
            }
1898
#endif
1899
            // Passive BGE: Brigade
1900
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::brigade] && legion_value, false)
22,211,011✔
1901
                    && can_be_healed(att_status))
×
1902
            {
1903
                _DEBUG_MSG(1, "Brigade: %s heals itself for %u\n",
×
1904
                        status_description(att_status).c_str(), legion_value);
1905
                att_status->add_hp(legion_value);
×
1906
            }
1907

1908
            // Passive BGE: Unity
1909
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::unity] && coalition_value, false)
22,211,011✔
1910
                    && can_be_healed(att_status))
22,214,535✔
1911
            {
1912
                _DEBUG_MSG(1, "Unity: %s heals itself for %u\n",
932✔
1913
                        status_description(att_status).c_str(), (coalition_value + 1)/2);
1914
                att_status->add_hp((coalition_value + 1)/2);
466✔
1915
            }
1916
        }
22,211,011✔
1917

1918
    template<enum CardType::CardType>
1919
        void attack_damage()
11,481,403✔
1920
        {
1921

1922
            remove_hp(fd, def_status, att_dmg);
11,481,403✔
1923
            prepend_on_death(fd);
11,481,403✔
1924
            resolve_skill(fd);
11,481,403✔
1925
        }
11,481,403✔
1926

1927
    template<enum CardType::CardType>
1928
        void on_attacked() {
20,895,351✔
1929
            //APN
1930
            // resolve On-Attacked skills
1931
            for (const auto& ss: def_status->m_card->m_skills_on_attacked)
21,093,625✔
1932
            {
1933
                _DEBUG_MSG(1, "On Attacked %s: Preparing (tail) skill %s\n",
396,548✔
1934
                        status_description(def_status).c_str(), skill_description(fd->cards, ss).c_str());
1935
                fd->skill_queue.emplace_back(def_status, ss);
198,274✔
1936
                resolve_skill(fd);
198,274✔
1937
            }
1938
        }
20,895,351✔
1939

1940
    template<enum CardType::CardType>
1941
        void damage_dependant_pre_oa() {}
1942

1943
    template<enum CardType::CardType>
1944
        void do_leech() {}
1945

1946
    template<enum CardType::CardType>
1947
        void perform_swipe_drain(Field* fd, CardStatus* att_status, CardStatus* def_status,unsigned att_dmg) {}
1948
    };
1949

1950

1951
    template<>
1952
void PerformAttack::attack_damage<CardType::commander>()
7,034,856✔
1953
{
1954
    remove_commander_hp(fd, *def_status, att_dmg);
7,034,856✔
1955
}
×
1956

1957
    template<>
1958
void PerformAttack::damage_dependant_pre_oa<CardType::assault>()
10,688,271✔
1959
{
1960
    if (!__builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
10,688,271✔
1961
    {
1962
    unsigned poison_value = std::max(att_status->skill(Skill::poison), att_status->skill(Skill::venom));
×
1963
    
1964
    if (poison_value > def_status->m_poisoned && skill_check<Skill::poison>(fd, att_status, def_status))
×
1965
    {
1966
        // perform_skill_poison
1967
#ifndef NQUEST
1968
        if (att_status->m_player == 0)
1969
        {
1970
            fd->inc_counter(QuestType::skill_use, Skill::poison);
1971
        }
1972
#endif
1973
        _DEBUG_MSG(1, "%s poisons %s by %u\n",
×
1974
                status_description(att_status).c_str(),
1975
                status_description(def_status).c_str(), poison_value);
×
1976
        def_status->m_poisoned = poison_value;
×
1977
    }
1978
    }
1979
    else {            
1980
        unsigned venom_value = att_status->skill(Skill::venom);
10,688,271✔
1981
        if (venom_value > 0 && skill_check<Skill::venom>(fd, att_status, def_status)) {
10,688,271✔
1982
#ifndef NQUEST
1983
              if (att_status->m_player == 0)
1984
              {
1985
                 fd->inc_counter(QuestType::skill_use, Skill::venom);
1986
              }
1987
#endif
1988

1989
              _DEBUG_MSG(1, "%s venoms %s by %u\n",
266,123✔
1990
                  status_description(att_status).c_str(),
1991
                  status_description(def_status).c_str(), venom_value);
266,123✔
1992
              // new venom keeps adding stacks
1993
              def_status->m_poisoned += venom_value;
266,123✔
1994
        }
1995
    }
1996
}
10,688,271✔
1997

1998

1999
    template<>
2000
void PerformAttack::do_leech<CardType::assault>()
10,531,662✔
2001
{
2002
    unsigned leech_value = std::min(att_dmg, att_status->skill(Skill::leech));
10,531,662✔
2003
    if(leech_value > 0 && skill_check<Skill::leech>(fd, att_status, nullptr))
10,531,662✔
2004
    {
2005
#ifndef NQUEST
2006
        if (att_status->m_player == 0)
2007
        {
2008
            fd->inc_counter(QuestType::skill_use, Skill::leech);
2009
        }
2010
#endif
2011
        _DEBUG_MSG(1, "%s leeches %u health\n", status_description(att_status).c_str(), leech_value);
31,715✔
2012
        if (__builtin_expect(fd->fixes[Fix::leech_increase_max_hp],true)) {
31,715✔
2013
            att_status->ext_hp(leech_value);
31,715✔
2014
        }
2015
        else {
2016
            att_status->add_hp(leech_value);
×
2017
        }
2018
    }
2019
}
10,531,662✔
2020

2021
// General attack phase by the currently evaluated assault, taking into accounts exotic stuff such as flurry, etc.
2022
unsigned attack_commander(Field* fd, CardStatus* att_status)
7,829,762✔
2023
{
2024
    CardStatus* def_status{select_first_enemy_wall(fd)}; // defending wall
7,829,762✔
2025
    if (def_status != nullptr)
7,829,762✔
2026
    {
2027
        return PerformAttack{fd, att_status, def_status}.op<CardType::structure>();
793,132✔
2028
    }
2029
    else
2030
    {
2031
        return PerformAttack{fd, att_status, &fd->tip->commander}.op<CardType::commander>();
7,036,630✔
2032
    }
2033
}
2034
// Return true if actually attacks
2035
bool attack_phase(Field* fd)
24,926,051✔
2036
{
2037
    CardStatus* att_status(&fd->tap->assaults[fd->current_ci]); // attacking card
24,926,051✔
2038
    Storage<CardStatus>& def_assaults(fd->tip->assaults);
24,926,051✔
2039

2040
    if (!att_status->attack_power())
24,926,051✔
2041
    {
2042
        _DEBUG_MSG(1, "%s cannot take attack (zeroed)\n", status_description(att_status).c_str());
2,659,512✔
2043
        return false;
2,659,512✔
2044
    }
2045

2046
    unsigned att_dmg = 0;
22,266,539✔
2047
    if (alive_assault(def_assaults, fd->current_ci))
22,266,539✔
2048
    {
2049
        CardStatus* def_status = &def_assaults[fd->current_ci];
14,436,777✔
2050
        att_dmg = PerformAttack{fd, att_status, def_status}.op<CardType::assault>();
14,436,777✔
2051
        
2052
    }
2053
    else
2054
    {
2055
        // might be blocked by walls
2056
        att_dmg = attack_commander(fd, att_status);
7,829,762✔
2057
    }
2058

2059
    // Passive BGE: Bloodlust
2060
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::bloodlust], false)
22,266,539✔
2061
            && !fd->assault_bloodlusted && (att_dmg > 0))
22,266,539✔
2062
    {
2063
        fd->bloodlust_value += fd->bg_effects[fd->tapi][PassiveBGE::bloodlust];
113,582✔
2064
        fd->assault_bloodlusted = true;
113,582✔
2065
    }
2066

2067
    return true;
2068
}
2069

2070
//---------------------- $65 active skills implementation ----------------------
2071
template<
2072
bool C
2073
, typename T1
2074
, typename T2
2075
>
2076
struct if_
2077
{
2078
    typedef T1 type;
2079
};
2080

2081
template<
2082
typename T1
2083
, typename T2
2084
>
2085
struct if_<false,T1,T2>
2086
{
2087
    typedef T2 type;
2088
};
2089

2090
    template<unsigned skill_id>
2091
inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
287✔
2092
{ return skill_check<static_cast<Skill::Skill>(skill_id)>(fd, dst, src); }
363,963,311✔
2093

2094
    template<>
2095
inline bool skill_predicate<Skill::enhance>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
31,856,583✔
2096
{
2097
    if (!is_alive(dst)) return false;
31,856,583✔
2098
    if (!dst->has_skill(s.s)) return false;
63,713,166✔
2099
    if (is_active(dst)) return true;
7,339,434✔
2100
    if (is_defensive_skill(s.s)) return true;
3,825,152✔
2101
    if (is_instant_debuff_skill(s.s)) return true; // Enhance Sabotage, Inhibit, Disease also without dst being active
524,716✔
2102
    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)
524,616✔
2103

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

2112
    template<>
2113
inline bool skill_predicate<Skill::evolve>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,077,863✔
2114
{
2115
    if (!is_alive(dst)) return false;
3,077,863✔
2116
    if (!dst->has_skill(s.s)) return false;
6,149,858✔
2117
    if (dst->has_skill(s.s2)) return false;
1,868,842✔
2118
    if (is_active(dst)) return true;
934,421✔
2119
    if (is_defensive_skill(s.s2)) return true;
534,549✔
2120

2121
    /* Strange Transmission [Gilians]: strange gillian's behavior implementation:
2122
     * The Gillian commander and assaults can enhance any skills on any assaults
2123
     * regardless of jammed/delayed states. But what kind of behavior is in the case
2124
     * when gilians are played among standard assaults, I don't know. :)
2125
     */
2126
    return is_alive_gilian(src);
2127
}
2128

2129
    template<>
2130
inline bool skill_predicate<Skill::mimic>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
1,172,366✔
2131
{
2132
    // skip dead units
2133
    if (!is_alive(dst)) return false;
1,172,366✔
2134

2135
    //include on activate/attacked/death
2136
    //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})
2137
    //
2138
    std::vector<std::vector<SkillSpec>> all;
1,127,037✔
2139
    all.emplace_back(dst->m_card->m_skills);
1,127,037✔
2140
    all.emplace_back(dst->m_card->m_skills_on_attacked);
1,127,037✔
2141

2142
    for(std::vector<SkillSpec> & a  : all)
1,796,995✔
2143
    {
2144
    // scan all enemy skills until first activation
2145
    for (const SkillSpec & ss: a)
3,460,586✔
2146
    {
2147
        // get skill
2148
        Skill::Skill skill_id = static_cast<Skill::Skill>(ss.id);
2,790,628✔
2149

2150
        // skip non-activation skills and Mimic (Mimic can't be mimicked)
2151
        if (!is_activation_skill(skill_id) || (skill_id == Skill::mimic))
3,592,436✔
2152
        { continue; }
1,988,820✔
2153

2154
        // skip mend for non-assault mimickers
2155
        if ((skill_id == Skill::mend || skill_id == Skill::fortify) && (src->m_card->m_type != CardType::assault))
801,808✔
2156
        { continue; }
2,800✔
2157

2158
        // enemy has at least one activation skill that can be mimicked, so enemy is eligible target for Mimic
2159
        return true;
1,127,037✔
2160
    }
2161
    }
2162

2163
    // found nothing (enemy has no skills to be mimicked, so enemy isn't eligible target for Mimic)
2164
    return false;
2165
}
1,127,037✔
2166

2167
    template<>
2168
inline bool skill_predicate<Skill::overload>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
705,846✔
2169
{
2170
    // basic skill check
2171
    if (!skill_check<Skill::overload>(fd, dst, src))
1,142,380✔
2172
    { return false; }
2173

2174
    // check skills
2175
    bool inhibited_searched = false;
361,154✔
2176
    for (const auto& ss: dst->m_card->m_skills)
1,025,940✔
2177
    {
2178
        // skip cooldown skills
2179
        if (dst->m_skill_cd[ss.id] > 0)
932,178✔
2180
        { continue; }
11,735✔
2181

2182
        // get evolved skill
2183
        Skill::Skill evolved_skill_id = static_cast<Skill::Skill>(ss.id + dst->m_evolved_skill_offset[ss.id]);
920,443✔
2184

2185
        // unit with an activation hostile skill is always valid target for OL
2186
        if (is_activation_hostile_skill(evolved_skill_id))
1,187,090✔
2187
        { return true; }
2188

2189
        // unit with an activation helpful skill is valid target only when there are inhibited units
2190
        // TODO check mend/fortify valid overload target?!?
2191
        if ((evolved_skill_id != Skill::mend && evolved_skill_id != Skill::fortify)
657,243✔
2192
                && is_activation_helpful_skill(evolved_skill_id)
664,786✔
2193
                && __builtin_expect(!inhibited_searched, true))
840,419✔
2194
        {
2195
            for (const auto & c: fd->players[dst->m_player]->assaults.m_indirect)
968,522✔
2196
            {
2197
                if (is_alive(c) && c->m_inhibited)
794,177✔
2198
                { return true; }
267,392✔
2199
            }
2200
            inhibited_searched = true;
2201
        }
2202
    }
2203
    return false;
2204
}
2205

2206
    template<>
2207
inline bool skill_predicate<Skill::rally>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
27,067,980✔
2208
{
2209
    return skill_check<Skill::rally>(fd, dst, src) // basic skill check
27,067,980✔
2210
        && (__builtin_expect((fd->tapi == dst->m_player), true) // is target on the active side?
27,067,980✔
2211
                ? is_active(dst) && !has_attacked(dst) // normal case
15,636,267✔
2212
                : is_active_next_turn(dst) // diverted case / on-death activation
27,067,980✔
2213
           )
27,067,980✔
2214
        ;
2215
}
2216

2217
    template<>
2218
inline bool skill_predicate<Skill::enrage>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
32,158,009✔
2219
{
2220
    return (__builtin_expect((fd->tapi == dst->m_player), true) // is target on the active side?
32,158,009✔
2221
            ? is_active(dst) && (dst->m_step == CardStep::none) // normal case
14,964,691✔
2222
            : is_active_next_turn(dst) // on-death activation
46,584,129✔
2223
           )
2224
        && (dst->attack_power()) // card can perform direct attack
46,584,129✔
2225
        ;
2226
}
2227

2228
    template<>
2229
inline bool skill_predicate<Skill::rush>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
×
2230
{
2231
    return (!src->m_rush_attempted)
×
2232
        && (dst->m_delay >= ((src->m_card->m_type == CardType::assault) && (dst->m_index < src->m_index) ? 2u : 1u));
×
2233
}
2234

2235
    template<>
2236
inline bool skill_predicate<Skill::weaken>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
38,447,906✔
2237
{
2238
    if(__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::ironwill], false) && dst->has_skill(Skill::armor))return false;
38,447,906✔
2239
    if (!dst->attack_power()) { return false; }
38,444,343✔
2240

2241
    // active player performs Weaken (normal case)
2242
    if (__builtin_expect((fd->tapi == src->m_player), true))
34,121,866✔
2243
    { return is_active_next_turn(dst); }
34,006,419✔
2244

2245
    // APN - On-Attacked/Death don't target the attacking card
2246

2247
    // inactive player performs Weaken (inverted case (on-death activation))
2248
    return will_activate_this_turn(dst);
115,447✔
2249
}
2250

2251
    template<>
2252
inline bool skill_predicate<Skill::sunder>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
17,545,221✔
2253
{
2254
    return skill_predicate<Skill::weaken>(fd, src, dst, s);
101✔
2255
}
2256

2257
    template<unsigned skill_id>
2258
inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2259
{ assert(false); }
2260

2261
    template<>
2262
inline void perform_skill<Skill::enfeeble>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
7,575,083✔
2263
{
2264
    dst->m_enfeebled += s.x;
7,575,083✔
2265
}
×
2266

2267
    template<>
2268
inline void perform_skill<Skill::enhance>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
6,376,355✔
2269
{
2270
    dst->m_enhanced_value[s.s + dst->m_primary_skill_offset[s.s]] += s.x;
6,376,355✔
2271
}
27✔
2272

2273
    template<>
2274
inline void perform_skill<Skill::evolve>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
386,163✔
2275
{
2276
    auto primary_s1 = dst->m_primary_skill_offset[s.s] + s.s;
386,163✔
2277
    auto primary_s2 = dst->m_primary_skill_offset[s.s2] + s.s2;
386,163✔
2278
    dst->m_primary_skill_offset[s.s] = primary_s2 - s.s;
386,163✔
2279
    dst->m_primary_skill_offset[s.s2] = primary_s1 - s.s2;
386,163✔
2280
    dst->m_evolved_skill_offset[primary_s1] = s.s2 - primary_s1;
386,163✔
2281
    dst->m_evolved_skill_offset[primary_s2] = s.s - primary_s2;
386,163✔
2282
}
×
2283

2284
    template<>
2285
inline void perform_skill<Skill::heal>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
8,402,420✔
2286
{
2287
    dst->add_hp(s.x);
8,402,420✔
2288

2289
    // Passive BGE: ZealotsPreservation
2290
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::zealotspreservation], false)
8,402,420✔
2291
            && (src->m_card->m_type == CardType::assault))
8,402,420✔
2292
    {
2293
        unsigned bge_value = (s.x + 1) / 2;
348✔
2294
        _DEBUG_MSG(1, "Zealot's Preservation: %s Protect %u on %s\n",
348✔
2295
                status_description(src).c_str(), bge_value,
2296
                status_description(dst).c_str());
348✔
2297
        dst->m_protected += bge_value;
348✔
2298
    }
2299
}
8,402,420✔
2300

2301
    template<>
2302
inline void perform_skill<Skill::jam>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
1,797,932✔
2303
{
2304
    dst->m_jammed = true;
1,797,932✔
2305
}
×
2306

2307
    template<>
2308
inline void perform_skill<Skill::mend>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,939✔
2309
{
2310
    dst->add_hp(s.x);
3,939✔
2311
}
×
2312

2313
    template<>
2314
inline void perform_skill<Skill::fortify>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,420,446✔
2315
{
2316
    dst->ext_hp(s.x);
1,762,785✔
2317
}
657,661✔
2318

2319
    template<>
2320
inline void perform_skill<Skill::siege>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,575,451✔
2321
{ 
2322
    //only structures can be sieged
2323
    _DEBUG_ASSERT(dst->m_card->m_type != CardType::assault);
2,575,451✔
2324
    _DEBUG_ASSERT(dst->m_card->m_type != CardType::commander);
2,575,451✔
2325
    unsigned siege_dmg = remove_absorption(fd,dst,s.x);
2,575,451✔
2326
    // structure should not have protect normally..., but let's allow it for barrier support
2327
    siege_dmg = safe_minus(siege_dmg, src->m_overloaded ? 0 : dst->m_protected);
2,575,451✔
2328
    remove_hp(fd, dst, siege_dmg);
2,575,451✔
2329
}
2,575,451✔
2330

2331
    template<>
2332
inline void perform_skill<Skill::mortar>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,332,110✔
2333
{
2334
    if (dst->m_card->m_type == CardType::structure)
3,332,110✔
2335
    {
2336
        perform_skill<Skill::siege>(fd, src, dst, s);
2,394,075✔
2337
    }
2338
    else
2339
    {
2340
        unsigned strike_dmg = remove_absorption(fd,dst,(s.x + 1) / 2 + dst->m_enfeebled);
938,035✔
2341
        strike_dmg = safe_minus(strike_dmg, src->m_overloaded ? 0 : dst->protected_value());
938,035✔
2342
        remove_hp(fd, dst, strike_dmg);
938,035✔
2343
    }
2344
}
3,332,110✔
2345

2346
    template<>
2347
inline void perform_skill<Skill::overload>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
240,085✔
2348
{
2349
    dst->m_overloaded = true;
240,085✔
2350
}
7✔
2351

2352
    template<>
2353
inline void perform_skill<Skill::protect>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
34,420,215✔
2354
{
2355
    dst->m_protected += s.x;
34,420,215✔
2356
}
409✔
2357

2358
    template<>
2359
inline void perform_skill<Skill::rally>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
11,885,913✔
2360
{
2361
    dst->m_temp_attack_buff += s.x;
11,885,913✔
2362
}
227,827✔
2363

2364
    template<>
2365
inline void perform_skill<Skill::enrage>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
9,908,811✔
2366
{
2367
    dst->m_enraged += s.x;
9,908,811✔
2368
    // Passive BGE: Furiosity
2369
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::furiosity], false)
9,908,811✔
2370
            && can_be_healed(dst))
9,908,811✔
2371
    {
2372
        unsigned bge_value = s.x;
6,687✔
2373
        _DEBUG_MSG(1, "Furiosity: %s Heals %s for %u\n",
6,687✔
2374
                status_description(src).c_str(),
2375
                status_description(dst).c_str(), bge_value);
6,687✔
2376
        dst->add_hp(bge_value);
6,687✔
2377
    }
2378
}
9,908,811✔
2379

2380
    template<>
2381
inline void perform_skill<Skill::entrap>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
5,647,032✔
2382
{
2383
    dst->m_entrapped += s.x;
5,647,032✔
2384
}
87,541✔
2385

2386
    template<>
2387
inline void perform_skill<Skill::rush>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
×
2388
{
2389
    dst->m_delay -= std::min(std::max(s.x, 1u), dst->m_delay);
×
2390
    if (dst->m_delay == 0)
×
2391
    {
2392
        check_and_perform_valor(fd, dst);
×
2393
        if(dst->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate)check_and_perform_summon(fd, dst);
×
2394
    }
2395
}
×
2396

2397
    template<>
2398
inline void perform_skill<Skill::strike>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
18,669,884✔
2399
{
2400
    unsigned strike_dmg = remove_absorption(fd,dst,s.x+ dst->m_enfeebled);
18,669,884✔
2401
    strike_dmg = safe_minus(strike_dmg , src->m_overloaded ? 0 : dst->protected_value());
18,669,884✔
2402
    remove_hp(fd, dst, strike_dmg);
18,669,884✔
2403
}
18,669,884✔
2404

2405
    template<>
2406
inline void perform_skill<Skill::weaken>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
10,202,072✔
2407
{
2408
    dst->m_temp_attack_buff -= (unsigned)std::min(s.x, dst->attack_power());
10,202,072✔
2409
}
×
2410

2411
    template<>
2412
inline void perform_skill<Skill::sunder>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,292,558✔
2413
{
2414
    dst->m_sundered = true;
3,292,558✔
2415
    perform_skill<Skill::weaken>(fd, src, dst, s);
3,226,354✔
2416
}
×
2417

2418
    template<>
2419
inline void perform_skill<Skill::mimic>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
556,795✔
2420
{
2421
    // collect all mimickable enemy skills
2422
    std::vector<const SkillSpec *> mimickable_skills;
556,795✔
2423
    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());
556,795✔
2424
    _DEBUG_MSG(2, " * Mimickable skills of %s\n", status_description(dst).c_str());
556,795✔
2425
    //include on activate/attacked
2426
    std::vector<std::vector<SkillSpec>> all;
556,795✔
2427
    all.emplace_back(dst->m_card->m_skills);
556,795✔
2428
    all.emplace_back(dst->m_card->m_skills_on_attacked);
556,795✔
2429
    for(std::vector<SkillSpec> & a : all)
1,670,385✔
2430
    {
2431
    for (const SkillSpec & ss: a)
2,782,775✔
2432
    {
2433
        // get skill
2434
        Skill::Skill skill_id = static_cast<Skill::Skill>(ss.id);
1,669,185✔
2435

2436
        // skip non-activation skills and Mimic (Mimic can't be mimicked)
2437
        if (!is_activation_skill(skill_id) || (skill_id == Skill::mimic))
2,394,258✔
2438
        { continue; }
944,112✔
2439

2440
        // skip mend for non-assault mimickers
2441
        if ((skill_id == Skill::mend || skill_id == Skill::fortify) && (src->m_card->m_type != CardType::assault))
725,073✔
2442
        { continue; }
1,200✔
2443

2444
        mimickable_skills.emplace_back(&ss);
723,873✔
2445
        _DEBUG_MSG(2, "  + %s\n", skill_description(fd->cards, ss).c_str());
1,669,185✔
2446
    }
2447
    }
2448

2449
    // select skill
2450
    unsigned mim_idx = 0;
556,795✔
2451
    switch (mimickable_skills.size())
556,795✔
2452
    {
2453
        case 0: assert(false); break;
×
2454
        case 1: break;
2455
        default: mim_idx = (fd->re() % mimickable_skills.size()); break;
165,261✔
2456
    }
2457
    // prepare & perform selected skill
2458
    const SkillSpec & mim_ss = *mimickable_skills[mim_idx];
556,795✔
2459
    Skill::Skill mim_skill_id = static_cast<Skill::Skill>(mim_ss.id);
556,795✔
2460
    auto skill_value = s.x + src->enhanced(mim_skill_id); //enhanced skill from mimic ?!?
556,795✔
2461
    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,};
556,795✔
2462
    _DEBUG_MSG(1, " * Mimicked skill: %s\n", skill_description(fd->cards, mimicked_ss).c_str());
556,795✔
2463
    skill_table[mim_skill_id](fd, src, mimicked_ss);
556,795✔
2464
}
556,795✔
2465

2466
    template<unsigned skill_id>
2467
inline unsigned select_fast(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
120,927,290✔
2468
{
2469
    if ((s.y == allfactions)
120,927,290✔
2470
            || fd->bg_effects[fd->tapi][PassiveBGE::metamorphosis]
19,235,191✔
2471
            || fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis])
140,007,845✔
2472
    {
2473
        auto pred = [fd, src, s](CardStatus* c) {
392,650,679✔
2474
            return(skill_predicate<skill_id>(fd, src, c, s));
340,046,873✔
2475
        };
2476
        return fd->make_selection_array(cards.begin(), cards.end(), pred);
102,000,597✔
2477
    }
2478
    else
2479
    {
2480
        auto pred = [fd, src, s](CardStatus* c) {
98,265,203✔
2481
            return ((c->m_card->m_faction == s.y || c->m_card->m_faction == progenitor) && skill_predicate<skill_id>(fd, src, c, s));
93,340,271✔
2482
        };
2483
        return fd->make_selection_array(cards.begin(), cards.end(), pred);
18,926,693✔
2484
    }
2485
}
2486

2487
    template<>
2488
inline unsigned select_fast<Skill::mend>(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
109,924✔
2489
{
2490
    fd->selection_array.clear();
109,924✔
2491
    bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
109,924✔
2492
    auto& assaults = fd->players[src->m_player]->assaults;
109,924✔
2493
    unsigned adj_size = 1 + (unsigned)(critical_reach);
109,924✔
2494
    unsigned host_idx = src->m_index;
109,924✔
2495
    unsigned from_idx = safe_minus(host_idx, adj_size);
109,924✔
2496
    unsigned till_idx = std::min(host_idx + adj_size, safe_minus(assaults.size(), 1));
214,395✔
2497
    for (; from_idx <= till_idx; ++ from_idx)
377,594✔
2498
    {
2499
        if (from_idx == host_idx) { continue; }
269,268✔
2500
        CardStatus* adj_status = &assaults[from_idx];
157,746✔
2501
        if (!is_alive(adj_status)) { continue; }
157,746✔
2502
        if (skill_predicate<Skill::mend>(fd, src, adj_status, s))
312,296✔
2503
        {
2504
            fd->selection_array.push_back(adj_status);
4,214✔
2505
        }
2506
    }
2507
    return fd->selection_array.size();
109,924✔
2508
}
2509

2510
    template<>
2511
inline unsigned select_fast<Skill::fortify>(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
1,196,480✔
2512
{
2513
    fd->selection_array.clear();
1,196,480✔
2514
    bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
1,196,480✔
2515
    auto& assaults = fd->players[src->m_player]->assaults;
1,196,480✔
2516
    unsigned adj_size = 1 + (unsigned)(critical_reach);
1,196,480✔
2517
    unsigned host_idx = src->m_index;
1,196,480✔
2518
    unsigned from_idx = safe_minus(host_idx, adj_size);
1,196,480✔
2519
    unsigned till_idx = std::min(host_idx + adj_size, safe_minus(assaults.size(), 1));
2,351,646✔
2520
    for (; from_idx <= till_idx; ++ from_idx)
4,241,265✔
2521
    {
2522
        if (from_idx == host_idx) { continue; }
3,059,621✔
2523
        CardStatus* adj_status = &assaults[from_idx];
1,848,305✔
2524
        if (!is_alive(adj_status)) { continue; }
1,848,305✔
2525
        if (skill_predicate<Skill::fortify>(fd, src, adj_status, s))
1,833,469✔
2526
        {
2527
            fd->selection_array.push_back(adj_status);
1,833,469✔
2528
        }
2529
    }
2530
    return fd->selection_array.size();
1,196,480✔
2531
}
2532
inline std::vector<CardStatus*>& skill_targets_hostile_assault(Field* fd, CardStatus* src)
42,333,460✔
2533
{
2534
    return(fd->players[opponent(src->m_player)]->assaults.m_indirect);
42,333,460✔
2535
}
2536

2537
inline std::vector<CardStatus*>& skill_targets_allied_assault(Field* fd, CardStatus* src)
76,498,105✔
2538
{
2539
    return(fd->players[src->m_player]->assaults.m_indirect);
76,498,105✔
2540
}
2541

2542
inline std::vector<CardStatus*>& skill_targets_hostile_structure(Field* fd, CardStatus* src)
3,402,129✔
2543
{
2544
    return(fd->players[opponent(src->m_player)]->structures.m_indirect);
3,402,129✔
2545
}
2546

2547
inline std::vector<CardStatus*>& skill_targets_allied_structure(Field* fd, CardStatus* src)
2548
{
2549
    return(fd->players[src->m_player]->structures.m_indirect);
2550
}
2551

2552
    template<unsigned skill>
2553
std::vector<CardStatus*>& skill_targets(Field* fd, CardStatus* src)
2554
{
2555
    std::cerr << "skill_targets: Error: no specialization for " << skill_names[skill] << "\n";
2556
    throw;
2557
}
2558

2559
template<> std::vector<CardStatus*>& skill_targets<Skill::enfeeble>(Field* fd, CardStatus* src)
10,164,996✔
2560
{ return(skill_targets_hostile_assault(fd, src)); }
10,164,996✔
2561

2562
template<> std::vector<CardStatus*>& skill_targets<Skill::enhance>(Field* fd, CardStatus* src)
14,253,262✔
2563
{ return(skill_targets_allied_assault(fd, src)); }
14,253,262✔
2564

2565
template<> std::vector<CardStatus*>& skill_targets<Skill::evolve>(Field* fd, CardStatus* src)
1,355,473✔
2566
{ return(skill_targets_allied_assault(fd, src)); }
1,355,473✔
2567

2568
template<> std::vector<CardStatus*>& skill_targets<Skill::heal>(Field* fd, CardStatus* src)
22,734,945✔
2569
{ return(skill_targets_allied_assault(fd, src)); }
22,734,945✔
2570

2571
template<> std::vector<CardStatus*>& skill_targets<Skill::jam>(Field* fd, CardStatus* src)
3,502,035✔
2572
{ return(skill_targets_hostile_assault(fd, src)); }
3,502,035✔
2573

2574
template<> std::vector<CardStatus*>& skill_targets<Skill::mend>(Field* fd, CardStatus* src)
109,924✔
2575
{ return(skill_targets_allied_assault(fd, src)); }
109,924✔
2576

2577
template<> std::vector<CardStatus*>& skill_targets<Skill::fortify>(Field* fd, CardStatus* src)
1,196,480✔
2578
{ return(skill_targets_allied_assault(fd, src)); }
1,196,480✔
2579

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

2583
template<> std::vector<CardStatus*>& skill_targets<Skill::protect>(Field* fd, CardStatus* src)
11,253,826✔
2584
{ return(skill_targets_allied_assault(fd, src)); }
11,253,826✔
2585

2586
template<> std::vector<CardStatus*>& skill_targets<Skill::rally>(Field* fd, CardStatus* src)
7,814,017✔
2587
{ return(skill_targets_allied_assault(fd, src)); }
7,814,017✔
2588

2589
template<> std::vector<CardStatus*>& skill_targets<Skill::enrage>(Field* fd, CardStatus* src)
12,851,490✔
2590
{ return(skill_targets_allied_assault(fd, src)); }
12,851,490✔
2591

2592
template<> std::vector<CardStatus*>& skill_targets<Skill::entrap>(Field* fd, CardStatus* src)
4,759,647✔
2593
{ return(skill_targets_allied_assault(fd, src)); }
4,759,647✔
2594

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

2598
template<> std::vector<CardStatus*>& skill_targets<Skill::siege>(Field* fd, CardStatus* src)
3,402,129✔
2599
{ return(skill_targets_hostile_structure(fd, src)); }
3,402,129✔
2600

2601
template<> std::vector<CardStatus*>& skill_targets<Skill::strike>(Field* fd, CardStatus* src)
14,473,866✔
2602
{ return(skill_targets_hostile_assault(fd, src)); }
866,911✔
2603

2604
template<> std::vector<CardStatus*>& skill_targets<Skill::sunder>(Field* fd, CardStatus* src)
5,122,623✔
2605
{ return(skill_targets_hostile_assault(fd, src)); }
5,122,623✔
2606

2607
template<> std::vector<CardStatus*>& skill_targets<Skill::weaken>(Field* fd, CardStatus* src)
8,217,333✔
2608
{ return(skill_targets_hostile_assault(fd, src)); }
8,217,333✔
2609

2610
template<> std::vector<CardStatus*>& skill_targets<Skill::mimic>(Field* fd, CardStatus* src)
852,607✔
2611
{ return(skill_targets_hostile_assault(fd, src)); }
852,607✔
2612

2613
    template<Skill::Skill skill_id>
2614
inline bool check_and_perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s, bool is_evadable
134,886,532✔
2615
#ifndef NQUEST
2616
        , bool & has_counted_quest
2617
#endif
2618
        )
2619
{
2620
    if (__builtin_expect(skill_check<skill_id>(fd, dst, src), true))
134,886,532✔
2621
    {
2622
#ifndef NQUEST
2623
        if (src->m_player == 0 && ! has_counted_quest)
2624
        {
2625
            fd->inc_counter(QuestType::skill_use, skill_id, dst->m_card->m_id);
2626
            has_counted_quest = true;
2627
        }
2628
#endif
2629
        if (is_evadable && (dst->m_evaded < dst->skill(Skill::evade)))
134,886,532✔
2630
        {
2631
            ++ dst->m_evaded;
16,361,538✔
2632
            _DEBUG_MSG(1, "%s %s on %s but it evades\n",
18,315,460✔
2633
                    status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2634
                    status_description(dst).c_str());
2635
            return(false);
16,361,538✔
2636
        }
2637
        _DEBUG_MSG(1, "%s %s on %s\n",
144,365,666✔
2638
                status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2639
                status_description(dst).c_str());
2640
        perform_skill<skill_id>(fd, src, dst, s);
118,524,994✔
2641
        if (s.c > 0)
118,524,994✔
2642
        {
2643
            src->m_skill_cd[skill_id] = s.c;
1,429,656✔
2644
        }
2645
        // Skill: Tribute
2646
        if (skill_check<Skill::tribute>(fd, dst, src)
138,963,494✔
2647
                // only activation helpful skills can be tributed (* except Evolve, Enhance, and Rush)
2648
                && is_activation_helpful_skill(s.id) && (s.id != Skill::evolve) && (s.id != Skill::enhance) && (s.id != Skill::rush)
20,438,500✔
2649
                && (dst->m_tributed < dst->skill(Skill::tribute))
7,727,631✔
2650
                && skill_check<skill_id>(fd, src, src))
106,471,579✔
2651
        {
2652
            ++ dst->m_tributed;
970,469✔
2653
            _DEBUG_MSG(1, "%s tributes %s back to %s\n",
970,469✔
2654
                    status_description(dst).c_str(), skill_short_description(fd->cards, s).c_str(),
2655
                    status_description(src).c_str());
2656
            perform_skill<skill_id>(fd, src, src, s);
970,469✔
2657
        }
2658
        return(true);
118,524,994✔
2659
    }
2660
    _DEBUG_MSG(1, "(CANCELLED) %s %s on %s\n",
×
2661
            status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2662
            status_description(dst).c_str());
2663
    return(false);
2664
}
2665
template<enum CardType::CardType def_cardtype>
2666
void perform_bge_devour(Field* fd, CardStatus* att_status, CardStatus* def_status)
16,536,535✔
2667
{
2668
// Passive BGE: Devour
2669
                unsigned leech_value;
2670
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::devour], false)
10,531,662✔
2671
                        && ((leech_value = att_status->skill(Skill::leech) + att_status->skill(Skill::refresh)) > 0)
46,966✔
2672
                        && (def_cardtype == CardType::assault))
10,531,662✔
2673
                {
2674
                    unsigned bge_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::devour] > 0)
1,814✔
2675
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::devour]
907✔
2676
                        : 4;
2677
                    unsigned bge_value = (leech_value - 1) / bge_denominator + 1;
907✔
2678
                    if (! att_status->m_sundered)
907✔
2679
                    {
2680
                        _DEBUG_MSG(1, "Devour: %s gains %u attack\n",
2,670✔
2681
                                status_description(att_status).c_str(), bge_value);
2682
                        att_status->m_perm_attack_buff += bge_value;
890✔
2683
                    }
2684
                    _DEBUG_MSG(1, "Devour: %s extends max hp / heals itself for %u\n",
2,721✔
2685
                            status_description(att_status).c_str(), bge_value);
2686
                    att_status->ext_hp(bge_value);
907✔
2687
                }
2688
}
10,531,662✔
2689
template<enum CardType::CardType def_cardtype>
2690
void perform_bge_heroism(Field* fd, CardStatus* att_status, CardStatus* def_status)
16,536,535✔
2691
{
2692
// Passive BGE: Heroism
2693
                unsigned valor_value;
2694
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::heroism], false)
16,536,535✔
2695
                        && ((valor_value = att_status->skill(Skill::valor) + att_status->skill(Skill::bravery)) > 0)
23,619✔
2696
                        && !att_status->m_sundered
2,492✔
2697
                        && (def_cardtype == CardType::assault) && (def_status->m_hp <= 0))
10,534,139✔
2698
                {
2699
                    _DEBUG_MSG(1, "Heroism: %s gain %u attack\n",
2,154✔
2700
                            status_description(att_status).c_str(), valor_value);
2701
                    att_status->m_perm_attack_buff += valor_value;
718✔
2702
                }
2703
                }
10,531,662✔
2704
/**
2705
 * @brief Perform Mark skill, increases Mark-counter.
2706
 * 
2707
 * @param fd     Field
2708
 * @param att_status Attacker
2709
 * @param def_status Defender
2710
 */
2711
void perform_mark(Field* fd, CardStatus* att_status, CardStatus* def_status)
16,536,535✔
2712
{
2713
    // Bug fix? 2023-04-03 mark should come after berserk
2714
    // Increase Mark-counter
2715
    unsigned mark_base = att_status->skill(Skill::mark);
16,536,535✔
2716
    if(mark_base && skill_check<Skill::mark>(fd,att_status,def_status)) {
16,536,535✔
2717
        _DEBUG_MSG(1, "%s marks %s for %u\n",
35,226✔
2718
                status_description(att_status).c_str(), status_description(def_status).c_str(), mark_base);
35,226✔
2719
        def_status->m_marked += mark_base;
35,226✔
2720
    }
2721
}
16,536,535✔
2722
/**
2723
 * @brief Perform Berserk skill and Passive BGE: EnduringRage
2724
 * 
2725
 * Increases attack by berserk value if not sundered.
2726
 * 
2727
 * @param fd     Field
2728
 * @param att_status Attacker
2729
 * @param def_status Defender
2730
 */
2731
void perform_berserk(Field* fd, CardStatus* att_status, CardStatus* def_status)
16,536,535✔
2732
{
2733
            // Skill: Berserk
2734
            unsigned berserk_value = att_status->skill(Skill::berserk);
16,536,535✔
2735
            if ( !att_status->m_sundered && (berserk_value > 0))
16,536,535✔
2736
            {
2737
                // perform_skill_berserk
2738
                att_status->m_perm_attack_buff += berserk_value;
7,102,567✔
2739
#ifndef NQUEST
2740
                if (att_status->m_player == 0)
2741
                {
2742
                    fd->inc_counter(QuestType::skill_use, Skill::berserk);
2743
                }
2744
#endif
2745

2746
                // Passive BGE: EnduringRage
2747
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::enduringrage], false))
7,102,567✔
2748
                {
2749
                    unsigned bge_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::enduringrage] > 0)
21,776✔
2750
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::enduringrage]
21,776✔
2751
                        : 2;
21,776✔
2752
                    unsigned bge_value = (berserk_value - 1) / bge_denominator + 1;
21,776✔
2753
                    _DEBUG_MSG(1, "EnduringRage: %s heals and protects itself for %u\n",
21,776✔
2754
                            status_description(att_status).c_str(), bge_value);
21,776✔
2755
                    att_status->add_hp(bge_value);
21,776✔
2756
                    att_status->m_protected += bge_value;
21,776✔
2757
                }
2758
            }
2759

2760

2761
}
16,536,535✔
2762
/**
2763
 * @brief Poisoning of a attacking card
2764
 * 
2765
 * @param fd  Field
2766
 * @param att_status Attacking card
2767
 * @param def_status Defending card
2768
 */
2769
void perform_poison(Field* fd, CardStatus* att_status, CardStatus* def_status)
20,170,018✔
2770
{
2771
    if(__builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
20,170,018✔
2772
    {
2773
        if (is_alive(att_status) && def_status->has_skill(Skill::poison))
20,170,018✔
2774
        {
2775
            unsigned poison_value = def_status->skill(Skill::poison);
10,283✔
2776
            _DEBUG_MSG(1, "%s gets poisoned by %u from %s\n",
10,283✔
2777
                            status_description(att_status).c_str(), poison_value,
2778
                            status_description(def_status).c_str());
10,283✔
2779
            att_status->m_poisoned += poison_value; 
10,283✔
2780
        }
2781
    }
2782
}
20,170,018✔
2783

2784
void perform_corrosive(Field* fd, CardStatus* att_status, CardStatus* def_status)
20,170,018✔
2785
{
2786
    // Skill: Corrosive
2787
    unsigned corrosive_value = def_status->skill(Skill::corrosive);
20,170,018✔
2788
    if (corrosive_value > att_status->m_corroded_rate)
20,170,018✔
2789
    {
2790
        // perform_skill_corrosive
2791
        _DEBUG_MSG(1, "%s corrodes %s by %u\n",
45,122✔
2792
                status_description(def_status).c_str(),
2793
                status_description(att_status).c_str(), corrosive_value);
45,122✔
2794
        att_status->m_corroded_rate = corrosive_value;
45,122✔
2795
    }
2796
}
20,170,018✔
2797

2798
template<enum CardType::CardType def_cardtype>
2799
void perform_counter(Field* fd, CardStatus* att_status, CardStatus* def_status)
20,883,382✔
2800
{
2801
            // Enemy Skill: Counter
2802
            if (def_status->has_skill(Skill::counter))
20,883,382✔
2803
            {
2804
                // perform_skill_counter
2805
                unsigned counter_dmg(counter_damage(fd, att_status, def_status));
2,098,382✔
2806
#ifndef NQUEST
2807
                if (def_status->m_player == 0)
2808
                {
2809
                    fd->inc_counter(QuestType::skill_use, Skill::counter);
2810
                    fd->inc_counter(QuestType::skill_damage, Skill::counter, 0, counter_dmg);
2811
                }
2812
#endif
2813
                _DEBUG_MSG(1, "%s takes %u counter damage from %s\n",
3,145,756✔
2814
                        status_description(att_status).c_str(), counter_dmg,
2815
                        status_description(def_status).c_str());
2816
                remove_hp(fd, att_status, counter_dmg);
2,098,382✔
2817
                prepend_on_death(fd);
2,098,382✔
2818
                resolve_skill(fd);
2,098,382✔
2819

2820
                // Passive BGE: Counterflux
2821
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::counterflux], false)
2,098,382✔
2822
                        && (def_cardtype == CardType::assault) && is_alive(def_status))
1,247,199✔
2823
                {
2824
                    unsigned flux_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::counterflux] > 0)
8,318✔
2825
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::counterflux]
4,159✔
2826
                        : 4;
2827
                    unsigned flux_value = (def_status->skill(Skill::counter) - 1) / flux_denominator + 1;
4,159✔
2828
                    _DEBUG_MSG(1, "Counterflux: %s heals itself and berserks for %u\n",
12,477✔
2829
                            status_description(def_status).c_str(), flux_value);
2830
                    def_status->add_hp(flux_value);
4,159✔
2831
                    if (! def_status->m_sundered)
4,159✔
2832
                    { def_status->m_perm_attack_buff += flux_value; }
3,767✔
2833
                }
2834
            }
2835
}
20,883,382✔
2836
bool check_and_perform_enhance(Field* fd, CardStatus* src, bool early)
154,385,570✔
2837
{
2838
      if(!is_active(src))return false; // active
154,385,570✔
2839
      if(!src->has_skill(Skill::enhance))return false; // enhance Skill
96,288,050✔
2840
      for(auto ss : src->m_card->m_skills)
113,284,704✔
2841
      {
2842
          if(ss.id != Skill::enhance)continue;
84,963,378✔
2843
          if(early ^ (ss.s == Skill::allegiance || ss.s == Skill::absorb ||ss.s == Skill::stasis || ss.s == Skill::bravery))continue; //only specified skills are 'early'
28,849,548✔
2844
          skill_table[ss.id](fd,src,ss);
14,160,663✔
2845
      }
2846
      return true;
28,321,326✔
2847
}
2848
bool check_and_perform_early_enhance(Field* fd, CardStatus* src)
77,192,785✔
2849
{
2850
      return check_and_perform_enhance(fd,src,true);
77,192,785✔
2851
}
2852
bool check_and_perform_later_enhance(Field* fd, CardStatus* src)
77,192,785✔
2853
{
2854
      return check_and_perform_enhance(fd,src,false);
77,192,785✔
2855
}
2856
/**
2857
 * @brief Perform drain skill
2858
 * 
2859
 * @param fd Field
2860
 * @param att_status Attacker status
2861
 * @param def_status Defender status
2862
 * @param att_dmg damage dealt by attacker
2863
 */
2864
template<>
2865
void PerformAttack::perform_swipe_drain<CardType::assault>(Field* fd, CardStatus* att_status, CardStatus* def_status,unsigned att_dmg) {
14,383,023✔
2866
        unsigned swipe_value = att_status->skill(Skill::swipe);
14,383,023✔
2867
        unsigned drain_value = att_status->skill(Skill::drain);
14,383,023✔
2868
        if (swipe_value || drain_value)
14,383,023✔
2869
        {
2870
            Storage<CardStatus>& def_assaults(fd->tip->assaults);
1,345,605✔
2871
            bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
1,345,605✔
2872
            auto drain_total_dmg = att_dmg;
1,345,605✔
2873
            unsigned adj_size = 1 + (unsigned)(critical_reach);
1,345,605✔
2874
            unsigned host_idx = def_status->m_index;
1,345,605✔
2875
            unsigned from_idx = safe_minus(host_idx, adj_size);
1,345,605✔
2876
            unsigned till_idx = std::min(host_idx + adj_size, safe_minus(def_assaults.size(), 1));
2,267,072✔
2877
            for (; from_idx <= till_idx; ++ from_idx)
3,734,871✔
2878
            {
2879
                if (from_idx == host_idx) { continue; }
2,389,266✔
2880
                CardStatus* adj_status = &def_assaults[from_idx];
1,043,661✔
2881
                if (!is_alive(adj_status)) { continue; }
1,043,661✔
2882
                _DEBUG_ASSERT(adj_status->m_card->m_type == CardType::assault); //only assaults
843,696✔
2883
                //unsigned swipe_dmg = safe_minus(
2884
                //    swipe_value + drain_value + def_status->m_enfeebled,
2885
                //    def_status->protected_value());
2886
                unsigned remaining_dmg = remove_absorption(fd,adj_status,swipe_value + drain_value + adj_status->m_enfeebled);
843,696✔
2887
                remaining_dmg = safe_minus(remaining_dmg,adj_status->protected_value());
843,696✔
2888
                _DEBUG_MSG(1, "%s swipes %s for %u damage\n",
843,696✔
2889
                        status_description(att_status).c_str(),
2890
                        status_description(adj_status).c_str(), remaining_dmg);
843,696✔
2891

2892
                remove_hp(fd, adj_status, remaining_dmg);
843,696✔
2893
                drain_total_dmg += remaining_dmg;
843,696✔
2894
            }
2895
            if (drain_value && skill_check<Skill::drain>(fd, att_status, nullptr))
1,345,605✔
2896
            {
2897
                _DEBUG_MSG(1, "%s drains %u hp\n",
61,409✔
2898
                        status_description(att_status).c_str(), drain_total_dmg);
61,409✔
2899
                att_status->add_hp(drain_total_dmg);
61,409✔
2900
            }
2901
            prepend_on_death(fd);
1,345,605✔
2902
            resolve_skill(fd);
1,345,605✔
2903
        }
2904
}
14,383,023✔
2905
/**
2906
 * @brief Perform Hunt skill
2907
 * 
2908
 * @param fd Field 
2909
 * @param att_status Attacker status
2910
 * @param def_status Defender status
2911
 */
2912
void perform_hunt(Field* fd, CardStatus* att_status, CardStatus* def_status) {
20,895,351✔
2913
    unsigned hunt_value = att_status->skill(Skill::hunt);
20,895,351✔
2914
        if(hunt_value)
20,895,351✔
2915
        {
2916
            CardStatus* hunted_status{select_first_enemy_assault(fd)};
32,562✔
2917
            if (hunted_status != nullptr)
32,562✔
2918
            {
2919
                unsigned remaining_dmg = remove_absorption(fd,hunted_status,hunt_value + hunted_status->m_enfeebled);
22,496✔
2920
                remaining_dmg = safe_minus(remaining_dmg,hunted_status->protected_value());
22,496✔
2921
                _DEBUG_MSG(1, "%s hunts %s for %u damage\n",
22,496✔
2922
                        status_description(att_status).c_str(),
2923
                        status_description(hunted_status).c_str(), remaining_dmg);
22,496✔
2924

2925
                remove_hp(fd, hunted_status, remaining_dmg);
22,496✔
2926

2927
                prepend_on_death(fd);
22,496✔
2928
                resolve_skill(fd);
22,496✔
2929
            }
2930
        }
2931
}
20,895,351✔
2932
/**
2933
 * @brief Perform Subdue skill
2934
 * 
2935
 * @param fd Field
2936
 * @param att_status Attacker status
2937
 * @param def_status Defender status
2938
 */
2939
void perform_subdue(Field* fd, CardStatus* att_status, CardStatus* def_status)
22,215,880✔
2940
{
2941
                // Skill: Subdue
2942
                unsigned subdue_value = def_status->skill(Skill::subdue);
22,215,880✔
2943
                if (__builtin_expect(subdue_value, false))
22,215,880✔
2944
                {
2945
                    _DEBUG_MSG(1, "%s subdues %s by %u\n",
848,510✔
2946
                            status_description(def_status).c_str(),
2947
                            status_description(att_status).c_str(), subdue_value);
848,510✔
2948
                    att_status->m_subdued += subdue_value;
848,510✔
2949
                    //fix negative attack
2950
                    if(att_status->calc_attack_power()<0)
848,510✔
2951
                    {
2952
                        att_status->m_temp_attack_buff -= att_status->calc_attack_power();
3,340✔
2953
                    }
2954
                    if (att_status->m_hp > att_status->max_hp())
955,395✔
2955
                    {
2956
                        _DEBUG_MSG(1, "%s loses %u HP due to subdue (max hp: %u)\n",
138,051✔
2957
                                status_description(att_status).c_str(),
2958
                                (att_status->m_hp - att_status->max_hp()),
2959
                                att_status->max_hp());
138,051✔
2960
                        att_status->m_hp = att_status->max_hp();
241,435✔
2961
                    }
2962
                }
2963
}
22,215,880✔
2964
bool check_and_perform_valor(Field* fd, CardStatus* src)
13,957,169✔
2965
{
2966
    unsigned valor_value = src->skill(Skill::valor);
13,957,169✔
2967
    if (valor_value && !src->m_sundered && skill_check<Skill::valor>(fd, src, nullptr))
27,914,338✔
2968
    {
2969
        _DEBUG_ASSERT(src->m_card->m_type == CardType::assault); //only assaults
45,448✔
2970
        unsigned opponent_player = opponent(src->m_player);
45,448✔
2971
        const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ?
45,448✔
2972
            &fd->players[opponent_player]->assaults[src->m_index] :
20,466✔
2973
            nullptr;
45,448✔
2974
        if (dst == nullptr || dst->m_hp <= 0)
20,466✔
2975
        {
2976
            _DEBUG_MSG(1, "%s loses Valor (no blocker)\n", status_description(src).c_str());
24,982✔
2977
            return false;
24,982✔
2978
        }
2979
        else if (dst->attack_power() <= src->attack_power())
20,466✔
2980
        {
2981
            _DEBUG_MSG(1, "%s loses Valor (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str());
2,183✔
2982
            return false;
2,183✔
2983
        }
2984
#ifndef NQUEST
2985
        if (src->m_player == 0)
2986
        {
2987
            fd->inc_counter(QuestType::skill_use, Skill::valor);
2988
        }
2989
#endif
2990
        _DEBUG_MSG(1, "%s activates Valor %u\n", status_description(src).c_str(), valor_value);
18,283✔
2991
        src->m_perm_attack_buff += valor_value;
18,283✔
2992
        return true;
18,283✔
2993
    }
2994
    return false;
2995
}
2996

2997
bool check_and_perform_bravery(Field* fd, CardStatus* src)
59,974,584✔
2998
{
2999
    unsigned bravery_value = src->skill(Skill::bravery);
59,974,584✔
3000
    if (bravery_value && !src->m_sundered && skill_check<Skill::bravery>(fd, src, nullptr))
119,949,168✔
3001
    {
3002
        _DEBUG_ASSERT(src->m_card->m_type == CardType::assault); //only assaults
1,975,844✔
3003
        unsigned opponent_player = opponent(src->m_player);
1,975,844✔
3004
        const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ?
1,975,844✔
3005
            &fd->players[opponent_player]->assaults[src->m_index] :
1,095,455✔
3006
            nullptr;
1,975,844✔
3007
        if (dst == nullptr || dst->m_hp <= 0)
1,095,455✔
3008
        {
3009
            _DEBUG_MSG(1, "%s loses Bravery (no blocker)\n", status_description(src).c_str());
880,389✔
3010
            return false;
880,389✔
3011
        }
3012
        else if (dst->attack_power() <= src->attack_power())
1,095,455✔
3013
        {
3014
            _DEBUG_MSG(1, "%s loses Bravery (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str());
801,350✔
3015
            return false;
801,350✔
3016
        }
3017
#ifndef NQUEST
3018
        if (src->m_player == 0)
3019
        {
3020
            fd->inc_counter(QuestType::skill_use, Skill::bravery);
3021
        }
3022
#endif
3023
        _DEBUG_MSG(1, "%s activates Bravery %u\n", status_description(src).c_str(), bravery_value);
294,105✔
3024
        src->m_perm_attack_buff += bravery_value;
294,105✔
3025

3026
        //BGE: superheroism
3027
        if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::superheroism], false))
294,105✔
3028
        {
3029
            unsigned bge_value = bravery_value * fd->bg_effects[fd->tapi][PassiveBGE::superheroism];
997✔
3030
            const SkillSpec ss_heal{Skill::heal, bge_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, true, 0,};
997✔
3031
            _DEBUG_MSG(1, "%s activates SuperHeroism: %s\n", status_description(src).c_str(),
997✔
3032
                    skill_description(fd->cards, ss_heal).c_str());
997✔
3033
            //fd->skill_queue.emplace(fd->skill_queue.begin()+0, src, ss_heal);
3034
            skill_table[Skill::heal](fd,src,ss_heal); //Probably better to perform the skill direct instead of queuing it
997✔
3035
        }
3036

3037
        return true;
294,105✔
3038
    }
3039
    return false;
3040
}
3041

3042
bool check_and_perform_inhibit(Field* fd, CardStatus* att_status,CardStatus* def_status)
32,783,200✔
3043
{
3044
      unsigned inhibit_value = att_status->skill(Skill::inhibit);
32,783,200✔
3045
      if (inhibit_value > def_status->m_inhibited && skill_check<Skill::inhibit>(fd, att_status, def_status))
35,594,927✔
3046
      {
3047
        _DEBUG_MSG(1, "%s inhibits %s by %u\n",
2,811,726✔
3048
                status_description(att_status).c_str(),
3049
                status_description(def_status).c_str(), inhibit_value);
2,811,726✔
3050
        def_status->m_inhibited = inhibit_value;
2,811,726✔
3051
        return true;
2,811,726✔
3052
      }
3053
      return false;
3054
}
3055
bool check_and_perform_sabotage(Field* fd, CardStatus* att_status, CardStatus* def_status)
32,783,200✔
3056
{
3057
    unsigned sabotage_value = att_status->skill(Skill::sabotage);
32,783,200✔
3058
    if (sabotage_value > def_status->m_sabotaged && skill_check<Skill::sabotage>(fd, att_status, def_status))
33,401,543✔
3059
    {
3060
        _DEBUG_MSG(1, "%s sabotages %s by %u\n",
618,343✔
3061
                status_description(att_status).c_str(),
3062
                status_description(def_status).c_str(), sabotage_value);
618,343✔
3063
        def_status->m_sabotaged = sabotage_value;
618,343✔
3064
        return true;
618,343✔
3065
    }
3066
    return false;
3067
}
3068
bool check_and_perform_disease(Field* fd, CardStatus* att_status,CardStatus* def_status)
32,783,200✔
3069
{
3070
    unsigned disease_base = att_status->skill(Skill::disease);
32,783,200✔
3071
    if(disease_base && skill_check<Skill::disease>(fd, att_status, def_status)) {
32,948,967✔
3072
        _DEBUG_MSG(1, "%s diseases %s for %u\n",
165,767✔
3073
        status_description(att_status).c_str(), status_description(def_status).c_str(), disease_base);
165,767✔
3074
        def_status->m_diseased += disease_base;
165,767✔
3075
        return true;
165,767✔
3076
    }
3077
    return false;
3078
}
3079

3080
CardStatus* check_and_perform_summon(Field* fd, CardStatus* src)
13,131,301✔
3081
{
3082
    unsigned summon_card_id = src->m_card->m_skill_value[Skill::summon];
13,131,301✔
3083
    if (summon_card_id)
13,131,301✔
3084
    {
3085
        const Card* summoned_card(fd->cards.by_id(summon_card_id));
3,290,399✔
3086
        _DEBUG_MSG(1, "%s summons %s\n", status_description(src).c_str(), summoned_card->m_name.c_str());
3,290,399✔
3087
        CardStatus* summoned_status = nullptr;
3,290,399✔
3088
        switch (summoned_card->m_type)
3,290,399✔
3089
        {
3090
            case CardType::assault:
1,183,105✔
3091
                summoned_status = PlayCard(summoned_card, fd, src->m_player, src).op<CardType::assault>(true);
1,183,105✔
3092
                return summoned_status;
1,183,105✔
3093
            case CardType::structure:
2,107,294✔
3094
                summoned_status = PlayCard(summoned_card, fd, src->m_player, src).op<CardType::structure>(true);
2,107,294✔
3095
                return summoned_status;
2,107,294✔
3096
            default:
×
3097
                _DEBUG_MSG(0, "Unknown card type: #%u %s: %u\n",
×
3098
                        summoned_card->m_id, card_description(fd->cards, summoned_card).c_str(),
3099
                        summoned_card->m_type);
×
3100
                _DEBUG_ASSERT(false);
×
3101
                __builtin_unreachable();
3102
        }
3103
    }
3104
    return nullptr;
3105
}
3106

3107

3108
    template<Skill::Skill skill_id>
3109
size_t select_targets(Field* fd, CardStatus* tsrc, const SkillSpec& s)
121,366,783✔
3110
{
3111
    size_t n_candidates;
3112
    CardStatus* src;
3113
    if(fd->fixes[Fix::revenge_on_death] && s.s2 == Skill::revenge)
121,366,783✔
3114
    {
3115
            _DEBUG_MSG(2,"FIX ON DEATH REVENGE SELECTION")
35,114✔
3116
            src = &fd->players[(tsrc->m_player+1)%2]->commander; // selection like enemy commander
35,114✔
3117
    }
35,114✔
3118
    else
3119
    {
3120
            src = tsrc;
3121
    }
3122
    switch (skill_id)
3123
    {
3124
        case Skill::mortar:
3,176,977✔
3125
            n_candidates = select_fast<Skill::siege>(fd, src, skill_targets<Skill::siege>(fd, src), s);
3,176,977✔
3126
            if (n_candidates == 0)
3,176,977✔
3127
            {
3128
                n_candidates = select_fast<Skill::strike>(fd, src, skill_targets<Skill::strike>(fd, src), s);
866,911✔
3129
            }
3130
            break;
3131

3132
        default:
118,189,806✔
3133
            n_candidates = select_fast<skill_id>(fd, src, skill_targets<skill_id>(fd, src), s);
118,189,806✔
3134
            break;
3135
    }
3136

3137
    // (false-loop)
3138
    unsigned n_selected = n_candidates;
122,066,385✔
3139
    do
121,366,783✔
3140
    {
3141
        // no candidates
3142
        if (n_candidates == 0)
119,056,717✔
3143
        { break; }
3144

3145
        // show candidates (debug)
3146
        _DEBUG_SELECTION("%s", skill_names[skill_id].c_str());
75,210,593✔
3147

3148
        // analyze targets count / skill
3149
        unsigned n_targets = s.n > 0 ? s.n : 1;
74,053,557✔
3150
        if (s.all || n_targets >= n_candidates || skill_id == Skill::mend || skill_id == Skill::fortify)  // target all or mend
74,053,557✔
3151
        { break; }
3152

3153
        // shuffle & trim
3154
        for (unsigned i = 0; i < n_targets; ++i)
40,752,338✔
3155
        {
3156
            std::swap(fd->selection_array[i], fd->selection_array[fd->rand(i, n_candidates - 1)]);
20,571,555✔
3157
        }
3158
        fd->selection_array.resize(n_targets);
20,180,783✔
3159
        if (n_targets > 1)
20,180,783✔
3160
        {
3161
            std::sort(fd->selection_array.begin(), fd->selection_array.end(),
264,476✔
3162
                    [](const CardStatus * a, const CardStatus * b) { return a->m_index < b->m_index; });
649,045✔
3163
        }
3164
        n_selected = n_targets;
3165

3166
    } while (false); // (end)
3167

3168
    return n_selected;
121,366,783✔
3169
}
3170

3171
    template<Skill::Skill skill_id>
3172
void perform_targetted_allied_fast(Field* fd, CardStatus* src, const SkillSpec& s)
76,492,407✔
3173
{
3174
    select_targets<skill_id>(fd, src, s);
76,492,407✔
3175
    unsigned num_inhibited = 0;
76,492,407✔
3176
#ifndef NQUEST
3177
    bool has_counted_quest = false;
3178
#endif
3179
    bool src_overloaded = src->m_overloaded;
76,492,407✔
3180
    std::vector<CardStatus*> selection_array = fd->selection_array;
76,492,407✔
3181
    for (CardStatus * dst: selection_array)
160,038,098✔
3182
    {
3183
        if (dst->m_inhibited > 0 && !src_overloaded)
83,545,691✔
3184
        {
3185
            _DEBUG_MSG(1, "%s %s on %s but it is inhibited\n",
5,309,971✔
3186
                    status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
3187
                    status_description(dst).c_str());
3188
            -- dst->m_inhibited;
4,844,889✔
3189
            ++ num_inhibited;
4,844,889✔
3190
            continue;
4,844,889✔
3191
        }
4,844,889✔
3192
        check_and_perform_skill<skill_id>(fd, src, dst, s, false
78,700,802✔
3193
#ifndef NQUEST
3194
                , has_counted_quest
3195
#endif
3196
                );
3197
    }
3198

3199
    // Passive BGE: Divert
3200
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::divert], false)
76,492,407✔
3201
            && (num_inhibited > 0))
76,492,407✔
3202
    {
3203
        SkillSpec diverted_ss = s;
5,644✔
3204
        diverted_ss.y = allfactions;
5,644✔
3205
        diverted_ss.n = 1;
5,644✔
3206
        diverted_ss.all = false;
5,644✔
3207
        for (unsigned i = 0; i < num_inhibited; ++ i)
11,342✔
3208
        {
3209
            select_targets<skill_id>(fd, &fd->tip->commander, diverted_ss);
5,698✔
3210
            std::vector<CardStatus*> selection_array = fd->selection_array;
5,698✔
3211
            for (CardStatus * dst: selection_array)
9,545✔
3212
            {
3213
                if (dst->m_inhibited > 0)
3,847✔
3214
                {
3215
                    _DEBUG_MSG(1, "%s %s (Diverted) on %s but it is inhibited\n",
762✔
3216
                            status_description(src).c_str(), skill_short_description(fd->cards, diverted_ss).c_str(),
3217
                            status_description(dst).c_str());
3218
                    -- dst->m_inhibited;
254✔
3219
                    continue;
254✔
3220
                }
254✔
3221
                _DEBUG_MSG(1, "%s %s (Diverted) on %s\n",
10,779✔
3222
                        status_description(src).c_str(), skill_short_description(fd->cards, diverted_ss).c_str(),
3223
                        status_description(dst).c_str());
3224
                perform_skill<skill_id>(fd, src, dst, diverted_ss);
3,620✔
3225
            }
3226
        }
3227
    }
3228
}
76,492,407✔
3229

3230
void perform_targetted_allied_fast_rush(Field* fd, CardStatus* src, const SkillSpec& s)
×
3231
{
3232
    if (src->m_card->m_type == CardType::commander)
×
3233
    {  // Passive BGE skills are casted as by commander
3234
        perform_targetted_allied_fast<Skill::rush>(fd, src, s);
×
3235
        return;
×
3236
    }
3237
    if (src->m_rush_attempted)
×
3238
    {
3239
        _DEBUG_MSG(2, "%s does not check Rush again.\n", status_description(src).c_str());
×
3240
        return;
×
3241
    }
3242
    _DEBUG_MSG(1, "%s attempts to activate Rush.\n", status_description(src).c_str());
×
3243
    perform_targetted_allied_fast<Skill::rush>(fd, src, s);
×
3244
    src->m_rush_attempted = true;
×
3245
}
3246

3247
    template<Skill::Skill skill_id>
3248
void perform_targetted_hostile_fast(Field* fd, CardStatus* src, const SkillSpec& s)
44,868,678✔
3249
{
3250
    select_targets<skill_id>(fd, src, s);
44,868,678✔
3251
    std::vector<CardStatus *> paybackers;
44,868,678✔
3252
#ifndef NQUEST
3253
    bool has_counted_quest = false;
3254
#endif
3255
    const bool has_turningtides = (fd->bg_effects[fd->tapi][PassiveBGE::turningtides] && (skill_id == Skill::weaken || skill_id == Skill::sunder));
44,868,678✔
3256
    unsigned turningtides_value(0), old_attack(0);
44,868,678✔
3257

3258
    // apply skill to each target(dst)
3259
    unsigned selection_array_len = fd->selection_array.size();
44,868,678✔
3260
    std::vector<CardStatus*> selection_array = fd->selection_array;
44,868,678✔
3261
    for (CardStatus * dst: selection_array)
101,037,893✔
3262
    {
3263
        // TurningTides
3264
        if (__builtin_expect(has_turningtides, false))
15,247,205✔
3265
        {
3266
            old_attack = dst->attack_power();
11,324✔
3267
        }
3268

3269
        // check & apply skill to target(dst)
3270
        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))
56,726,010✔
3271
#ifndef NQUEST
3272
                    , has_counted_quest
3273
#endif
3274
                    ))
3275
        {
3276
            // TurningTides: get max attack decreasing
3277
            if (__builtin_expect(has_turningtides, false))
10,092,938✔
3278
            {
3279
                turningtides_value = std::max(turningtides_value, safe_minus(old_attack, dst->attack_power()));
26,036✔
3280
            }
3281

3282
            // Payback/Revenge: collect paybackers/revengers
3283
            unsigned payback_value = dst->skill(Skill::payback) + dst->skill(Skill::revenge);
39,807,677✔
3284
            if ((s.id != Skill::mimic) && (dst->m_paybacked < payback_value) && skill_check<Skill::payback>(fd, dst, src))
39,807,677✔
3285
            {
3286
                paybackers.reserve(selection_array_len);
1,067,115✔
3287
                paybackers.push_back(dst);
1,067,115✔
3288
            }
3289
        }
3290
    }
3291

3292
    // apply TurningTides
3293
    if (__builtin_expect(has_turningtides, false) && (turningtides_value > 0))
13,339,956✔
3294
    {
3295
        SkillSpec ss_rally{Skill::rally, turningtides_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, s.all, 0,};
8,511✔
3296
        _DEBUG_MSG(1, "TurningTides %u!\n", turningtides_value);
17,022✔
3297
        perform_targetted_allied_fast<Skill::rally>(fd, &fd->players[src->m_player]->commander, ss_rally);
8,511✔
3298
    }
3299

3300
    prepend_on_death(fd);  // skills
44,868,678✔
3301

3302
    // Payback/Revenge
3303
    for (CardStatus * pb_status: paybackers)
45,935,793✔
3304
    {
3305
        turningtides_value = 0;
1,067,115✔
3306

3307
        // apply Revenge
3308
        if (pb_status->skill(Skill::revenge))
1,067,115✔
3309
        {
3310
            unsigned revenged_count(0);
3311
            for (unsigned case_index(0); case_index < 3; ++ case_index)
4,266,356✔
3312
            {
3313
                CardStatus * target_status;
3314
#ifndef NDEBUG
3315
                const char * target_desc;
3316
#endif
3317
                switch (case_index)
3,199,767✔
3318
                {
3319
                    // revenge to left
3320
                    case 0:
1,066,589✔
3321
                        if (!(target_status = fd->left_assault(src))) { continue; }
275,308✔
3322
#ifndef NDEBUG
3323
                        target_desc = "left";
3324
#endif
3325
                        break;
3326

3327
                        // revenge to core
3328
                    case 1:
3329
                        target_status = src;
3330
#ifndef NDEBUG
3331
                        target_desc = "core";
3332
#endif
3333
                        break;
3334

3335
                        // revenge to right
3336
                    case 2:
1,066,589✔
3337
                        if (!(target_status = fd->right_assault(src))) { continue; }
2,479,356✔
3338
#ifndef NDEBUG
3339
                        target_desc = "right";
3340
#endif
3341
                        break;
3342

3343
                        // wtf?
3344
                    default:
3345
                        __builtin_unreachable();
3346
                }
3347

3348
                // skip illegal target
3349
                if (!skill_predicate<skill_id>(fd, target_status, target_status, s))
2,676,684✔
3350
                {
3351
                    continue;
169,461✔
3352
                }
3353

3354
                // skip dead target
3355
                if (!is_alive(target_status))
2,507,223✔
3356
                {
3357
#ifndef NDEBUG
3358
                    _DEBUG_MSG(1, "(CANCELLED: target unit dead) %s Revenge (to %s) %s on %s\n",
×
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
                    continue;
×
3363
                }
×
3364

3365
                // TurningTides
3366
                if (__builtin_expect(has_turningtides, false))
109,035✔
3367
                {
3368
                    old_attack = target_status->attack_power();
822✔
3369
                }
3370

3371
                // apply revenged skill
3372
#ifndef NDEBUG
3373
                _DEBUG_MSG(1, "%s Revenge (to %s) %s on %s\n",
2,689,015✔
3374
                        status_description(pb_status).c_str(), target_desc,
3375
                        skill_short_description(fd->cards, s).c_str(), status_description(target_status).c_str());
3376
#endif
3377
                perform_skill<skill_id>(fd, pb_status, target_status, s);
2,507,223✔
3378
                ++ revenged_count;
2,507,223✔
3379

3380
                // revenged TurningTides: get max attack decreasing
3381
                if (__builtin_expect(has_turningtides, false))
109,035✔
3382
                {
3383
                    turningtides_value = std::max(turningtides_value, safe_minus(old_attack, target_status->attack_power()));
2,133✔
3384
                }
3385
            }
3386
            if (revenged_count)
1,066,589✔
3387
            {
3388
                // consume remaining payback/revenge
3389
                ++ pb_status->m_paybacked;
1,044,968✔
3390

3391
                // apply TurningTides
3392
                if (__builtin_expect(has_turningtides, false) && (turningtides_value > 0))
57,584✔
3393
                {
3394
                    SkillSpec ss_rally{Skill::rally, turningtides_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
420✔
3395
                    _DEBUG_MSG(1, "Paybacked TurningTides %u!\n", turningtides_value);
840✔
3396
                    perform_targetted_allied_fast<Skill::rally>(fd, &fd->players[pb_status->m_player]->commander, ss_rally);
420✔
3397
                }
3398
            }
3399
        }
3400
        // apply Payback
3401
        else
3402
        {
3403
            // skip illegal target(src)
3404
            if (!skill_predicate<skill_id>(fd, src, src, s))
526✔
3405
            {
3406
                continue;
174✔
3407
            }
3408

3409
            // skip dead target(src)
3410
            if (!is_alive(src))
352✔
3411
            {
3412
                _DEBUG_MSG(1, "(CANCELLED: src unit dead) %s Payback %s on %s\n",
×
3413
                        status_description(pb_status).c_str(), skill_short_description(fd->cards, s).c_str(),
3414
                        status_description(src).c_str());
3415
                continue;
×
3416
            }
×
3417

3418
            // TurningTides
3419
            if (__builtin_expect(has_turningtides, false))
99✔
3420
            {
3421
                old_attack = src->attack_power();
×
3422
            }
3423

3424
            // apply paybacked skill
3425
            _DEBUG_MSG(1, "%s Payback %s on %s\n",
1,056✔
3426
                    status_description(pb_status).c_str(), skill_short_description(fd->cards, s).c_str(), status_description(src).c_str());
3427
            perform_skill<skill_id>(fd, pb_status, src, s);
352✔
3428
            ++ pb_status->m_paybacked;
352✔
3429

3430
            // handle paybacked TurningTides
3431
            if (__builtin_expect(has_turningtides, false))
99✔
3432
            {
3433
                turningtides_value = std::max(turningtides_value, safe_minus(old_attack, src->attack_power()));
×
3434
                if (turningtides_value > 0)
×
3435
                {
3436
                    SkillSpec ss_rally{Skill::rally, turningtides_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
×
3437
                    _DEBUG_MSG(1, "Paybacked TurningTides %u!\n", turningtides_value);
×
3438
                    perform_targetted_allied_fast<Skill::rally>(fd, &fd->players[pb_status->m_player]->commander, ss_rally);
×
3439
                }
3440
            }
3441
        }
3442
    }
3443

3444
    prepend_on_death(fd,true);  // paybacked skills
44,868,678✔
3445
}
44,868,678✔
3446

3447
//------------------------------------------------------------------------------
3448
inline unsigned evaluate_brawl_score(Field* fd, unsigned player)
×
3449
{
3450
    const auto & p = fd->players;
×
3451
    return 55
×
3452
        // - (10 - p[player]->deck->cards.size())
3453
        // + (10 - p[opponent(player)]->deck->cards.size())
3454
        + p[opponent(player)]->total_cards_destroyed
×
3455
        + p[player]->deck->shuffled_cards.size()
×
3456
        - (unsigned)((fd->turn+7)/8);
×
3457
}
3458

3459
inline unsigned evaluate_war_score(Field* fd, unsigned player)
×
3460
{
3461
    return 208 - ((unsigned)(fd->turn)/2)*4;
×
3462
}
3463
int evaluate_card(Field* fd,const Card* cs);
3464
int evaluate_skill(Field* fd,const Card* c , SkillSpec* ss)
×
3465
{
3466
        // TODO optimize this
3467
        int tvalue = ss->x;
×
3468

3469
        if(ss->card_id != 0)tvalue += 1*evaluate_card(fd,card_by_id_safe(fd->cards,ss->card_id));
×
3470
        tvalue += 10*(ss->id==Skill::flurry);
×
3471
        tvalue += 10*(ss->id==Skill::jam);
×
3472
        tvalue += 5*(ss->id==Skill::overload);
×
3473
        tvalue += 2*(ss->id==Skill::flying);
×
3474
        tvalue += 2*(ss->id==Skill::evolve);
×
3475
        tvalue += 2*(ss->id==Skill::wall);
×
3476
        tvalue += 5*(ss->id==Skill::tribute);
×
3477

3478
        tvalue *= 1.+1.5*(ss->id==Skill::flurry);
×
3479
        tvalue *= 1.+1.5*(ss->id==Skill::drain);
×
3480
        tvalue *= 1.+1.5*(ss->id==Skill::mortar);
×
3481
        tvalue *= 1.+1.5*(ss->id==Skill::scavenge);
×
3482
        tvalue *= 1.+1.5*(ss->id==Skill::disease);
×
3483

3484
        tvalue *= 1.+1.3*(ss->id==Skill::rally);
×
3485
        tvalue *= 1.+1.3*(ss->id==Skill::strike);
×
3486

3487
        tvalue *= 1.+1.2*(ss->id==Skill::avenge);
×
3488
        tvalue *= 1.+1.1*(ss->id==Skill::sunder);
×
3489
        tvalue *= 1.+1.1*(ss->id==Skill::venom);
×
3490

3491
        tvalue *= 1.+1.0*(ss->id==Skill::evade);
×
3492
        tvalue *= 1.+1.0*(ss->id==Skill::enfeeble);
×
3493

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

3496
        tvalue *= 1.+0.2*(ss->id==Skill::fortify);
×
3497
        tvalue *= 1.+0.5*(ss->id==Skill::mend);
×
3498

3499
        tvalue *= 1.+0.4*(ss->id==Skill::jam);
×
3500
        tvalue *= 1.+0.4*(ss->id==Skill::overload);
×
3501
        //tvalue *= 1.+0.4*(ss->id==Skill::rupture);
3502
        tvalue *= 1.+0.4*(ss->id==Skill::bravery);
×
3503
        tvalue *= 1.+0.4*(ss->id==Skill::entrap);
×
3504
        tvalue *= 1.+0.4*(ss->id==Skill::heal);
×
3505

3506

3507
        tvalue *= 1.+0.3*(ss->id==Skill::revenge);
×
3508
        tvalue *= 1.+0.3*(ss->id==Skill::enrage);
×
3509

3510

3511
        //tvalue *= 1.+2.1*(ss->id==Skill::hunt);
3512
        tvalue *= 1.+0.1*(ss->id==Skill::mark);
×
3513
        tvalue *= 1.+0.1*(ss->id==Skill::coalition);
×
3514
        tvalue *= 1.+0.1*(ss->id==Skill::legion);
×
3515
        //tvalue *= 1.+1.1*(ss->id==Skill::barrier);
3516
        tvalue *= 1.+0.1*(ss->id==Skill::pierce);
×
3517
        tvalue *= 1.+0.1*(ss->id==Skill::armor);
×
3518
        //tvalue *= 1.+0.1*(ss->id==Skill::swipe);
3519
        //tvalue *= 1.+0.1*(ss->id==Skill::berserk);
3520
        tvalue *= 1.-0.1*(ss->id==Skill::weaken);
×
3521

3522

3523

3524
        tvalue *= 1.-0.5 *(ss->id==Skill::sabotage); //sucks
×
3525
        tvalue *= 1.-0.5 *(ss->id==Skill::inhibit); //sucks
×
3526
        tvalue *= 1.-0.5 *(ss->id==Skill::corrosive); //sucks
×
3527
        tvalue *= 1.-0.5 *(ss->id==Skill::payback); //sucks
×
3528
        tvalue *= 1.-0.5 *(ss->id==Skill::leech); //sucks
×
3529

3530

3531
        tvalue *= 1.+1*ss->all;
×
3532
        tvalue *= 1.-1./5.*ss->all*(ss->y!=0);
×
3533
        tvalue *= 1.+1*std::min<int>(3,ss->n);
×
3534
        tvalue *= 1.-1./3.* ((c->m_skill_trigger[ss->id] == Skill::Trigger::death) + (c->m_skill_trigger[ss->id] == Skill::Trigger::play));
×
3535
        tvalue *= 1./(2.+ss->c);
×
3536
        //if(tvalue == 0) std::cout << ss->id << " "<<tvalue << std::endl;
3537
        //if(tvalue > 10000) std::cout << ss->id <<" "<< tvalue << std::endl;
3538
        return 0.9*tvalue; // 0.85
×
3539
}
3540
int evaluate_card(Field* fd,const Card* cs)
×
3541
{
3542
        int value = 0;
×
3543
        value += cs->m_health;
×
3544
        value += 2*cs->m_attack;
×
3545
        for( auto ss : cs->m_skills) {
×
3546
                value += evaluate_skill(fd,cs,&ss);
×
3547
        }
3548
        int denom_scale = 1+cs->m_delay*0;
×
3549
        //if(value > 10000) std::cout << cs->m_name << value << std::endl;
3550
        return value /denom_scale;
×
3551
}
3552
int evaluate_cardstatus(Field* fd,CardStatus* cs)
×
3553
{
3554
        int value = 0;
×
3555
        value += cs->m_hp;
×
3556
        value += 2*cs->attack_power();
×
3557
        value += cs->protected_value();
×
3558
        for( auto ss : cs->m_card->m_skills) {
×
3559
                value += evaluate_skill(fd,cs->m_card,&ss);
×
3560
        }
3561
        value -= (cs->m_enfeebled);
×
3562
        int denom_scale = 1+cs->m_delay*0;
×
3563
#ifdef DEBUG
3564
        if(value > 10000) std::cout << cs->m_card->m_name << value <<std::endl;
3565
#endif
3566
        return value /denom_scale;
×
3567
}
3568
// calculate a value for current field, high values are better for fd->tap
3569
// dead commander -> the player gets zero value
3570
int evaluate_field(Field* fd)
×
3571
{
3572
        int value = 0;
×
3573

3574
        int scale = is_alive(&fd->tap->commander);
×
3575
        auto& assaults(fd->tap->assaults);
×
3576
        auto& structures(fd->tap->structures);
×
3577

3578

3579
        value += 0.5*scale * evaluate_cardstatus(fd,&fd->tap->commander);
×
3580
        for(unsigned index(0); index < assaults.size();++index)
×
3581
        {
3582
                value += scale * evaluate_cardstatus(fd,&assaults[index]);
×
3583
        }
3584
        for(unsigned index(0); index < structures.size();++index)
×
3585
        {
3586
                value += scale * evaluate_cardstatus(fd,&structures[index]);
×
3587
        }
3588

3589
        scale = is_alive(&fd->tip->commander);
×
3590
        auto& eassaults(fd->tip->assaults);
×
3591
        auto& estructures(fd->tip->structures);
×
3592
        value -= 0.5*scale * evaluate_cardstatus(fd,&fd->tip->commander);
×
3593
        for(unsigned index(0); index < eassaults.size();++index)
×
3594
        {
3595
                value -= (scale * evaluate_cardstatus(fd,&eassaults[index]));
×
3596
        }
3597
        for(unsigned index(0); index < estructures.size();++index)
×
3598
        {
3599
                value -= (scale * evaluate_cardstatus(fd,&estructures[index]));
×
3600
        }
3601
        return value;
×
3602
}
3603

3604

3605
Results<uint64_t> evaluate_sim_result(Field* fd, bool single_turn_both)
1,336,312✔
3606
{
3607
    typedef unsigned points_score_type;
1,336,312✔
3608
    const auto & p = fd->players;
1,336,312✔
3609
    unsigned raid_damage = 0;
1,336,312✔
3610
#ifndef NQUEST
3611
    unsigned quest_score = 0;
3612
#endif
3613

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

3745
    // Huh? How did we get here?
3746
    assert(false);
×
3747
    return {0, 0, 0, 0,1};
3748
}
3749

3750
//------------------------------------------------------------------------------
3751
//turns_both sets the number of turns to sim before exiting before winner exists.
3752
Results<uint64_t> play(Field* fd,bool skip_init, bool skip_preplay,unsigned turns_both)
1,336,312✔
3753
{
3754
    if(!skip_init){ //>>> start skip init
1,336,312✔
3755
        fd->players[0]->commander.m_player = 0;
1,336,312✔
3756
        fd->players[1]->commander.m_player = 1;
1,336,312✔
3757
        fd->tapi = fd->gamemode == surge ? 1 : 0;
1,336,312✔
3758
        fd->tipi = opponent(fd->tapi);
1,336,312✔
3759
        fd->tap = fd->players[fd->tapi];
1,336,312✔
3760
        fd->tip = fd->players[fd->tipi];
1,336,312✔
3761
        fd->end = false;
1,336,312✔
3762

3763
        // Play dominion & fortresses
3764
        for (unsigned _(0), ai(fd->tapi); _ < 2; ++_)
4,008,936✔
3765
        {
3766
            if (fd->players[ai]->deck->alpha_dominion)
2,672,624✔
3767
            { PlayCard(fd->players[ai]->deck->alpha_dominion, fd, ai, &fd->players[ai]->commander).op<CardType::structure>(); }
1,450,998✔
3768
            for (const Card* played_card: fd->players[ai]->deck->shuffled_forts)
5,345,248✔
3769
            {
3770

3771
                switch (played_card->m_type)
×
3772
                {
3773
                    case CardType::assault:
×
3774
                        PlayCard(played_card, fd, ai, &fd->players[ai]->commander).op<CardType::assault>();
×
3775
                        break;
×
3776
                    case CardType::structure:
×
3777
                        PlayCard(played_card, fd, ai, &fd->players[ai]->commander).op<CardType::structure>();
×
3778
                        break;
×
3779
                    case CardType::commander:
×
3780
                    case CardType::num_cardtypes:
×
3781
                        _DEBUG_MSG(0, "Unknown card type: #%u %s: %u\n",
×
3782
                                played_card->m_id, card_description(fd->cards, played_card).c_str(), played_card->m_type);
×
3783
                        assert(false);
×
3784
                        break;
3785
                }
3786
            }
3787
            resolve_skill(fd);
2,672,624✔
3788
            std::swap(fd->tapi, fd->tipi);
2,672,624✔
3789
            std::swap(fd->tap, fd->tip);
2,672,624✔
3790
            ai = opponent(ai);
2,672,624✔
3791
        }
3792
    }//>>> end skip init
3793
    unsigned both_turn_limit = fd->turn+2*turns_both;
1,336,312✔
3794
    while(__builtin_expect(fd->turn <= turn_limit && !fd->end && (turns_both==0 || fd->turn < both_turn_limit), true))
18,708,820✔
3795
    {
3796
        if(!skip_preplay){ //>>> start skip init
18,688,168✔
3797

3798
            fd->current_phase = Field::playcard_phase;
18,688,168✔
3799
            // Initialize stuff, remove dead cards
3800
            _DEBUG_MSG(1, "------------------------------------------------------------------------\n"
18,688,168✔
3801
                    "TURN %u begins for %s\n", fd->turn, status_description(&fd->tap->commander).c_str());
18,688,168✔
3802

3803
            // reduce timers & perform triggered skills (like Summon)
3804
            fd->prepare_action();
18,688,168✔
3805
            turn_start_phase(fd); // summon may postpone skills to be resolved
18,688,168✔
3806
            resolve_skill(fd); // resolve postponed skills recursively
18,688,168✔
3807
            fd->finalize_action();
18,688,168✔
3808

3809
            //bool bge_megamorphosis = fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis];
3810

3811
        }//>>> end skip init
3812
        else { skip_preplay = false;}
3813
        // Play a card
3814
        const Card* played_card(fd->tap->deck->next(fd));
18,688,168✔
3815
        if (played_card)
18,688,168✔
3816
        {
3817

3818
            // Begin 'Play Card' phase action
3819
            fd->prepare_action();
16,820,941✔
3820

3821
            // Play selected card
3822
            //CardStatus* played_status = nullptr;
3823
            switch (played_card->m_type)
16,820,941✔
3824
            {
3825
                case CardType::assault:
16,017,405✔
3826
                    PlayCard(played_card, fd, fd->tapi, &fd->tap->commander).op<CardType::assault>();
16,017,405✔
3827
                    break;
16,017,405✔
3828
                case CardType::structure:
803,536✔
3829
                    PlayCard(played_card, fd, fd->tapi, &fd->tap->commander).op<CardType::structure>();
803,536✔
3830
                    break;
803,536✔
3831
                case CardType::commander:
×
3832
                case CardType::num_cardtypes:
×
3833
                    _DEBUG_MSG(0, "Unknown card type: #%u %s: %u\n",
×
3834
                            played_card->m_id, card_description(fd->cards, played_card).c_str(), played_card->m_type);
×
3835
                    assert(false);
×
3836
                    break;
3837
            }
3838
            resolve_skill(fd); // resolve postponed skills recursively
16,820,941✔
3839
            //status_description(played_status)
3840
            //_DEBUG_MSG(3,"Card played: %s", status_description(played_status).c_str());
3841
            // End 'Play Card' phase action
3842
            fd->finalize_action();
16,820,941✔
3843

3844

3845

3846
        }
3847
        if (__builtin_expect(fd->end, false)) { break; }
18,688,168✔
3848

3849
        //-------------------------------------------------
3850
        // Phase: (Later-) Enhance, Inhibit, Sabotage, Disease
3851
        //-------------------------------------------------
3852
        //Skill: Enhance
3853
        //Perform later enhance for commander
3854
        if(!fd->fixes[Fix::enhance_early]) {
18,688,168✔
3855
        check_and_perform_later_enhance(fd,&fd->tap->commander);
×
3856
        auto& structures(fd->tap->structures);
×
3857
        for(unsigned index(0); index < structures.size(); ++index)
×
3858
        {
3859
            CardStatus * status = &structures[index];
×
3860
            //enhance everything else after card was played
3861
            check_and_perform_later_enhance(fd,status);
×
3862
        }
3863
        }
3864
        //Perform Inhibit, Sabotage, Disease
3865
        auto& assaults(fd->tap->assaults);
18,688,168✔
3866
        for(unsigned index(0); index < assaults.size(); ++index)
72,402,137✔
3867
        {
3868
            CardStatus * att_status = &assaults[index];
53,713,969✔
3869
            if(att_status->m_index >= fd->tip->assaults.size())continue; //skip no enemy
53,713,969✔
3870
            auto def_status = &fd->tip->assaults[att_status->m_index];
32,783,349✔
3871
            if(!is_alive(def_status))continue; //skip dead
32,783,349✔
3872

3873
            check_and_perform_inhibit(fd,att_status,def_status);
32,783,200✔
3874
            check_and_perform_sabotage(fd,att_status,def_status);
32,783,200✔
3875
            check_and_perform_disease(fd,att_status,def_status);
32,783,200✔
3876
        }
3877
        //-------------------------------------------------
3878

3879
        // Evaluate Passive BGE Heroism skills
3880
        if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::heroism], false))
18,688,168✔
3881
        {
3882
            for (CardStatus * dst: fd->tap->assaults.m_indirect)
241,723✔
3883
            {
3884
                unsigned bge_value = (dst->skill(Skill::valor) + dst->skill(Skill::bravery)+ 1) / 2;
149,568✔
3885
                if (bge_value <= 0)
149,568✔
3886
                { continue; }
133,053✔
3887
                SkillSpec ss_protect{Skill::protect, bge_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
16,625✔
3888
                if (dst->m_inhibited > 0)
16,625✔
3889
                {
3890
                    _DEBUG_MSG(1, "Heroism: %s on %s but it is inhibited\n",
110✔
3891
                            skill_short_description(fd->cards, ss_protect).c_str(), status_description(dst).c_str());
110✔
3892
                    -- dst->m_inhibited;
110✔
3893

3894
                    // Passive BGE: Divert
3895
                    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::divert], false))
110✔
3896
                    {
3897
                        SkillSpec diverted_ss = ss_protect;
×
3898
                        diverted_ss.y = allfactions;
×
3899
                        diverted_ss.n = 1;
×
3900
                        diverted_ss.all = false;
×
3901
                        // for (unsigned i = 0; i < num_inhibited; ++ i)
3902
                        {
×
3903
                            select_targets<Skill::protect>(fd, &fd->tip->commander, diverted_ss);
×
3904
                            std::vector<CardStatus*> selection_array = fd->selection_array;
×
3905
                            for (CardStatus * dst: selection_array)
×
3906
                            {
3907
                                if (dst->m_inhibited > 0)
×
3908
                                {
3909
                                    _DEBUG_MSG(1, "Heroism: %s (Diverted) on %s but it is inhibited\n",
×
3910
                                            skill_short_description(fd->cards, diverted_ss).c_str(), status_description(dst).c_str());
×
3911
                                    -- dst->m_inhibited;
×
3912
                                    continue;
×
3913
                                }
×
3914
                                _DEBUG_MSG(1, "Heroism: %s (Diverted) on %s\n",
×
3915
                                        skill_short_description(fd->cards, diverted_ss).c_str(), status_description(dst).c_str());
×
3916
                                perform_skill<Skill::protect>(fd, &fd->tap->commander, dst, diverted_ss);  // XXX: the caster
×
3917
                            }
3918
                        }
×
3919
                    }
3920
                    continue;
110✔
3921
                }
110✔
3922
#ifndef NQUEST
3923
                bool has_counted_quest = false;
3924
#endif
3925
                check_and_perform_skill<Skill::protect>(fd, &fd->tap->commander, dst, ss_protect, false
16,515✔
3926
#ifndef NQUEST
3927
                        , has_counted_quest
3928
#endif
3929
                        );
3930
            }
3931
        }
3932

3933
        // Evaluate activation BGE skills
3934
        fd->current_phase = Field::bge_phase;
18,688,168✔
3935
        for (const auto & bg_skill: fd->bg_skills[fd->tapi])
18,780,554✔
3936
        {
3937
            fd->prepare_action();
92,386✔
3938
            _DEBUG_MSG(2, "Evaluating BG skill %s\n", skill_description(fd->cards, bg_skill).c_str());
92,386✔
3939
            fd->skill_queue.emplace_back(&fd->tap->commander, bg_skill);
92,386✔
3940
            resolve_skill(fd);
92,386✔
3941
            fd->finalize_action();
92,386✔
3942
        }
3943
        if (__builtin_expect(fd->end, false)) { break; }
18,688,168✔
3944

3945
        // Evaluate commander
3946
        fd->current_phase = Field::commander_phase;
18,688,168✔
3947
        evaluate_skills<CardType::commander>(fd, &fd->tap->commander, fd->tap->commander.m_card->m_skills);
18,688,168✔
3948
        if (__builtin_expect(fd->end, false)) { break; }
18,688,168✔
3949

3950
        // Evaluate structures
3951
        fd->current_phase = Field::structures_phase;
18,688,168✔
3952
        for (fd->current_ci = 0; !fd->end && (fd->current_ci < fd->tap->structures.size()); ++fd->current_ci)
43,482,296✔
3953
        {
3954
            CardStatus* current_status(&fd->tap->structures[fd->current_ci]);
24,794,128✔
3955
            if (!is_active(current_status))
24,794,128✔
3956
            {
3957
                _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str());
8,151,813✔
3958
            }
3959
            else
3960
            {
3961
                evaluate_skills<CardType::structure>(fd, current_status, current_status->m_card->m_skills);
16,642,315✔
3962
            }
3963
        }
3964

3965
        // Evaluate assaults
3966
        fd->current_phase = Field::assaults_phase;
18,688,168✔
3967
        fd->bloodlust_value = 0;
18,688,168✔
3968
        for (fd->current_ci = 0; !fd->end && (fd->current_ci < fd->tap->assaults.size()); ++fd->current_ci)
67,194,240✔
3969
        {
3970
            CardStatus* current_status(&fd->tap->assaults[fd->current_ci]);
49,821,732✔
3971
            bool attacked = false;
49,821,732✔
3972
            if (!is_active(current_status))
49,821,732✔
3973
            {
3974
                _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str());
25,552,558✔
3975
                // Passive BGE: HaltedOrders
3976
                /*
3977
                unsigned inhibit_value;
3978
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::haltedorders], false)
3979
                        && (current_status->m_delay > 0) // still frozen
3980
                        && (fd->current_ci < fd->tip->assaults.size()) // across slot isn't empty
3981
                        && is_alive(&fd->tip->assaults[fd->current_ci]) // across assault is alive
3982
                        && ((inhibit_value = current_status->skill(Skill::inhibit))
3983
                            > fd->tip->assaults[fd->current_ci].m_inhibited)) // inhibit/re-inhibit(if higher)
3984
                        {
3985
                            CardStatus* across_status(&fd->tip->assaults[fd->current_ci]);
3986
                            _DEBUG_MSG(1, "Halted Orders: %s inhibits %s by %u\n",
3987
                                    status_description(current_status).c_str(),
3988
                                    status_description(across_status).c_str(), inhibit_value);
3989
                            across_status->m_inhibited = inhibit_value;
3990
                        }
3991
                        */
3992
            }
3993
            else
3994
            {
3995
                if (current_status->m_protected_stasis)
24,269,174✔
3996
                {
3997
                    _DEBUG_MSG(1, "%s loses Stasis protection (activated)\n",
2,121,572✔
3998
                            status_description(current_status).c_str());
3999
                }
4000
                current_status->m_protected_stasis = 0;
24,269,174✔
4001
                fd->assault_bloodlusted = false;
24,269,174✔
4002
                current_status->m_step = CardStep::attacking;
24,269,174✔
4003
                evaluate_skills<CardType::assault>(fd, current_status, current_status->m_card->m_skills, &attacked);
24,269,174✔
4004
                if (__builtin_expect(fd->end, false)) { break; }
24,269,174✔
4005
                if (__builtin_expect(!is_alive(current_status), false)) { continue; }
22,953,514✔
4006
            }
4007

4008
            current_status->m_step = CardStep::attacked;
47,767,027✔
4009
        }
4010
        fd->current_phase = Field::end_phase;
18,688,168✔
4011
        turn_end_phase(fd);
18,688,168✔
4012
        if (__builtin_expect(fd->end, false)) { break; }
18,688,168✔
4013
        _DEBUG_MSG(1, "TURN %u ends for %s\n", fd->turn, status_description(&fd->tap->commander).c_str());
17,372,508✔
4014
        std::swap(fd->tapi, fd->tipi);
17,372,508✔
4015
        std::swap(fd->tap, fd->tip);
17,372,508✔
4016
        ++fd->turn;
17,372,508✔
4017
    }
4018

4019
    return evaluate_sim_result(fd,turns_both!= 0);
1,336,312✔
4020
}
4021

4022
//------------------------------------------------------------------------------
4023
void fill_skill_table()
81✔
4024
{
4025
    memset(skill_table, 0, sizeof skill_table);
81✔
4026
    skill_table[Skill::mortar] = perform_targetted_hostile_fast<Skill::mortar>;
81✔
4027
    skill_table[Skill::enfeeble] = perform_targetted_hostile_fast<Skill::enfeeble>;
81✔
4028
    skill_table[Skill::enhance] = perform_targetted_allied_fast<Skill::enhance>;
81✔
4029
    skill_table[Skill::evolve] = perform_targetted_allied_fast<Skill::evolve>;
81✔
4030
    skill_table[Skill::heal] = perform_targetted_allied_fast<Skill::heal>;
81✔
4031
    skill_table[Skill::jam] = perform_targetted_hostile_fast<Skill::jam>;
81✔
4032
    skill_table[Skill::mend] = perform_targetted_allied_fast<Skill::mend>;
81✔
4033
    skill_table[Skill::fortify] = perform_targetted_allied_fast<Skill::fortify>;
81✔
4034
    skill_table[Skill::overload] = perform_targetted_allied_fast<Skill::overload>;
81✔
4035
    skill_table[Skill::protect] = perform_targetted_allied_fast<Skill::protect>;
81✔
4036
    skill_table[Skill::rally] = perform_targetted_allied_fast<Skill::rally>;
81✔
4037
    skill_table[Skill::enrage] = perform_targetted_allied_fast<Skill::enrage>;
81✔
4038
    skill_table[Skill::entrap] = perform_targetted_allied_fast<Skill::entrap>;
81✔
4039
    skill_table[Skill::rush] = perform_targetted_allied_fast_rush;
81✔
4040
    skill_table[Skill::siege] = perform_targetted_hostile_fast<Skill::siege>;
81✔
4041
    skill_table[Skill::strike] = perform_targetted_hostile_fast<Skill::strike>;
81✔
4042
    skill_table[Skill::sunder] = perform_targetted_hostile_fast<Skill::sunder>;
81✔
4043
    skill_table[Skill::weaken] = perform_targetted_hostile_fast<Skill::weaken>;
81✔
4044
    skill_table[Skill::mimic] = perform_targetted_hostile_fast<Skill::mimic>;
81✔
4045
}
81✔
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