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

APN-Pucky / tyrant_optimize / 20460090975

23 Dec 2025 11:54AM UTC coverage: 70.463% (+0.06%) from 70.399%
20460090975

push

github

dsuchka
Fix Enhance for Hunt and Mark

Looks like it is a game bug, but Hunt and Mark both
can be enhanced on inactive units. Implement it.

1 of 1 new or added line in 1 file covered. (100.0%)

3 existing lines in 2 files now uncovered.

4783 of 6788 relevant lines covered (70.46%)

8587459.55 hits per line

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

84.34
/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); }
13,066,627✔
39
inline bool is_alive(CardStatus* c) { return (c->m_hp > 0); }
13,512,763✔
40
inline bool can_act(CardStatus* c) { return is_alive(c) && !c->m_jammed; }
363,120,168✔
41
inline bool is_active(CardStatus* c) { return can_act(c) && (c->m_delay == 0); }
301,870,531✔
42
inline bool is_active_next_turn(CardStatus* c) { return can_act(c) && (c->m_delay <= 1); }
42,883,126✔
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)));}
279,267✔
44
// Can be healed / repaired
45
inline bool can_be_healed(CardStatus* c) { return is_alive(c) && (c->m_hp < c->max_hp()); }
36,271✔
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)
58,124,145✔
60
{
61
    return status->description();
23,235,707✔
62
}
63
//------------------------------------------------------------------------------
64
template <typename CardsIter, typename Functor>
65
inline unsigned Field::make_selection_array(CardsIter first, CardsIter last, Functor f)
110,606,888✔
66
{
67
    this->selection_array.clear();
110,606,888✔
68
    for(auto c = first; c != last; ++c)
452,150,840✔
69
    {
70
        if (f(*c))
691,501,952✔
71
        {
72
            this->selection_array.push_back(*c);
176,985,926✔
73
        }
74
    }
75
    return(this->selection_array.size());
110,606,888✔
76
}
77
inline CardStatus * Field::left_assault(const CardStatus * status)
1,013,559✔
78
{
79
    return left_assault(status, 1);
3,796,563✔
80
}
81
inline CardStatus * Field::left_assault(const CardStatus * status, const unsigned n)
1,013,559✔
82
{
83
    auto & assaults = this->players[status->m_player]->assaults;
1,013,559✔
84
    if (status->m_index >= n)
1,013,559✔
85
    {
86
        auto left_status = &assaults[status->m_index - n];
816,101✔
87
        if (is_alive(left_status))
816,101✔
88
        {
89
            return left_status;
90
        }
91
    }
92
    return nullptr;
93
}
94
inline CardStatus * Field::right_assault(const CardStatus * status)
1,013,559✔
95
{
96
    return right_assault(status, 1);
3,826,423✔
97
}
98
inline CardStatus * Field::right_assault(const CardStatus * status, const unsigned n)
1,013,559✔
99
{
100
    auto & assaults = this->players[status->m_player]->assaults;
1,013,559✔
101
    if ((status->m_index + n) < assaults.size())
1,013,559✔
102
    {
103
        auto right_status = &assaults[status->m_index + n];
758,868✔
104
        if (is_alive(right_status))
758,868✔
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()
87,803,789✔
123
{
124
    damaged_units_to_times.clear();
175,607,578✔
125
}
126

127
//------------------------------------------------------------------------------
128
inline void Field::finalize_action()
86,785,013✔
129
{
130
    for (auto unit_it = damaged_units_to_times.begin(); unit_it != damaged_units_to_times.end(); ++ unit_it)
87,041,317✔
131
    {
132
        if (__builtin_expect(!unit_it->second, false))
256,304✔
133
        { continue; }
×
134
        CardStatus * dmg_status = unit_it->first;
256,304✔
135
        if (__builtin_expect(!is_alive(dmg_status), false))
256,304✔
136
        { continue; }
105,730✔
137
        unsigned barrier_base = dmg_status->skill(Skill::barrier);
150,574✔
138
        if (barrier_base)
150,574✔
139
        {
140
            unsigned protect_value = barrier_base * unit_it->second;
150,574✔
141
            _DEBUG_MSG(1, "%s protects itself for %u (barrier %u x %u damage taken)\n",
150,574✔
142
                    status_description(dmg_status).c_str(), protect_value, barrier_base, unit_it->second);
150,574✔
143
            dmg_status->m_protected += protect_value;
150,574✔
144
        }
145
    }
146
}
86,785,013✔
147

148
//------------------------------------------------------------------------------
149
inline unsigned CardStatus::skill_base_value(Skill::Skill skill_id) const
1,090,170,081✔
150
{
151
    return m_card->m_skill_value[skill_id + m_primary_skill_offset[skill_id]]
1,090,170,081✔
152
        + (skill_id == Skill::berserk ? m_enraged : 0)
156,918✔
153
        + (skill_id == Skill::counter ? m_entrapped : 0)
33,567,273✔
154
        ;
155
}
156
//------------------------------------------------------------------------------
157
inline unsigned CardStatus::skill(Skill::Skill skill_id) const
688,243,294✔
158
{
159
    return (is_activation_skill_with_x(skill_id)
688,243,294✔
160
            ? safe_minus(skill_base_value(skill_id), m_sabotaged)
161
            : skill_base_value(skill_id))
690,409,330✔
162
        + enhanced(skill_id);
690,409,330✔
163
}
164
//------------------------------------------------------------------------------
165
inline bool CardStatus::has_skill(Skill::Skill skill_id) const
400,233,038✔
166
{
167
    return skill_base_value(skill_id);
58,156✔
168
}
169
//------------------------------------------------------------------------------
170
inline unsigned CardStatus::enhanced(Skill::Skill skill_id) const
786,195,453✔
171
{
172
    return m_enhanced_value[skill_id + m_primary_skill_offset[skill_id]];
150,574✔
173
}
174
//------------------------------------------------------------------------------
175
inline unsigned CardStatus::protected_value() const
49,576,868✔
176
{
177
    return m_protected + m_protected_stasis;
49,576,868✔
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
90,620,815✔
187
{
188
    return (m_card->m_health + safe_minus(m_perm_health_buff, m_subdued));
88,119,209✔
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)
20,894,669✔
197
{
198
    value = remove_disease(this,value);
20,894,669✔
199
    return (m_hp = std::min(m_hp + value, max_hp()));
37,316,583✔
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)
12,487,659✔
209
{
210
    value = remove_disease(this,value);
12,487,659✔
211
    m_perm_health_buff += value;
12,487,659✔
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);
12,487,659✔
215
}
216
//------------------------------------------------------------------------------
217
inline void CardStatus::set(const Card* card)
20,001,044✔
218
{
219
    this->set(*card);
20,001,044✔
220
}
221
//------------------------------------------------------------------------------
222
inline void CardStatus::set(const Card& card)
20,001,044✔
223
{
224
    m_card = &card;
20,001,044✔
225
    m_index = 0;
20,001,044✔
226
    m_action_index=0;
20,001,044✔
227
    m_player = 0;
20,001,044✔
228
    m_delay = card.m_delay;
20,001,044✔
229
    m_hp = card.m_health;
20,001,044✔
230
    m_absorption = 0;
20,001,044✔
231
    m_step = CardStep::none;
20,001,044✔
232
    m_perm_health_buff = 0;
20,001,044✔
233
    m_perm_attack_buff = 0;
20,001,044✔
234
    m_temp_attack_buff = 0;
20,001,044✔
235
    m_corroded_rate = 0;
20,001,044✔
236
    m_corroded_weakened = 0;
20,001,044✔
237
    m_subdued = 0;
20,001,044✔
238
    m_enfeebled = 0;
20,001,044✔
239
    m_evaded = 0;
20,001,044✔
240
    m_inhibited = 0;
20,001,044✔
241
    m_sabotaged = 0;
20,001,044✔
242
    m_jammed = false;
20,001,044✔
243
    m_overloaded = false;
20,001,044✔
244
    m_paybacked = 0;
20,001,044✔
245
    m_tributed = 0;
20,001,044✔
246
    m_poisoned = 0;
20,001,044✔
247
    m_protected = 0;
20,001,044✔
248
    m_protected_stasis = 0;
20,001,044✔
249
    m_enraged = 0;
20,001,044✔
250
    m_entrapped = 0;
20,001,044✔
251
    m_marked = 0;
20,001,044✔
252
    m_diseased = 0;
20,001,044✔
253
    m_rush_attempted = false;
20,001,044✔
254
    m_sundered = false;
20,001,044✔
255
    //APN
256
    m_summoned = false;
20,001,044✔
257

258
    std::memset(m_primary_skill_offset, 0, sizeof m_primary_skill_offset);
20,001,044✔
259
    std::memset(m_evolved_skill_offset, 0, sizeof m_evolved_skill_offset);
20,001,044✔
260
    std::memset(m_enhanced_value, 0, sizeof m_enhanced_value);
20,001,044✔
261
    std::memset(m_skill_cd, 0, sizeof m_skill_cd);
20,001,044✔
262
}
20,001,044✔
263
//------------------------------------------------------------------------------
264
/**
265
 * @brief Calculate the attack power of the CardStatus.
266
 * 
267
 * @return unsigned 
268
 */
269
inline unsigned CardStatus::attack_power() const
172,397,081✔
270
{
271
    signed attack = calc_attack_power();
172,397,081✔
272
    if(__builtin_expect(attack <0,false))
172,397,081✔
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);
172,397,081✔
277
    return (unsigned)attack;
172,397,081✔
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
172,982,641✔
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."
172,982,641✔
293
    {
294
        return
172,982,641✔
295
        (signed)safe_minus(
345,965,282✔
296
                m_card->m_attack + safe_minus(m_perm_attack_buff, m_subdued)+ m_temp_attack_buff,
172,982,641✔
297
                m_corroded_weakened
172,982,641✔
298
                );
172,982,641✔
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)
109,440✔
318
{
319
    const auto cardIter = cards.cards_by_id.find(card_id);
109,440✔
320
    if (cardIter == cards.cards_by_id.end()) { return "UnknownCard.id[" + tuo::to_string(card_id) + "]"; }
109,440✔
321
    return cardIter->second->m_name;
218,880✔
322
}
323
//------------------------------------------------------------------------------
324
std::string card_description(const Cards& cards, const Card* c)
2,560,534✔
325
{
326
    std::string desc;
2,560,534✔
327
    desc = c->m_name;
2,560,534✔
328
    switch(c->m_type)
2,560,534✔
329
    {
330
        case CardType::assault:
2,118,867✔
331
            desc += ": " + tuo::to_string(c->m_attack) + "/" + tuo::to_string(c->m_health) + "/" + tuo::to_string(c->m_delay);
10,594,335✔
332
            break;
2,118,867✔
333
        case CardType::structure:
441,103✔
334
            desc += ": " + tuo::to_string(c->m_health) + "/" + tuo::to_string(c->m_delay);
1,764,412✔
335
            break;
441,103✔
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,298,603✔
344
    if (c->m_faction != allfactions) { desc += " " + faction_names[c->m_faction]; }
5,121,068✔
345
    for (auto& skill: c->m_skills_on_play) { desc += ", " + skill_description(cards, skill, Skill::Trigger::play); }
2,697,640✔
346
    for (auto& skill: c->m_skills) { desc += ", " + skill_description(cards, skill, Skill::Trigger::activate); }
16,945,028✔
347
    //APN
348
    for (auto& skill: c->m_skills_on_attacked) { desc += ", " + skill_description(cards, skill, Skill::Trigger::attacked); }
3,129,688✔
349
    for (auto& skill: c->m_skills_on_death) { desc += ", " + skill_description(cards, skill, Skill::Trigger::death); }
2,736,280✔
350
    return desc;
2,560,534✔
351
}
×
352
//------------------------------------------------------------------------------
353
std::string CardStatus::description() const
58,124,145✔
354
{
355
    std::string desc = "P" + tuo::to_string(m_player) + " ";
174,372,435✔
356
    switch(m_card->m_type)
58,124,145✔
357
    {
358
        case CardType::commander: desc += "Commander "; break;
17,635,799✔
359
        case CardType::assault: desc += "Assault " + tuo::to_string(m_index) + " "; break;
135,156,176✔
360
        case CardType::structure: desc += "Structure " + tuo::to_string(m_index) + " "; break;
26,797,208✔
361
        case CardType::num_cardtypes: assert(false); break;
×
362
    }
363
    desc += "[" + m_card->m_name;
116,248,290✔
364
    switch (m_card->m_type)
58,124,145✔
365
    {
366
        case CardType::assault:
33,789,044✔
367
            desc += " att:[[" + tuo::to_string(m_card->m_attack) + "(base)";
135,156,176✔
368
            if (m_perm_attack_buff)
33,789,044✔
369
            {
370
                desc += "+[" + tuo::to_string(m_perm_attack_buff) + "(perm)";
28,650,664✔
371
                if (m_subdued) { desc += "-" + tuo::to_string(m_subdued) + "(subd)"; }
8,337,943✔
372
                desc += "]";
7,162,666✔
373
            }
374
            if (m_corroded_weakened) { desc += "-" + tuo::to_string(m_corroded_weakened) + "(corr)"; }
35,392,184✔
375
            desc += "]";
33,789,044✔
376
            if (m_temp_attack_buff) { desc += (m_temp_attack_buff > 0 ? "+" : "") + tuo::to_string(m_temp_attack_buff) + "(temp)"; }
51,949,241✔
377
            desc += "]=" + tuo::to_string(attack_power());
101,367,132✔
378
        case CardType::structure:
58,124,145✔
379
        case CardType::commander:
58,124,145✔
380
            desc += " hp:" + tuo::to_string(m_hp);
174,372,435✔
381
            if(m_absorption)desc += " absorb:" + tuo::to_string(m_absorption);
64,116,965✔
382
            break;
383
        case CardType::num_cardtypes:
×
384
            assert(false);
×
385
            break;
386
    }
387
    if (m_delay) { desc += " cd:" + tuo::to_string(m_delay); }
83,569,969✔
388
    // Status w/o value
389
    if (m_jammed) { desc += ", jammed"; }
58,124,145✔
390
    if (m_overloaded) { desc += ", overloaded"; }
58,124,145✔
391
    if (m_sundered) { desc += ", sundered"; }
58,124,145✔
392
    if (m_summoned) { desc+= ", summoned"; }
58,124,145✔
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) + ")"; }
60,459,121✔
395
    if (m_subdued) { desc += ", subdued " + tuo::to_string(m_subdued); }
60,279,885✔
396
    if (m_enfeebled) { desc += ", enfeebled " + tuo::to_string(m_enfeebled); }
63,543,195✔
397
    if (m_inhibited) { desc += ", inhibited " + tuo::to_string(m_inhibited); }
61,536,359✔
398
    if (m_sabotaged) { desc += ", sabotaged " + tuo::to_string(m_sabotaged); }
61,448,101✔
399
    if (m_poisoned) { desc += ", poisoned " + tuo::to_string(m_poisoned); }
59,034,297✔
400
    if (m_protected) { desc += ", protected " + tuo::to_string(m_protected); }
81,178,781✔
401
    if (m_protected_stasis) { desc += ", stasis " + tuo::to_string(m_protected_stasis); }
63,303,099✔
402
    if (m_enraged) { desc += ", enraged " + tuo::to_string(m_enraged); }
64,440,163✔
403
    if (m_entrapped) { desc += ", entrapped " + tuo::to_string(m_entrapped); }
79,003,719✔
404
    if (m_marked) { desc += ", marked " + tuo::to_string(m_marked); }
59,404,451✔
405
    if (m_diseased) { desc += ", diseased " + tuo::to_string(m_diseased); }
59,151,337✔
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};
58,124,145✔
409
    for (const Skill::Trigger& trig: s_triggers)
290,620,725✔
410
    {
411
        std::vector<SkillSpec> card_skills(
232,496,580✔
412
                (trig == Skill::Trigger::play) ? m_card->m_skills_on_play :
232,496,580✔
413
                (trig == Skill::Trigger::activate) ? m_card->m_skills :
174,372,435✔
414
                //APN
415
                (trig == Skill::Trigger::attacked) ? m_card->m_skills_on_attacked :
116,248,290✔
416
                (trig == Skill::Trigger::death) ? m_card->m_skills_on_death :
58,124,145✔
417
                std::vector<SkillSpec>());
232,496,580✔
418

419
        // emulate Berserk/Counter by status Enraged/Entrapped unless such skills exist (only for normal skill triggering)
420
        if (trig == Skill::Trigger::activate)
232,496,580✔
421
        {
422
            if (m_enraged && !std::count_if(card_skills.begin(), card_skills.end(), [](const SkillSpec ss) { return (ss.id == Skill::berserk); }))
61,282,154✔
423
            {
424
                SkillSpec ss{Skill::berserk, m_enraged, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
3,158,009✔
425
                card_skills.emplace_back(ss);
3,158,009✔
426
            }
427
            if (m_entrapped && !std::count_if(card_skills.begin(), card_skills.end(), [](const SkillSpec ss) { return (ss.id == Skill::counter); }))
68,563,932✔
428
            {
429
                SkillSpec ss{Skill::counter, m_entrapped, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
10,256,490✔
430
                card_skills.emplace_back(ss);
10,256,490✔
431
            }
432
        }
433
        for (const auto& ss : card_skills)
419,610,855✔
434
        {
435
            std::string skill_desc;
187,114,275✔
436
            if (m_evolved_skill_offset[ss.id]) { skill_desc += "->" + skill_names[ss.id + m_evolved_skill_offset[ss.id]]; }
187,529,497✔
437
            if (m_enhanced_value[ss.id]) { skill_desc += " +" + tuo::to_string(m_enhanced_value[ss.id]); }
190,225,499✔
438
            if (!skill_desc.empty())
187,114,275✔
439
            {
440
                desc += ", " + (
3,941,668✔
441
                        (trig == Skill::Trigger::play) ? "(On Play)" :
3,941,668✔
442
                        (trig == Skill::Trigger::attacked) ? "(On Attacked)" :
1,970,834✔
443
                        (trig == Skill::Trigger::death) ? "(On Death)" :
1,970,834✔
444
                        std::string("")) + skill_names[ss.id] + skill_desc;
9,854,170✔
445
            }
446
        }
187,114,275✔
447
    }
232,496,580✔
448
    return desc + "]";
58,124,145✔
449
}
58,124,145✔
450
//------------------------------------------------------------------------------
451
void Hand::reset(std::mt19937& re)
2,078,338✔
452
{
453
    assaults.reset();
2,078,338✔
454
    structures.reset();
2,078,338✔
455
    deck->shuffle(re);
2,078,338✔
456
    commander.set(deck->shuffled_commander);
2,078,338✔
457
    total_cards_destroyed = 0;
2,078,338✔
458
    total_nonsummon_cards_destroyed = 0;
2,078,338✔
459
    if (commander.skill(Skill::stasis))
2,078,338✔
460
    {
461
        stasis_faction_bitmap |= (1u << commander.m_card->m_faction);
×
462
    }
463
}
2,078,338✔
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)
48,718,305✔
473
{
474
    return((player + 1) % 2);
48,718,305✔
475
}
476

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

501
//------------------------------------------------------------------------------
502
inline void resolve_scavenge(Storage<CardStatus>& store)
33,862,212✔
503
{
504
    for(auto status : store.m_indirect)
131,625,511✔
505
    {
506
        if(!is_alive(status))continue;
97,763,299✔
507
        unsigned scavenge_value = status->skill(Skill::scavenge);
85,606,918✔
508
        if(!scavenge_value)continue;
85,606,918✔
509

510
        _DEBUG_MSG(1, "%s activates Scavenge %u\n",
5,970,510✔
511
                status_description(status).c_str(), scavenge_value);
5,970,510✔
512
        status->ext_hp(scavenge_value);
5,970,510✔
513
    }
514
}
33,862,212✔
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)
115,288,329✔
523
{
524
    if (fd->killed_units.empty())
115,288,329✔
525
        return;
526
    bool skip_all_except_summon = fd->fixes[Fix::death_from_bge] && (fd->current_phase == Field::bge_phase);
8,363,486✔
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;
8,363,486✔
532
    unsigned stacked_poison_value = 0;
8,363,486✔
533
    unsigned last_index = 99999;
8,363,486✔
534
    CardStatus* left_virulence_victim = nullptr;
8,363,486✔
535
    for (auto status: fd->killed_units)
16,829,039✔
536
    {
537
        // Skill: Scavenge
538
        // Any unit dies => perm-hp-buff
539
        if (__builtin_expect(!skip_all_except_summon, true))
8,465,553✔
540
        {
541
            resolve_scavenge(fd->players[0]->assaults);
8,465,553✔
542
            resolve_scavenge(fd->players[1]->assaults);
8,465,553✔
543
            resolve_scavenge(fd->players[0]->structures);
8,465,553✔
544
            resolve_scavenge(fd->players[1]->structures);
8,465,553✔
545
        }
546

547
        if ((status->m_card->m_type == CardType::assault) && (!skip_all_except_summon))
8,465,553✔
548
        {
549
            // Skill: Avenge
550
            const unsigned host_idx = status->m_index;
7,467,414✔
551
            unsigned from_idx, till_idx;
7,467,414✔
552
            //scan all assaults for Avenge
553
            from_idx = 0;
7,467,414✔
554
            till_idx = assaults.size() - 1;
7,467,414✔
555
            for (; from_idx <= till_idx; ++ from_idx)
29,724,470✔
556
            {
557
                if (from_idx == host_idx) { continue; }
22,257,056✔
558
                CardStatus* adj_status = &assaults[from_idx];
14,789,642✔
559
                if (!is_alive(adj_status)) { continue; }
14,789,642✔
560
                unsigned avenge_value = adj_status->skill(Skill::avenge);
13,019,751✔
561
                if (!avenge_value) { continue; }
13,019,751✔
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,235,290✔
566
                {
567
                    avenge_value = (avenge_value + 1) / 2;
1,835,399✔
568
                }
569
                _DEBUG_MSG(1, "%s activates %sAvenge %u\n",
3,408,616✔
570
                        status_description(adj_status).c_str(),
571
                        (std::abs((signed)from_idx - (signed)host_idx) > 1 ? "Half-" : ""),
572
                         avenge_value);
3,235,290✔
573
                if (!adj_status->m_sundered)
3,235,290✔
574
                { adj_status->m_perm_attack_buff += avenge_value; }
3,010,759✔
575
                adj_status->ext_hp(avenge_value);
3,235,290✔
576
            }
577

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

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

656
//------------------------------------------------------------------------------
657
void(*skill_table[Skill::num_skills])(Field*, CardStatus* src, const SkillSpec&);
658
void resolve_skill(Field* fd)
156,168,800✔
659
{
660
    while (!fd->skill_queue.empty())
256,361,031✔
661
    {
662
        auto skill_instance(fd->skill_queue.front());
100,192,231✔
663
        auto& status(std::get<0>(skill_instance));
100,192,231✔
664
        const auto& ss(std::get<1>(skill_instance));
100,192,231✔
665
        fd->skill_queue.pop_front();
100,192,231✔
666
        if (__builtin_expect(status->m_card->m_skill_trigger[ss.id] == Skill::Trigger::activate, true))
100,192,231✔
667
        {
668
            if (!is_alive(status))
96,121,833✔
669
            {
670
                _DEBUG_MSG(2, "%s failed to %s because it is dead.\n",
3,638✔
671
                        status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
3,638✔
672
                continue;
2,841,916✔
673
            }
3,638✔
674
            if (status->m_jammed)
96,118,195✔
675
            {
676
                _DEBUG_MSG(2, "%s failed to %s because it is Jammed.\n",
1,360✔
677
                        status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
1,360✔
678
                continue;
1,360✔
679
            }
1,360✔
680
        }
681

682
        // is summon? (non-activation skill)
683
        if (ss.id == Skill::summon)
100,187,233✔
684
        {
685
            check_and_perform_summon(fd, status);
2,759,603✔
686
            continue;
2,759,603✔
687
        }
688
        _DEBUG_ASSERT(is_activation_skill(ss.id) || ss.id == Skill::enhance); // enhance is no trigger, but  queues the skill
97,521,019✔
689

690
        SkillSpec modified_s = ss;
97,427,630✔
691

692
        // apply evolve
693
        signed evolved_offset = status->m_evolved_skill_offset[modified_s.id];
97,427,630✔
694
        if (evolved_offset != 0)
97,427,630✔
695
        { modified_s = apply_evolve(modified_s, evolved_offset); }
171,287✔
696

697
        // apply sabotage (only for X-based activation skills)
698
        unsigned sabotaged_value = status->m_sabotaged;
97,427,630✔
699
        if ((sabotaged_value > 0) && is_activation_skill_with_x(modified_s.id))
97,427,630✔
700
        { modified_s = apply_sabotage(modified_s, sabotaged_value); }
228,841✔
701

702
        // apply enhance
703
        unsigned enhanced_value = status->enhanced(modified_s.id);
97,427,630✔
704
        if (enhanced_value > 0)
97,427,630✔
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)
97,427,630✔
709
        {
710
            _DEBUG_MSG(2, "%s failed to %s because its X value is zeroed (sabotaged).\n",
77,315✔
711
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
77,315✔
712
            continue;
77,315✔
713
        }
77,315✔
714
        else { skill_table[modified_s.id](fd, status, modified_s); }
97,350,315✔
715
    }
716
}
156,168,800✔
717

718
void apply_corrosion(CardStatus * status)
18,406,207✔
719
{
720
      if (status->m_corroded_rate)
18,406,207✔
721
      {
722
        unsigned v = std::min(status->m_corroded_rate, status->attack_power());
99,889✔
723
        unsigned corrosion = std::min(v, status->m_card->m_attack
199,778✔
724
                + status->m_perm_attack_buff - status->m_corroded_weakened);
99,889✔
725
        _DEBUG_MSG(1, "%s loses Attack by %u (+corrosion %u).\n", status_description(status).c_str(), v, corrosion);
99,889✔
726
        status->m_corroded_weakened += corrosion;
99,889✔
727
      }
728
}
18,406,207✔
729
void remove_corrosion(CardStatus * status)
2,759,630✔
730
{
731
       if (status->m_corroded_rate)
2,759,630✔
732
       {
733
           _DEBUG_MSG(1, "%s loses Status corroded.\n", status_description(status).c_str());
5,011✔
734
           status->m_corroded_rate = 0;
5,011✔
735
           status->m_corroded_weakened = 0;
5,011✔
736
       }
737
}
2,759,630✔
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)
52,532,353✔
750
{
751
    _DEBUG_ASSERT(status);
52,532,353✔
752
    unsigned num_actions(1);
753
    for (unsigned action_index(0); action_index < num_actions; ++ action_index)
109,879,601✔
754
    {
755
        status->m_action_index = action_index;
58,366,024✔
756
        fd->prepare_action();
58,366,024✔
757
        _DEBUG_ASSERT(fd->skill_queue.size() == 0);
58,366,024✔
758
        for (auto & ss: skills)
224,630,305✔
759
        {
760
            if (!is_activation_skill(ss.id)) { continue; }
235,916,387✔
761
            if (status->m_skill_cd[ss.id] > 0) { continue; }
96,612,175✔
762
            _DEBUG_MSG(2, "Evaluating %s skill %s\n",
95,877,882✔
763
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
764
            fd->skill_queue.emplace_back(status, ss);
95,877,882✔
765
            resolve_skill(fd);
95,877,882✔
766
        }
767
        if (type == CardType::assault)
768
        {
769
            // Attack
770
            if (can_act(status))
22,184,613✔
771
            {
772
                if (attack_phase(fd))
22,037,256✔
773
                {
774
                    *attacked = true;
19,424,983✔
775
                    if (__builtin_expect(fd->end, false)) { break; }
19,424,983✔
776
                    //Apply corrosion
777
                    apply_corrosion(status);
18,406,207✔
778
                }
779
                else
780
                {
781
                  // Remove Corrosion
782
                  remove_corrosion(status);
2,612,273✔
783
                }
784
            }
785
            else
786
            {
787
                _DEBUG_MSG(2, "%s cannot take attack.\n", status_description(status).c_str());
147,357✔
788
                // Remove Corrosion
789
                remove_corrosion(status);
147,357✔
790
            }
791
        }
792
        fd->finalize_action();
57,347,248✔
793
        // Flurry
794
        if (can_act(status) && status->has_skill(Skill::flurry) && (status->m_skill_cd[Skill::flurry] == 0))
116,190,274✔
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,400,046✔
803
                    status_description(status).c_str(), status->skill_base_value(Skill::flurry));
804
            num_actions += status->skill_base_value(Skill::flurry);
2,262,027✔
805
            for (const auto & ss : skills)
9,045,620✔
806
            {
807
                Skill::Skill evolved_skill_id = static_cast<Skill::Skill>(ss.id + status->m_evolved_skill_offset[ss.id]);
6,783,593✔
808
                if (evolved_skill_id == Skill::flurry)
6,783,593✔
809
                {
810
                    status->m_skill_cd[ss.id] = ss.c;
2,262,027✔
811
                }
812
            }
813
        }
814
    }
815
}
52,532,353✔
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_) :
17,922,706✔
827
        card{card_},
17,922,706✔
828
        fd{fd_},
17,922,706✔
829
        status{nullptr},
17,922,706✔
830
        storage{nullptr},
17,922,706✔
831
        actor_index{ai_},
17,922,706✔
832
        actor_status{as_}
17,922,706✔
833
    {}
834

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

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

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

863
            //refresh/init absorb
864
            if(status->has_skill(Skill::absorb))
17,922,706✔
865
            {
866
                status->m_absorption = status->skill_base_value(Skill::absorb);
1,033,291✔
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)
70,237,171✔
873
            {
874
                if (status_i == status || !is_alive(status_i)) { continue; } // except itself
52,314,465✔
875
                //std::cout << status_description(status_i).c_str();
876
                _DEBUG_ASSERT(is_alive(status_i));
38,161,331✔
877
                if (bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction))
38,161,331✔
878
                {
879
                    ++ same_faction_cards_count;
14,737,448✔
880
                    unsigned allegiance_value = status_i->skill(Skill::allegiance);
14,737,448✔
881
                    if (__builtin_expect(allegiance_value, false) && !status->m_summoned)
14,737,448✔
882
                    {
883
                        _DEBUG_MSG(1, "%s activates Allegiance %u\n", status_description(status_i).c_str(), allegiance_value);
841,947✔
884
                        if (! status_i->m_sundered)
749,231✔
885
                        { status_i->m_perm_attack_buff += allegiance_value; }
718,202✔
886
                        status_i->ext_hp(allegiance_value);
749,231✔
887
                    }
888
                }
889
                if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::coldsleep], false)
38,161,331✔
890
                        && status_i->m_protected_stasis && can_be_healed(status_i))
90,475,796✔
891
                {
892
                    unsigned bge_value = (status_i->m_protected_stasis + 1) / 2;
2,553✔
893
                    _DEBUG_MSG(1, "Cold Sleep: %s heals itself for %u\n", status_description(status_i).c_str(), bge_value);
7,659✔
894
                    status_i->add_hp(bge_value);
2,553✔
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))
17,922,706✔
901
            {
902
                played_faction_mask = (1u << card->m_faction);
17,857,035✔
903
                // do played card have stasis? mark this faction for stasis check
904
                if (__builtin_expect(status->skill(Skill::stasis), false)
17,857,035✔
905
                        || __builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::temporalbacklash] && status->skill(Skill::counter), false))
17,857,035✔
906
                {
907
                    fd->players[status->m_player]->stasis_faction_bitmap |= played_faction_mask;
2,850,416✔
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)
17,922,706✔
914
                    && ((allegiance_value = status->skill(Skill::allegiance)) > 0))
17,922,706✔
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,167✔
918
                {
919
                    if ((status_i->m_card->m_category == CardCategory::normal)
915✔
920
                            && (bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)))
915✔
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,252✔
928
                {
929
                    unsigned bge_value = allegiance_value * same_faction_cards_count;
454✔
930
                    _DEBUG_MSG(1, "Oath of Loyalty: %s activates Allegiance %u x %u = %u\n",
908✔
931
                            status_description(status).c_str(), allegiance_value, same_faction_cards_count, bge_value);
932
                    status->m_perm_attack_buff += bge_value;
454✔
933
                    status->ext_hp(bge_value);
454✔
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)
16,592,454✔
941
                    && __builtin_expect(bge_megamorphosis || (fd->players[status->m_player]->stasis_faction_bitmap & played_faction_mask), false))
30,837,325✔
942
            {
943
                unsigned stacked_stasis = (bge_megamorphosis || (fd->players[status->m_player]->commander.m_card->m_faction == card->m_faction))
5,397,666✔
944
                    ? fd->players[status->m_player]->commander.skill(Skill::stasis)
9,587,844✔
945
                    : 0u;
946
#ifndef NDEBUG
947
                if (stacked_stasis > 0)
4,190,178✔
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)
10,443,248✔
955
                {
956
                    if ((bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)) && is_alive(status_i))
5,001,925✔
957
                    {
958
                        stacked_stasis += status_i->skill(Skill::stasis);
1,593,758✔
959
#ifndef NDEBUG
960
                        if (status_i->skill(Skill::stasis) > 0)
1,593,758✔
961
                        {
962
                            _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
5,001,925✔
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)
20,802,939✔
970
                {
971
                    if ((bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)) && is_alive(status_i))
15,361,616✔
972
                    {
973
                        stacked_stasis += status_i->skill(Skill::stasis);
11,681,644✔
974
#ifndef NDEBUG
975
                        if (status_i->skill(Skill::stasis) > 0)
11,681,644✔
976
                        {
977
                            _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
5,239,468✔
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))
11,681,644✔
983
                        {
984
                            stacked_stasis += (status_i->skill(Skill::counter) + 1) / 2;
2,986✔
985
#ifndef NDEBUG
986
                            _DEBUG_MSG(2, "Temporal Backlash: + Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
15,364,602✔
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;
5,441,323✔
994
#ifndef NDEBUG
995
                if (stacked_stasis > 0)
5,441,323✔
996
                {
997
                    _DEBUG_MSG(1, "%s gains %u stasis protection\n",
4,518,973✔
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))
5,441,323✔
1003
                {
1004
                    fd->players[status->m_player]->stasis_faction_bitmap &= ~played_faction_mask;
1,161,742✔
1005
                    _DEBUG_MSG(1, "- Stasis [%s]: no more units with stasis from %s\n",
1,171,367✔
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)
17,922,706✔
1012
                    && !summoned && card->m_category == CardCategory::normal
65,801✔
1013
                    && fd->players[status->m_player]->commander.m_card->m_faction == card->m_faction)
17,983,683✔
1014
            {
1015
                unsigned devotion_percent = fd->bg_effects[status->m_player][PassiveBGE::devotion];
9,656✔
1016
                unsigned bge_buff = (card->m_health*devotion_percent+99)/100;
9,656✔
1017
                _DEBUG_MSG(1, "Devotion %s: Gains %u HP\n",
19,312✔
1018
                        status_description(status).c_str(), bge_buff);
1019
                status->ext_hp(bge_buff); // <bge_value>% bonus health (rounded up)
9,656✔
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))
17,922,706✔
1026
            {
1027
                for (const auto& ss: card->m_skills_on_play)
21,393,618✔
1028
                {
1029
                    _DEBUG_MSG(2, "On Play %s: Preparing (tail) skill %s\n",
3,470,912✔
1030
                            status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
1031
                    fd->skill_queue.emplace_back(status, ss);
3,470,912✔
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;
17,922,706✔
1040
        }
1041

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

1047
    template <enum CardType::CardType type>
1048
        void placeCard(bool summoned)
17,922,706✔
1049
        {
1050
            status = &storage->add_back();
17,922,706✔
1051
            status->set(card);
17,922,706✔
1052
            status->m_index = storage->size() - 1;
17,922,706✔
1053
            status->m_player = actor_index;
17,922,706✔
1054
            status->m_field = fd;
17,922,706✔
1055
            status->m_summoned = summoned;
17,922,706✔
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)
17,922,706✔
1059
                && actor_status->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate
3,013,572✔
1060
                && actor_status->m_card->m_type == CardType::structure
257,228✔
1061
                && status->m_delay > 0)
8,409✔
1062
            {
1063
                --status->m_delay;
8,409✔
1064

1065
                _DEBUG_MSG(1, "%s reduces its timer (as summoned by tower)\n", status_description(status).c_str());
16,818✔
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",
20,478,883✔
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
        }
17,922,706✔
1082
};
1083
// assault
1084
    template <>
1085
void PlayCard::setStorage<CardType::assault>()
14,107,128✔
1086
{
1087
    storage = &fd->players[actor_index]->assaults;
14,107,128✔
1088
}
×
1089
// structure
1090
    template <>
1091
void PlayCard::setStorage<CardType::structure>()
3,815,578✔
1092
{
1093
    storage = &fd->players[actor_index]->structures;
3,815,578✔
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)
222,945,312✔
1099
{
1100
    return is_defensive_skill(skill_id) || is_alive(c);
2,336,094✔
1101
}
1102

1103
    template<>
1104
inline bool skill_check<Skill::heal>(Field* fd, CardStatus* c, CardStatus* ref)
66,753,470✔
1105
{
1106
    return can_be_healed(c);
39,991✔
1107
}
1108

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

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

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

1127
    template<>
1128
inline bool skill_check<Skill::jam>(Field* fd, CardStatus* c, CardStatus* ref)
9,592,535✔
1129
{
1130
    if(__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::ironwill], false) && c->has_skill(Skill::armor))return false;
9,592,535✔
1131
    // active player performs Jam
1132
    if (fd->tapi == ref->m_player)
9,590,401✔
1133
    { return is_active_next_turn(c); }
9,561,180✔
1134

1135

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

1140
    template<>
1141
inline bool skill_check<Skill::leech>(Field* fd, CardStatus* c, CardStatus* ref)
8,815✔
1142
{
1143
    return can_be_healed(c) || (fd->fixes[Fix::leech_increase_max_hp] && is_alive(c));
5,561✔
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,756,153✔
1154
{
1155
    return (ref->m_card->m_type == CardType::assault);
2,756,153✔
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)
103,054,772✔
1166
{
1167
    return (ref->m_card->m_type == CardType::assault) && (c != ref);
103,054,772✔
1168
}
1169

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

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

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

1188
    template<>
1189
inline bool skill_check<Skill::disease>(Field* fd, CardStatus* c, CardStatus* ref)
298,745✔
1190
{
1191
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
298,745✔
1192
}
1193
    template<>
1194
inline bool skill_check<Skill::inhibit>(Field* fd, CardStatus* c, CardStatus* ref)
2,246,229✔
1195
{
1196
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
2,246,229✔
1197
}
1198
    template<>
1199
inline bool skill_check<Skill::sabotage>(Field* fd, CardStatus* c, CardStatus* ref)
419,271✔
1200
{
1201
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
419,271✔
1202
}
1203
inline unsigned remove_disease(CardStatus* status, unsigned heal)
33,382,328✔
1204
{
1205
    unsigned remaining_heal(heal);
33,382,328✔
1206
    if(__builtin_expect(status->m_diseased == 0,true))
33,382,328✔
1207
    {
1208
        //skip
1209
    }
1210
    else if (heal > status->m_diseased)
108,786✔
1211
    {
1212
        _DEBUG_MSG(1, "%s disease-blocked %u heal\n", status_description(status).c_str(), status->m_diseased);
26,042✔
1213
        remaining_heal = heal - status->m_diseased;
26,042✔
1214
        status->m_diseased = 0;
26,042✔
1215
    }
1216
    else
1217
    {
1218
        _DEBUG_MSG(1, "%s disease-blocked %u heal\n", status_description(status).c_str(), heal);
82,744✔
1219
        status->m_diseased -= heal;
82,744✔
1220
        remaining_heal = 0;
82,744✔
1221
    }
1222
    return remaining_heal;
33,382,328✔
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,461,576✔
1227
{
1228
    return remove_absorption(status,dmg);
25,461,576✔
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,461,576✔
1238
{
1239
    unsigned remaining_dmg(dmg);
25,461,576✔
1240
    if(__builtin_expect(status->m_absorption == 0,true))
25,461,576✔
1241
    {
1242
        //skip
1243
    }
1244
    else if(dmg > status->m_absorption)
2,610,868✔
1245
    {
1246
        _DEBUG_MSG(1, "%s absorbs %u damage\n", status_description(status).c_str(), status->m_absorption);
372,755✔
1247
        remaining_dmg = dmg - status->m_absorption;
372,755✔
1248
        status->m_absorption = 0;
372,755✔
1249
    }
1250
    else
1251
    {
1252
        _DEBUG_MSG(1, "%s absorbs %u damage\n", status_description(status).c_str(), dmg);
2,238,113✔
1253
        status->m_absorption -= dmg;
2,238,113✔
1254
        remaining_dmg = 0;
2,238,113✔
1255
    }
1256
    return remaining_dmg;
25,461,576✔
1257
}
1258

1259
void remove_hp(Field* fd, CardStatus* status, unsigned dmg)
35,706,114✔
1260
{
1261
    if (__builtin_expect(!dmg, false)) { return; }
35,706,114✔
1262
    _DEBUG_ASSERT(is_alive(status));
24,671,058✔
1263
    _DEBUG_MSG(2, "%s takes %u damage\n", status_description(status).c_str(), dmg);
24,671,058✔
1264
    status->m_hp = safe_minus(status->m_hp, dmg);
24,671,058✔
1265
    if (fd->current_phase < Field::end_phase && status->has_skill(Skill::barrier))
24,671,058✔
1266
    {
1267
        ++fd->damaged_units_to_times[status];
260,281✔
1268
        _DEBUG_MSG(2, "%s damaged %u times\n",
260,281✔
1269
                status_description(status).c_str(), fd->damaged_units_to_times[status]);
1270
    }
1271
    if (status->m_hp == 0)
24,671,058✔
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());
8,465,553✔
1284
        _DEBUG_ASSERT(status->m_card->m_type != CardType::commander);
8,465,553✔
1285
        fd->killed_units.push_back(status);
8,465,553✔
1286
        ++fd->players[status->m_player]->total_cards_destroyed;
8,465,553✔
1287
        if(!status->m_summoned)++fd->players[status->m_player]->total_nonsummon_cards_destroyed;
8,465,553✔
1288
        if (__builtin_expect((status->m_player == 0) && (fd->players[0]->deck->vip_cards.count(status->m_card->m_id)), false))
11,324,800✔
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)
126,344,109✔
1297
{
1298
    if (c.m_hp == 0) // yes it is
126,344,109✔
1299
    {
1300
        _DEBUG_MSG(1, "Dead and removed: %s\n", status_description(&c).c_str());
8,465,553✔
1301
        return true;
8,465,553✔
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)
62,462,504✔
1312
{
1313
    storage.remove(is_it_dead);
62,462,504✔
1314
}
1315

1316
void cooldown_skills(CardStatus * status)
43,359,390✔
1317
{
1318
    for (const auto & ss : status->m_card->m_skills)
167,036,854✔
1319
    {
1320
        if (status->m_skill_cd[ss.id] > 0)
123,677,464✔
1321
        {
1322
            _DEBUG_MSG(2, "%s reduces timer (%u) of skill %s\n",
3,234,543✔
1323
                    status_description(status).c_str(), status->m_skill_cd[ss.id], skill_names[ss.id].c_str());
3,234,543✔
1324
            -- status->m_skill_cd[ss.id];
3,234,543✔
1325
        }
1326
    }
1327
}
43,359,390✔
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)
50,789,711✔
1335
{
1336
            //apply Absorb + Triggered\{Valor} Enhances
1337
            check_and_perform_early_enhance(fd,status);
101,579,422✔
1338
            if(fd->fixes[Fix::enhance_early])check_and_perform_later_enhance(fd,status);
50,789,711✔
1339
            //refresh absorb
1340
            if(status->has_skill(Skill::absorb))
50,789,711✔
1341
            {
1342
                status->m_absorption = status->skill_base_value(Skill::absorb);
3,421,931✔
1343
            }
1344
            if(__builtin_expect(fd->fixes[Fix::barrier_each_turn],true) && status->has_skill(Skill::barrier)){
50,789,711✔
1345
                _DEBUG_MSG(1,"%s gets barrier protection %u per turn\n",status_description(status).c_str(),status->skill(Skill::barrier));
876,294✔
1346
                status->m_protected += status->skill(Skill::barrier);
876,294✔
1347
            }
1348
            check_and_perform_bravery(fd,status);
50,789,711✔
1349
            if (status->m_delay > 0)
50,789,711✔
1350
            {
1351
                _DEBUG_MSG(1, "%s reduces its timer\n", status_description(status).c_str());
23,045,947✔
1352
                --status->m_delay;
23,045,947✔
1353
                if (status->m_delay == 0)
23,045,947✔
1354
                {
1355
                    check_and_perform_valor(fd, status);
10,580,248✔
1356
                    if (status->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate)
10,580,248✔
1357
                    {
1358
                        check_and_perform_summon(fd, status);
8,495,489✔
1359
                    }
1360
                }
1361
            }
1362
            else
1363
            {
1364
                cooldown_skills(status);
27,743,764✔
1365
            }
1366
}
50,789,711✔
1367

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

1376
    //Perform early enhance for commander
1377
    check_and_perform_early_enhance(fd,&fd->tap->commander);
31,231,252✔
1378
    if(fd->fixes[Fix::enhance_early])check_and_perform_later_enhance(fd,&fd->tap->commander);
15,615,626✔
1379

1380
    // Active player's structure cards:
1381
    // update index
1382
    // reduce delay; reduce skill cooldown
1383
    {
15,615,626✔
1384
        auto& structures(fd->tap->structures);
15,615,626✔
1385
        for(unsigned index(0); index < structures.size(); ++index)
35,436,921✔
1386
        {
1387
            CardStatus * status = &structures[index];
19,821,295✔
1388
            status->m_index = index;
19,821,295✔
1389
            turn_start_phase_update(fd,status);
19,821,295✔
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)
46,584,042✔
1397
        {
1398
            CardStatus * status = &assaults[index];
30,968,416✔
1399
            status->m_index = index;
30,968,416✔
1400
            turn_start_phase_update(fd,status);
30,968,416✔
1401
        }
1402
    }
1403
    // Defending player's structure cards:
1404
    // update index
1405
    {
15,615,626✔
1406
        auto& structures(fd->tip->structures);
15,615,626✔
1407
        for(unsigned index(0), end(structures.size()); index < end; ++index)
36,394,380✔
1408
        {
1409
            CardStatus& status(structures[index]);
20,778,754✔
1410
            status.m_index = index;
20,778,754✔
1411
        }
1412
    }
1413
    // Defending player's assault cards:
1414
    // update index
1415
    {
15,615,626✔
1416
        auto& assaults(fd->tip->assaults);
15,615,626✔
1417
        for(unsigned index(0), end(assaults.size()); index < end; ++index)
53,635,097✔
1418
        {
1419
            CardStatus& status(assaults[index]);
38,019,471✔
1420
            status.m_index = index;
38,019,471✔
1421
        }
1422
    }
1423
}
15,615,626✔
1424

1425
void turn_end_phase(Field* fd)
15,615,626✔
1426
{
1427
    // Inactive player's assault cards:
1428
    {
15,615,626✔
1429
        auto& assaults(fd->tip->assaults);
15,615,626✔
1430
        for(unsigned index(0), end(assaults.size()); index < end; ++ index)
53,688,281✔
1431
        {
1432
            CardStatus& status(assaults[index]);
38,072,655✔
1433
            if (status.m_hp <= 0)
38,072,655✔
1434
            {
1435
                continue;
6,565,861✔
1436
            }
1437
            status.m_enfeebled = 0;
31,506,794✔
1438
            status.m_protected = 0;
31,506,794✔
1439
            std::memset(status.m_primary_skill_offset, 0, sizeof status.m_primary_skill_offset);
31,506,794✔
1440
            std::memset(status.m_evolved_skill_offset, 0, sizeof status.m_evolved_skill_offset);
31,506,794✔
1441
            std::memset(status.m_enhanced_value, 0, sizeof status.m_enhanced_value);
31,506,794✔
1442
            status.m_evaded = 0;  // so far only useful in Inactive turn
31,506,794✔
1443
            status.m_paybacked = 0;  // ditto
31,506,794✔
1444
            status.m_entrapped = 0;
31,506,794✔
1445
        }
1446
    }
1447
    // Inactive player's structure cards:
1448
    {
15,615,626✔
1449
        auto& structures(fd->tip->structures);
15,615,626✔
1450
        for(unsigned index(0), end(structures.size()); index < end; ++ index)
36,398,207✔
1451
        {
1452
            CardStatus& status(structures[index]);
20,782,581✔
1453
            if (status.m_hp <= 0)
20,782,581✔
1454
            {
1455
                continue;
997,214✔
1456
            }
1457
            // reset the structure's (barrier) protect
1458
            status.m_protected = 0;
19,785,367✔
1459
            status.m_evaded = 0;  // so far only useful in Inactive turn
19,785,367✔
1460
        }
1461
    }
1462

1463
    // Active player's assault cards:
1464
    {
15,615,626✔
1465
        auto& assaults(fd->tap->assaults);
15,615,626✔
1466
        for(unsigned index(0), end(assaults.size()); index < end; ++ index)
60,637,748✔
1467
        {
1468
            CardStatus& status(assaults[index]);
45,022,122✔
1469
            if (status.m_hp <= 0)
45,022,122✔
1470
            {
1471
                continue;
850,672✔
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
44,171,450✔
1474

1475
            if (refresh_value && skill_check<Skill::refresh>(fd, &status, nullptr))
44,171,450✔
1476
            {
1477
                _DEBUG_MSG(1, "%s refreshes %u health\n", status_description(&status).c_str(), refresh_value);
120,741✔
1478
                status.add_hp(refresh_value);
120,741✔
1479
            }
1480
            if (status.m_poisoned > 0)
44,171,450✔
1481
            {
1482
                if(! __builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
245,601✔
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;
245,601✔
1500
                    _DEBUG_MSG(1, "%s takes poison damage %u\n", status_description(&status).c_str(), poison_dmg);
245,601✔
1501
                    remove_hp(fd, &status, poison_dmg);  // simultaneous
245,601✔
1502
                    status.m_poisoned = status.m_poisoned - (poison_dmg+1)/2;
245,601✔
1503
                }
1504
            }
1505
            // end of the opponent's next turn for enemy units
1506
            status.m_temp_attack_buff = 0;
44,171,450✔
1507
            status.m_jammed = false;
44,171,450✔
1508
            status.m_enraged = 0;
44,171,450✔
1509
            status.m_sundered = false;
44,171,450✔
1510
            status.m_inhibited = 0;
44,171,450✔
1511
            status.m_sabotaged = 0;
44,171,450✔
1512
            status.m_tributed = 0;
44,171,450✔
1513
            status.m_overloaded = false;
44,171,450✔
1514
            status.m_step = CardStep::none;
44,171,450✔
1515
        }
1516
    }
1517
    // Active player's structure cards:
1518
    // nothing so far
1519

1520
    prepend_on_death(fd);  // poison
15,615,626✔
1521
    resolve_skill(fd);
15,615,626✔
1522
    remove_dead(fd->tap->assaults);
15,615,626✔
1523
    remove_dead(fd->tap->structures);
15,615,626✔
1524
    remove_dead(fd->tip->assaults);
15,615,626✔
1525
    remove_dead(fd->tip->structures);
15,615,626✔
1526
}
15,615,626✔
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,163,050✔
1540
{
1541
    _DEBUG_ASSERT(att->m_card->m_type == CardType::assault);
2,163,050✔
1542
    _DEBUG_ASSERT(def->skill(Skill::counter) > 0); // counter skill must be present otherwise enfeeble is wrongly applied
2,163,050✔
1543

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

1547
inline CardStatus* select_first_enemy_wall(Field* fd)
7,162,697✔
1548
{
1549
    for(unsigned i(0); i < fd->tip->structures.size(); ++i)
13,802,315✔
1550
    {
1551
        CardStatus* c(&fd->tip->structures[i]);
7,443,231✔
1552
        if (c->has_skill(Skill::wall) && is_alive(c)) { return c; }
7,443,231✔
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)
97,362✔
1560
    {
1561
        CardStatus* c(&fd->tip->assaults[i]);
72,986✔
1562
        if (is_alive(c)) { return c; }
72,986✔
1563
    }
1564
    return nullptr;
1565
}
1566

1567

1568
inline bool alive_assault(Storage<CardStatus>& assaults, unsigned index)
19,424,983✔
1569
{
1570
    return(assaults.size() > index && is_alive(&assaults[index]));
13,512,763✔
1571
}
1572

1573
void remove_commander_hp(Field* fd, CardStatus& status, unsigned dmg)
6,358,743✔
1574
{
1575
    _DEBUG_ASSERT(is_alive(&status));
6,358,743✔
1576
    _DEBUG_ASSERT(status.m_card->m_type == CardType::commander);
6,358,743✔
1577
    _DEBUG_MSG(2, "%s takes %u damage\n", status_description(&status).c_str(), dmg);
6,358,743✔
1578
    status.m_hp = safe_minus(status.m_hp, dmg);
6,358,743✔
1579
    if (status.m_hp == 0)
6,358,743✔
1580
    {
1581
        _DEBUG_MSG(1, "%s dies\n", status_description(&status).c_str());
1,018,776✔
1582
        fd->end = true;
1,018,776✔
1583
    }
1584
}
6,358,743✔
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_) :
19,424,983✔
1597
        fd(fd_), att_status(att_status_), def_status(def_status_), att_dmg(0)
19,424,983✔
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()
19,424,983✔
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; }
19,424,983✔
1633

1634

1635
            if(__builtin_expect(def_status->has_skill(Skill::flying),false) && fd->flip()) {
19,424,983✔
1636
                _DEBUG_MSG(1, "%s flies away from %s\n",
99,844✔
1637
                        status_description(def_status).c_str(),
1638
                        status_description(att_status).c_str());
1639
                return 0;
49,922✔
1640
            }
1641

1642
            if ( __builtin_expect(fd->fixes[Fix::subdue_before_attack],true)) {
19,375,061✔
1643
                perform_subdue(fd, att_status, def_status);
19,375,061✔
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."
19,375,061✔
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();
19,372,413✔
1653

1654
            modify_attack_damage<def_cardtype>(pre_modifier_dmg);
19,372,413✔
1655

1656
            if(att_dmg) {
19,372,413✔
1657
                // Deal the attack damage
1658
                attack_damage<def_cardtype>();
16,357,680✔
1659

1660
                // Game Over -> return
1661
                if (__builtin_expect(fd->end, false)) { return att_dmg; }
16,357,680✔
1662
                damage_dependant_pre_oa<def_cardtype>();
9,195,324✔
1663
            }
1664
            // on attacked does also trigger if the attack damage is blocked to zero
1665
            on_attacked<def_cardtype>();
18,353,637✔
1666

1667
            // only if alive attacker
1668
            if (is_alive(att_status) && (__builtin_expect(fd->fixes[Fix::counter_without_damage],true) || att_dmg) )
18,353,637✔
1669
            {
1670
                perform_counter<def_cardtype>(fd, att_status, def_status);    
18,339,521✔
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"
18,353,637✔
1673
            {
1674
                perform_corrosive(fd, att_status, def_status);
17,597,680✔
1675
            }
1676
            if (is_alive(att_status) && att_dmg)
18,353,637✔
1677
            {
1678
                // Bug fix? 2023-04-03 leech after counter but before drain/berserk
1679
                // Skill: Leech
1680
                do_leech<def_cardtype>();
9,027,867✔
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);
18,353,637✔
1686

1687
            if (is_alive(att_status) && att_dmg) {
18,353,637✔
1688
                perform_berserk(fd, att_status, def_status);
14,647,354✔
1689
            }
1690
            if (is_alive(att_status) )  {
18,353,637✔
1691
                perform_poison(fd, att_status, def_status);
17,597,680✔
1692
            }
1693

1694
            if (is_alive(att_status) && att_dmg) {
18,353,637✔
1695
                perform_mark(fd, att_status, def_status);
14,647,354✔
1696

1697
                perform_bge_heroism<def_cardtype>(fd, att_status, def_status);
14,647,354✔
1698
                perform_bge_devour<def_cardtype>(fd, att_status, def_status);
14,647,354✔
1699

1700
                if ( __builtin_expect(!fd->fixes[Fix::subdue_before_attack],false)) {
14,647,354✔
1701
                    perform_subdue(fd, att_status, def_status);
×
1702
                }
1703
            }
1704
            // perform hunt
1705
            perform_hunt(fd, att_status, def_status);
18,353,637✔
1706

1707
            return att_dmg;
18,353,637✔
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)
19,372,413✔
1719
        {
1720
            _DEBUG_ASSERT(att_status->m_card->m_type == CardType::assault);
19,372,413✔
1721
            att_dmg = pre_modifier_dmg;
19,372,413✔
1722
            if (att_dmg == 0)
19,372,413✔
1723
            { return; }
×
1724
#ifndef NDEBUG
1725
            std::string desc;
19,372,413✔
1726
#endif
1727
            auto& att_assaults = fd->tap->assaults; // (active) attacker assaults
19,372,413✔
1728
            auto& def_assaults = fd->tip->assaults; // (inactive) defender assaults
19,372,413✔
1729
            unsigned legion_value = 0;
19,372,413✔
1730
            unsigned coalition_value = 0;
19,372,413✔
1731

1732
            // Enhance damage (if additional damage isn't prevented)
1733
            if (! att_status->m_sundered)
19,372,413✔
1734
            {
1735
                // Skill: Mark
1736
                unsigned marked_value = def_status->m_marked;
18,317,767✔
1737
                if(marked_value)
18,317,767✔
1738
                {
1739
#ifndef NDEBUG
1740
                    if (debug_print > 0) { desc += "+" + tuo::to_string(marked_value) + "(mark)"; }
546,701✔
1741
#endif
1742
                    att_dmg += marked_value;
470,495✔
1743
                }
1744
                // Skill: Legion
1745
                unsigned legion_base = att_status->skill(Skill::legion);
18,317,767✔
1746
                if (__builtin_expect(legion_base, false))
18,317,767✔
1747
                {
1748
                    unsigned itr_idx, till_idx;
1749
                    bool bge_megamorphosis = fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis] && !fd->fixes[Fix::legion_under_mega];
724,471✔
1750
                    //scan all assaults for Global Legion
1751
                    itr_idx = 0;
724,471✔
1752
                    till_idx = att_assaults.size() - 1;
724,471✔
1753
                    for (; itr_idx <= till_idx; ++ itr_idx)
3,458,947✔
1754
                    {
1755
                        if(itr_idx == att_status->m_index)continue; //legion doesn't count itself, unlike coalition
2,734,476✔
1756
                        legion_value += is_alive(&att_assaults[itr_idx])
4,010,635✔
1757
                          && (bge_megamorphosis || (att_assaults[itr_idx].m_card->m_faction == att_status->m_card->m_faction));
2,010,005✔
1758
                    }
1759
                    if (legion_value)
724,471✔
1760
                    {
1761
                        legion_value *= legion_base;
115,415✔
1762
#ifndef NDEBUG
1763
                        if (debug_print > 0) { desc += "+" + tuo::to_string(legion_value) + "(legion)"; }
376,880✔
1764
#endif
1765
                        att_dmg += legion_value;
115,415✔
1766
                    }
1767
                }
1768

1769
                // Skill: Coalition
1770
                unsigned coalition_base = att_status->skill(Skill::coalition);
18,317,767✔
1771
                if (__builtin_expect(coalition_base, false))
18,317,767✔
1772
                {
1773
                    uint8_t factions_bitmap = 0;
1,924,241✔
1774
                    for (CardStatus * status : att_assaults.m_indirect)
12,393,720✔
1775
                    {
1776
                        if (! is_alive(status)) { continue; }
10,469,479✔
1777
                        factions_bitmap |= (1 << (status->m_card->m_faction));
10,446,774✔
1778
                    }
1779
                    _DEBUG_ASSERT(factions_bitmap);
1,924,241✔
1780
                    unsigned uniq_factions = byte_bits_count(factions_bitmap);
1,924,241✔
1781
                    coalition_value = coalition_base * uniq_factions;
1,924,241✔
1782
#ifndef NDEBUG
1783
                    if (debug_print > 0) { desc += "+" + tuo::to_string(coalition_value) + "(coalition/x" + tuo::to_string(uniq_factions) + ")"; }
2,557,413✔
1784
#endif
1785
                    att_dmg += coalition_value;
1,924,241✔
1786
                }
1787

1788
                // Skill: Rupture
1789
                unsigned rupture_value = att_status->skill(Skill::rupture);
18,317,767✔
1790
                if (rupture_value > 0)
18,317,767✔
1791
                {
1792
#ifndef NDEBUG
1793
                    if (debug_print > 0) { desc += "+" + tuo::to_string(rupture_value) + "(rupture)"; }
1,081,635✔
1794
#endif
1795
                    att_dmg += rupture_value;
793,878✔
1796
                }
1797

1798
                // Skill: Venom
1799
                unsigned venom_value = att_status->skill(Skill::venom);
18,317,767✔
1800
                if (venom_value > 0 && def_status->m_poisoned > 0)
18,317,767✔
1801
                {
1802
#ifndef NDEBUG
1803
                    if (debug_print > 0) { desc += "+" + tuo::to_string(venom_value) + "(venom)"; }
14,684✔
1804
#endif
1805
                    att_dmg += venom_value;
3,671✔
1806
                }
1807

1808
                // Passive BGE: Bloodlust
1809
                if (fd->bloodlust_value > 0)
18,317,767✔
1810
                {
1811
#ifndef NDEBUG
1812
                    if (debug_print > 0) { desc += "+" + tuo::to_string(fd->bloodlust_value) + "(bloodlust)"; }
216,940✔
1813
#endif
1814
                    att_dmg += fd->bloodlust_value;
54,235✔
1815
                }
1816

1817
                // State: Enfeebled
1818
                if (def_status->m_enfeebled > 0)
18,317,767✔
1819
                {
1820
#ifndef NDEBUG
1821
                    if (debug_print > 0) { desc += "+" + tuo::to_string(def_status->m_enfeebled) + "(enfeebled)"; }
3,236,111✔
1822
#endif
1823
                    att_dmg += def_status->m_enfeebled;
2,281,709✔
1824
                }
1825
            }
1826
            // prevent damage
1827
#ifndef NDEBUG
1828
            std::string reduced_desc;
19,372,413✔
1829
#endif
1830
            unsigned reduced_dmg(0); // damage reduced by armor, protect and in turn again canceled by pierce, etc.
19,372,413✔
1831
            unsigned armor_value = 0;
19,372,413✔
1832
            // Armor
1833
            if (def_status->m_card->m_type == CardType::assault) {
19,372,413✔
1834
                // Passive BGE: Fortification (adj step -> 1 (1 left, host, 1 right)
1835
                unsigned adj_size = (unsigned)(fd->bg_effects[fd->tapi][PassiveBGE::fortification]);
12,210,057✔
1836
                unsigned host_idx = def_status->m_index;
12,210,057✔
1837
                unsigned from_idx = safe_minus(host_idx, adj_size);
12,210,057✔
1838
                unsigned till_idx = std::min(host_idx + adj_size, safe_minus(def_assaults.size(), 1));
22,680,468✔
1839
                for (; from_idx <= till_idx; ++ from_idx)
24,451,501✔
1840
                {
1841
                    CardStatus* adj_status = &def_assaults[from_idx];
12,241,444✔
1842
                    if (!is_alive(adj_status)) { continue; }
12,241,444✔
1843
                    armor_value = std::max(armor_value, adj_status->skill(Skill::armor));
15,465,925✔
1844
                }
1845
            }
1846
            if (armor_value > 0)
19,372,413✔
1847
            {
1848
#ifndef NDEBUG
1849
                if(debug_print > 0) { reduced_desc += tuo::to_string(armor_value) + "(armor)"; }
3,560,185✔
1850
#endif
1851
                reduced_dmg += armor_value;
1852
            }
1853
            if (def_status->protected_value() > 0)
19,372,413✔
1854
            {
1855
#ifndef NDEBUG
1856
                if(debug_print > 0) { reduced_desc += (reduced_desc.empty() ? "" : "+") + tuo::to_string(def_status->protected_value()) + "(protected)"; }
10,213,150✔
1857
#endif
1858
                reduced_dmg += def_status->protected_value();
7,375,237✔
1859
            }
1860
            unsigned pierce_value = att_status->skill(Skill::pierce);
19,372,413✔
1861
            if (reduced_dmg > 0 && pierce_value > 0)
19,372,413✔
1862
            {
1863
#ifndef NDEBUG
UNCOV
1864
                if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(pierce_value) + "(pierce)"; }
×
1865
#endif
1866
                reduced_dmg = safe_minus(reduced_dmg, pierce_value);
19,372,413✔
1867
            }
1868
            unsigned rupture_value = att_status->skill(Skill::rupture);
19,372,413✔
1869
            if (reduced_dmg > 0 && rupture_value > 0)
19,372,413✔
1870
            {
1871
#ifndef NDEBUG
1872
                if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(rupture_value) + "(rupture)"; }
454,849✔
1873
#endif
1874
                reduced_dmg = safe_minus(reduced_dmg, rupture_value);
19,372,413✔
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."
19,372,413✔
1877
            {
1878
                unsigned corrosive_value = def_status->m_corroded_rate;
19,372,413✔
1879
                if (reduced_dmg > 0 && corrosive_value > 0)
19,372,413✔
1880
                {
1881
#ifndef NDEBUG
1882
                    if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(corrosive_value) + "(corrosive)"; }
26,202✔
1883
#endif
1884
                    reduced_dmg = safe_minus(reduced_dmg, corrosive_value);
19,372,413✔
1885
                }
1886
            }
1887
            att_dmg = safe_minus(att_dmg, reduced_dmg);
19,372,413✔
1888
#ifndef NDEBUG
1889
            if (debug_print > 0)
19,372,413✔
1890
            {
1891
                if(!reduced_desc.empty()) { desc += "-[" + reduced_desc + "]"; }
5,105,184✔
1892
                if(!desc.empty()) { desc += "=" + tuo::to_string(att_dmg); }
5,852,992✔
1893
                else { assert(att_dmg == pre_modifier_dmg); }
1,669,459✔
1894
                _DEBUG_MSG(1, "%s attacks %s for %u%s damage\n",
6,127,940✔
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)
19,372,413✔
1901
                    && can_be_healed(att_status))
2,153✔
1902
            {
1903
                _DEBUG_MSG(1, "Brigade: %s heals itself for %u\n",
512✔
1904
                        status_description(att_status).c_str(), legion_value);
1905
                att_status->add_hp(legion_value);
256✔
1906
            }
1907

1908
            // Passive BGE: Unity
1909
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::unity] && coalition_value, false)
19,372,413✔
1910
                    && can_be_healed(att_status))
19,373,759✔
1911
            {
1912
                _DEBUG_MSG(1, "Unity: %s heals itself for %u\n",
60✔
1913
                        status_description(att_status).c_str(), (coalition_value + 1)/2);
1914
                att_status->add_hp((coalition_value + 1)/2);
30✔
1915
            }
1916
        }
19,372,413✔
1917

1918
    template<enum CardType::CardType>
1919
        void attack_damage()
9,998,937✔
1920
        {
1921

1922
            remove_hp(fd, def_status, att_dmg);
9,998,937✔
1923
            prepend_on_death(fd);
9,998,937✔
1924
            resolve_skill(fd);
9,998,937✔
1925
        }
9,998,937✔
1926

1927
    template<enum CardType::CardType>
1928
        void on_attacked() {
18,353,637✔
1929
            //APN
1930
            // resolve On-Attacked skills
1931
            for (const auto& ss: def_status->m_card->m_skills_on_attacked)
18,574,255✔
1932
            {
1933
                _DEBUG_MSG(1, "On Attacked %s: Preparing (tail) skill %s\n",
441,236✔
1934
                        status_description(def_status).c_str(), skill_description(fd->cards, ss).c_str());
1935
                fd->skill_queue.emplace_back(def_status, ss);
220,618✔
1936
                resolve_skill(fd);
220,618✔
1937
            }
1938
        }
18,353,637✔
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>()
6,358,743✔
1953
{
1954
    remove_commander_hp(fd, *def_status, att_dmg);
6,358,743✔
1955
}
×
1956

1957
    template<>
1958
void PerformAttack::damage_dependant_pre_oa<CardType::assault>()
9,195,324✔
1959
{
1960
    if (!__builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
9,195,324✔
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);
9,195,324✔
1981
        if (venom_value > 0 && skill_check<Skill::venom>(fd, att_status, def_status)) {
9,195,324✔
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",
20,075✔
1990
                  status_description(att_status).c_str(),
1991
                  status_description(def_status).c_str(), venom_value);
20,075✔
1992
              // new venom keeps adding stacks
1993
              def_status->m_poisoned += venom_value;
20,075✔
1994
        }
1995
    }
1996
}
9,195,324✔
1997

1998

1999
    template<>
2000
void PerformAttack::do_leech<CardType::assault>()
9,027,867✔
2001
{
2002
    unsigned leech_value = std::min(att_dmg, att_status->skill(Skill::leech));
9,027,867✔
2003
    if(leech_value > 0 && skill_check<Skill::leech>(fd, att_status, nullptr))
9,027,867✔
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);
8,815✔
2012
        if (__builtin_expect(fd->fixes[Fix::leech_increase_max_hp],true)) {
8,815✔
2013
            att_status->ext_hp(leech_value);
8,815✔
2014
        }
2015
        else {
2016
            att_status->add_hp(leech_value);
×
2017
        }
2018
    }
2019
}
9,027,867✔
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,162,697✔
2023
{
2024
    CardStatus* def_status{select_first_enemy_wall(fd)}; // defending wall
7,162,697✔
2025
    if (def_status != nullptr)
7,162,697✔
2026
    {
2027
        return PerformAttack{fd, att_status, def_status}.op<CardType::structure>();
803,613✔
2028
    }
2029
    else
2030
    {
2031
        return PerformAttack{fd, att_status, &fd->tip->commander}.op<CardType::commander>();
6,359,084✔
2032
    }
2033
}
2034
// Return true if actually attacks
2035
bool attack_phase(Field* fd)
22,037,256✔
2036
{
2037
    CardStatus* att_status(&fd->tap->assaults[fd->current_ci]); // attacking card
22,037,256✔
2038
    Storage<CardStatus>& def_assaults(fd->tip->assaults);
22,037,256✔
2039

2040
    if (!att_status->attack_power())
22,037,256✔
2041
    {
2042
        _DEBUG_MSG(1, "%s cannot take attack (zeroed)\n", status_description(att_status).c_str());
2,612,273✔
2043
        return false;
2,612,273✔
2044
    }
2045

2046
    unsigned att_dmg = 0;
19,424,983✔
2047
    if (alive_assault(def_assaults, fd->current_ci))
19,424,983✔
2048
    {
2049
        CardStatus* def_status = &def_assaults[fd->current_ci];
12,262,286✔
2050
        att_dmg = PerformAttack{fd, att_status, def_status}.op<CardType::assault>();
12,262,286✔
2051
        
2052
    }
2053
    else
2054
    {
2055
        // might be blocked by walls
2056
        att_dmg = attack_commander(fd, att_status);
7,162,697✔
2057
    }
2058

2059
    // Passive BGE: Bloodlust
2060
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::bloodlust], false)
19,424,983✔
2061
            && !fd->assault_bloodlusted && (att_dmg > 0))
19,424,983✔
2062
    {
2063
        fd->bloodlust_value += fd->bg_effects[fd->tapi][PassiveBGE::bloodlust];
117,120✔
2064
        fd->assault_bloodlusted = true;
117,120✔
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)
316✔
2092
{ return skill_check<static_cast<Skill::Skill>(skill_id)>(fd, dst, src); }
345,039,224✔
2093

2094
    template<>
2095
inline bool skill_predicate<Skill::enhance>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
30,676,353✔
2096
{
2097
    if (!is_alive(dst)) return false;
30,676,353✔
2098
    if (!dst->has_skill(s.s)) return false;
61,352,704✔
2099
    if (is_active(dst)) return true;
6,571,703✔
2100
    if (is_defensive_skill(s.s)) return true;
3,394,482✔
2101
    if (s.s == Skill::hunt || s.s == Skill::mark) return true; // Combat-Modifier exceptions (game bug?)
456,718✔
2102
    if (is_instant_debuff_skill(s.s)) return true; // Enhance Sabotage, Inhibit, Disease also without dst being active
456,418✔
2103
    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)
456,318✔
2104

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

2113
    template<>
2114
inline bool skill_predicate<Skill::evolve>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,219,417✔
2115
{
2116
    if (!is_alive(dst)) return false;
2,219,417✔
2117
    if (!dst->has_skill(s.s)) return false;
4,432,474✔
2118
    if (dst->has_skill(s.s2)) return false;
1,349,368✔
2119
    if (is_active(dst)) return true;
674,684✔
2120
    if (is_defensive_skill(s.s2)) return true;
375,272✔
2121

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

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

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

2143
    for(std::vector<SkillSpec> & a  : all)
1,675,756✔
2144
    {
2145
    // scan all enemy skills until first activation
2146
    for (const SkillSpec & ss: a)
3,240,895✔
2147
    {
2148
        // get skill
2149
        Skill::Skill skill_id = static_cast<Skill::Skill>(ss.id);
2,625,258✔
2150

2151
        // skip non-activation skills and Mimic (Mimic can't be mimicked)
2152
        if (!is_activation_skill(skill_id) || (skill_id == Skill::mimic))
3,387,675✔
2153
        { continue; }
1,862,841✔
2154

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

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

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

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

2175
    // check skills
2176
    bool inhibited_searched = false;
342,324✔
2177
    for (const auto& ss: dst->m_card->m_skills)
957,443✔
2178
    {
2179
        // skip cooldown skills
2180
        if (dst->m_skill_cd[ss.id] > 0)
861,873✔
2181
        { continue; }
16,056✔
2182

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

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

2190
        // unit with an activation helpful skill is valid target only when there are inhibited units
2191
        // TODO check mend/fortify valid overload target?!?
2192
        if ((evolved_skill_id != Skill::mend && evolved_skill_id != Skill::fortify)
604,552✔
2193
                && is_activation_helpful_skill(evolved_skill_id)
615,119✔
2194
                && __builtin_expect(!inhibited_searched, true))
775,241✔
2195
        {
2196
            for (const auto & c: fd->players[dst->m_player]->assaults.m_indirect)
905,019✔
2197
            {
2198
                if (is_alive(c) && c->m_inhibited)
745,125✔
2199
                { return true; }
246,754✔
2200
            }
2201
            inhibited_searched = true;
2202
        }
2203
    }
2204
    return false;
2205
}
2206

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

2218
    template<>
2219
inline bool skill_predicate<Skill::enrage>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
30,706,834✔
2220
{
2221
    return (__builtin_expect((fd->tapi == dst->m_player), true) // is target on the active side?
30,706,834✔
2222
            ? is_active(dst) && (dst->m_step == CardStep::none) // normal case
15,162,877✔
2223
            : is_active_next_turn(dst) // on-death activation
45,320,609✔
2224
           )
2225
        && (dst->attack_power()) // card can perform direct attack
45,320,609✔
2226
        ;
2227
}
2228

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

2236
    template<>
2237
inline bool skill_predicate<Skill::weaken>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
33,128,848✔
2238
{
2239
    if(__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::ironwill], false) && dst->has_skill(Skill::armor))return false;
33,128,848✔
2240
    if (!dst->attack_power()) { return false; }
33,125,398✔
2241

2242
    // active player performs Weaken (normal case)
2243
    if (__builtin_expect((fd->tapi == src->m_player), true))
28,808,571✔
2244
    { return is_active_next_turn(dst); }
28,682,886✔
2245

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

2248
    // inactive player performs Weaken (inverted case (on-death activation))
2249
    return will_activate_this_turn(dst);
125,685✔
2250
}
2251

2252
    template<>
2253
inline bool skill_predicate<Skill::sunder>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
18,218,451✔
2254
{
2255
    return skill_predicate<Skill::weaken>(fd, src, dst, s);
106✔
2256
}
2257

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

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

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

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

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

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

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

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

2314
    template<>
2315
inline void perform_skill<Skill::fortify>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,512,143✔
2316
{
2317
    dst->ext_hp(s.x);
1,824,640✔
2318
}
687,503✔
2319

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

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

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

2353
    template<>
2354
inline void perform_skill<Skill::protect>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
22,435,985✔
2355
{
2356
    dst->m_protected += s.x;
22,435,985✔
2357
}
492✔
2358

2359
    template<>
2360
inline void perform_skill<Skill::rally>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
9,070,494✔
2361
{
2362
    dst->m_temp_attack_buff += s.x;
9,070,494✔
2363
}
242,533✔
2364

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

2381
    template<>
2382
inline void perform_skill<Skill::entrap>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
5,924,596✔
2383
{
2384
    dst->m_entrapped += s.x;
5,924,596✔
2385
}
89,830✔
2386

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

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

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

2412
    template<>
2413
inline void perform_skill<Skill::sunder>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,200,577✔
2414
{
2415
    dst->m_sundered = true;
3,200,577✔
2416
    perform_skill<Skill::weaken>(fd, src, dst, s);
3,166,774✔
2417
}
×
2418

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

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

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

2445
        mimickable_skills.emplace_back(&ss);
684,931✔
2446
        _DEBUG_MSG(2, "  + %s\n", skill_description(fd->cards, ss).c_str());
1,572,594✔
2447
    }
2448
    }
2449

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

2467
    template<unsigned skill_id>
2468
inline unsigned select_fast(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
110,606,888✔
2469
{
2470
    if ((s.y == allfactions)
110,606,888✔
2471
            || fd->bg_effects[fd->tapi][PassiveBGE::metamorphosis]
18,867,123✔
2472
            || fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis])
129,317,264✔
2473
    {
2474
        auto pred = [fd, src, s](CardStatus* c) {
355,026,747✔
2475
            return(skill_predicate<skill_id>(fd, src, c, s));
307,056,605✔
2476
        };
2477
        return fd->make_selection_array(cards.begin(), cards.end(), pred);
92,051,833✔
2478
    }
2479
    else
2480
    {
2481
        auto pred = [fd, src, s](CardStatus* c) {
97,124,093✔
2482
            return ((c->m_card->m_faction == s.y || c->m_card->m_faction == progenitor) && skill_predicate<skill_id>(fd, src, c, s));
92,973,852✔
2483
        };
2484
        return fd->make_selection_array(cards.begin(), cards.end(), pred);
18,555,055✔
2485
    }
2486
}
2487

2488
    template<>
2489
inline unsigned select_fast<Skill::mend>(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
121,922✔
2490
{
2491
    fd->selection_array.clear();
121,922✔
2492
    bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
121,922✔
2493
    auto& assaults = fd->players[src->m_player]->assaults;
121,922✔
2494
    unsigned adj_size = 1 + (unsigned)(critical_reach);
121,922✔
2495
    unsigned host_idx = src->m_index;
121,922✔
2496
    unsigned from_idx = safe_minus(host_idx, adj_size);
121,922✔
2497
    unsigned till_idx = std::min(host_idx + adj_size, safe_minus(assaults.size(), 1));
238,547✔
2498
    for (; from_idx <= till_idx; ++ from_idx)
421,709✔
2499
    {
2500
        if (from_idx == host_idx) { continue; }
301,199✔
2501
        CardStatus* adj_status = &assaults[from_idx];
177,865✔
2502
        if (!is_alive(adj_status)) { continue; }
177,865✔
2503
        if (skill_predicate<Skill::mend>(fd, src, adj_status, s))
352,906✔
2504
        {
2505
            fd->selection_array.push_back(adj_status);
4,480✔
2506
        }
2507
    }
2508
    return fd->selection_array.size();
121,922✔
2509
}
2510

2511
    template<>
2512
inline unsigned select_fast<Skill::fortify>(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
1,236,481✔
2513
{
2514
    fd->selection_array.clear();
1,236,481✔
2515
    bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
1,236,481✔
2516
    auto& assaults = fd->players[src->m_player]->assaults;
1,236,481✔
2517
    unsigned adj_size = 1 + (unsigned)(critical_reach);
1,236,481✔
2518
    unsigned host_idx = src->m_index;
1,236,481✔
2519
    unsigned from_idx = safe_minus(host_idx, adj_size);
1,236,481✔
2520
    unsigned till_idx = std::min(host_idx + adj_size, safe_minus(assaults.size(), 1));
2,430,018✔
2521
    for (; from_idx <= till_idx; ++ from_idx)
4,382,898✔
2522
    {
2523
        if (from_idx == host_idx) { continue; }
3,161,349✔
2524
        CardStatus* adj_status = &assaults[from_idx];
1,909,936✔
2525
        if (!is_alive(adj_status)) { continue; }
1,909,936✔
2526
        if (skill_predicate<Skill::fortify>(fd, src, adj_status, s))
1,895,004✔
2527
        {
2528
            fd->selection_array.push_back(adj_status);
1,895,004✔
2529
        }
2530
    }
2531
    return fd->selection_array.size();
1,236,481✔
2532
}
2533
inline std::vector<CardStatus*>& skill_targets_hostile_assault(Field* fd, CardStatus* src)
40,914,903✔
2534
{
2535
    return(fd->players[opponent(src->m_player)]->assaults.m_indirect);
40,914,903✔
2536
}
2537

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

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

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

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

2560
template<> std::vector<CardStatus*>& skill_targets<Skill::enfeeble>(Field* fd, CardStatus* src)
11,230,473✔
2561
{ return(skill_targets_hostile_assault(fd, src)); }
11,230,473✔
2562

2563
template<> std::vector<CardStatus*>& skill_targets<Skill::enhance>(Field* fd, CardStatus* src)
13,494,731✔
2564
{ return(skill_targets_allied_assault(fd, src)); }
13,494,731✔
2565

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

2569
template<> std::vector<CardStatus*>& skill_targets<Skill::heal>(Field* fd, CardStatus* src)
21,076,408✔
2570
{ return(skill_targets_allied_assault(fd, src)); }
21,076,408✔
2571

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

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

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

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

2584
template<> std::vector<CardStatus*>& skill_targets<Skill::protect>(Field* fd, CardStatus* src)
7,764,670✔
2585
{ return(skill_targets_allied_assault(fd, src)); }
7,764,670✔
2586

2587
template<> std::vector<CardStatus*>& skill_targets<Skill::rally>(Field* fd, CardStatus* src)
6,243,391✔
2588
{ return(skill_targets_allied_assault(fd, src)); }
6,243,391✔
2589

2590
template<> std::vector<CardStatus*>& skill_targets<Skill::enrage>(Field* fd, CardStatus* src)
11,579,844✔
2591
{ return(skill_targets_allied_assault(fd, src)); }
11,579,844✔
2592

2593
template<> std::vector<CardStatus*>& skill_targets<Skill::entrap>(Field* fd, CardStatus* src)
5,219,608✔
2594
{ return(skill_targets_allied_assault(fd, src)); }
5,219,608✔
2595

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

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

2602
template<> std::vector<CardStatus*>& skill_targets<Skill::strike>(Field* fd, CardStatus* src)
15,312,589✔
2603
{ return(skill_targets_hostile_assault(fd, src)); }
673,026✔
2604

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

2608
template<> std::vector<CardStatus*>& skill_targets<Skill::weaken>(Field* fd, CardStatus* src)
4,850,566✔
2609
{ return(skill_targets_hostile_assault(fd, src)); }
4,850,566✔
2610

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

2614
    template<Skill::Skill skill_id>
2615
inline bool check_and_perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s, bool is_evadable
118,887,893✔
2616
#ifndef NQUEST
2617
        , bool & has_counted_quest
2618
#endif
2619
        )
2620
{
2621
    if (__builtin_expect(skill_check<skill_id>(fd, dst, src), true))
118,887,893✔
2622
    {
2623
#ifndef NQUEST
2624
        if (src->m_player == 0 && ! has_counted_quest)
2625
        {
2626
            fd->inc_counter(QuestType::skill_use, skill_id, dst->m_card->m_id);
2627
            has_counted_quest = true;
2628
        }
2629
#endif
2630
        if (is_evadable && (dst->m_evaded < dst->skill(Skill::evade)))
118,887,893✔
2631
        {
2632
            ++ dst->m_evaded;
15,833,121✔
2633
            _DEBUG_MSG(1, "%s %s on %s but it evades\n",
17,864,039✔
2634
                    status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2635
                    status_description(dst).c_str());
2636
            return(false);
15,833,121✔
2637
        }
2638
        _DEBUG_MSG(1, "%s %s on %s\n",
130,344,812✔
2639
                status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2640
                status_description(dst).c_str());
2641
        perform_skill<skill_id>(fd, src, dst, s);
103,054,772✔
2642
        if (s.c > 0)
103,054,772✔
2643
        {
2644
            src->m_skill_cd[skill_id] = s.c;
1,261,799✔
2645
        }
2646
        // Skill: Tribute
2647
        if (skill_check<Skill::tribute>(fd, dst, src)
122,708,354✔
2648
                // only activation helpful skills can be tributed (* except Evolve, Enhance, and Rush)
2649
                && is_activation_helpful_skill(s.id) && (s.id != Skill::evolve) && (s.id != Skill::enhance) && (s.id != Skill::rush)
19,653,582✔
2650
                && (dst->m_tributed < dst->skill(Skill::tribute))
7,831,398✔
2651
                && skill_check<skill_id>(fd, src, src))
94,052,714✔
2652
        {
2653
            ++ dst->m_tributed;
1,017,123✔
2654
            _DEBUG_MSG(1, "%s tributes %s back to %s\n",
1,017,123✔
2655
                    status_description(dst).c_str(), skill_short_description(fd->cards, s).c_str(),
2656
                    status_description(src).c_str());
2657
            perform_skill<skill_id>(fd, src, src, s);
1,017,123✔
2658
        }
2659
        return(true);
103,054,772✔
2660
    }
2661
    _DEBUG_MSG(1, "(CANCELLED) %s %s on %s\n",
×
2662
            status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2663
            status_description(dst).c_str());
2664
    return(false);
2665
}
2666
template<enum CardType::CardType def_cardtype>
2667
void perform_bge_devour(Field* fd, CardStatus* att_status, CardStatus* def_status)
14,647,354✔
2668
{
2669
// Passive BGE: Devour
2670
                unsigned leech_value;
2671
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::devour], false)
9,027,867✔
2672
                        && ((leech_value = att_status->skill(Skill::leech) + att_status->skill(Skill::refresh)) > 0)
47,057✔
2673
                        && (def_cardtype == CardType::assault))
9,027,867✔
2674
                {
2675
                    unsigned bge_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::devour] > 0)
3,120✔
2676
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::devour]
1,560✔
2677
                        : 4;
2678
                    unsigned bge_value = (leech_value - 1) / bge_denominator + 1;
1,560✔
2679
                    if (! att_status->m_sundered)
1,560✔
2680
                    {
2681
                        _DEBUG_MSG(1, "Devour: %s gains %u attack\n",
4,620✔
2682
                                status_description(att_status).c_str(), bge_value);
2683
                        att_status->m_perm_attack_buff += bge_value;
1,540✔
2684
                    }
2685
                    _DEBUG_MSG(1, "Devour: %s extends max hp / heals itself for %u\n",
4,680✔
2686
                            status_description(att_status).c_str(), bge_value);
2687
                    att_status->ext_hp(bge_value);
1,560✔
2688
                }
2689
}
9,027,867✔
2690
template<enum CardType::CardType def_cardtype>
2691
void perform_bge_heroism(Field* fd, CardStatus* att_status, CardStatus* def_status)
14,647,354✔
2692
{
2693
// Passive BGE: Heroism
2694
                unsigned valor_value;
2695
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::heroism], false)
14,647,354✔
2696
                        && ((valor_value = att_status->skill(Skill::valor) + att_status->skill(Skill::bravery)) > 0)
23,830✔
2697
                        && !att_status->m_sundered
2,536✔
2698
                        && (def_cardtype == CardType::assault) && (def_status->m_hp <= 0))
9,030,393✔
2699
                {
2700
                    _DEBUG_MSG(1, "Heroism: %s gain %u attack\n",
1,665✔
2701
                            status_description(att_status).c_str(), valor_value);
2702
                    att_status->m_perm_attack_buff += valor_value;
555✔
2703
                }
2704
                }
9,027,867✔
2705
/**
2706
 * @brief Perform Mark skill, increases Mark-counter.
2707
 * 
2708
 * @param fd     Field
2709
 * @param att_status Attacker
2710
 * @param def_status Defender
2711
 */
2712
void perform_mark(Field* fd, CardStatus* att_status, CardStatus* def_status)
14,647,354✔
2713
{
2714
    // Bug fix? 2023-04-03 mark should come after berserk
2715
    // Increase Mark-counter
2716
    unsigned mark_base = att_status->skill(Skill::mark);
14,647,354✔
2717
    if(mark_base && skill_check<Skill::mark>(fd,att_status,def_status)) {
14,647,354✔
2718
        _DEBUG_MSG(1, "%s marks %s for %u\n",
1,355,787✔
2719
                status_description(att_status).c_str(), status_description(def_status).c_str(), mark_base);
1,355,787✔
2720
        def_status->m_marked += mark_base;
1,355,787✔
2721
    }
2722
}
14,647,354✔
2723
/**
2724
 * @brief Perform Berserk skill and Passive BGE: EnduringRage
2725
 * 
2726
 * Increases attack by berserk value if not sundered.
2727
 * 
2728
 * @param fd     Field
2729
 * @param att_status Attacker
2730
 * @param def_status Defender
2731
 */
2732
void perform_berserk(Field* fd, CardStatus* att_status, CardStatus* def_status)
14,647,354✔
2733
{
2734
            // Skill: Berserk
2735
            unsigned berserk_value = att_status->skill(Skill::berserk);
14,647,354✔
2736
            if ( !att_status->m_sundered && (berserk_value > 0))
14,647,354✔
2737
            {
2738
                // perform_skill_berserk
2739
                att_status->m_perm_attack_buff += berserk_value;
6,207,026✔
2740
#ifndef NQUEST
2741
                if (att_status->m_player == 0)
2742
                {
2743
                    fd->inc_counter(QuestType::skill_use, Skill::berserk);
2744
                }
2745
#endif
2746

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

2761

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

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

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

2821
                // Passive BGE: Counterflux
2822
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::counterflux], false)
2,163,050✔
2823
                        && (def_cardtype == CardType::assault) && is_alive(def_status))
1,283,521✔
2824
                {
2825
                    unsigned flux_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::counterflux] > 0)
9,146✔
2826
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::counterflux]
4,573✔
2827
                        : 4;
2828
                    unsigned flux_value = (def_status->skill(Skill::counter) - 1) / flux_denominator + 1;
4,573✔
2829
                    _DEBUG_MSG(1, "Counterflux: %s heals itself and berserks for %u\n",
13,719✔
2830
                            status_description(def_status).c_str(), flux_value);
2831
                    def_status->add_hp(flux_value);
4,573✔
2832
                    if (! def_status->m_sundered)
4,573✔
2833
                    { def_status->m_perm_attack_buff += flux_value; }
4,139✔
2834
                }
2835
            }
2836
}
18,339,521✔
2837
bool check_and_perform_enhance(Field* fd, CardStatus* src, bool early)
132,810,674✔
2838
{
2839
      if(!is_active(src))return false; // active
132,810,674✔
2840
      if(!src->has_skill(Skill::enhance))return false; // enhance Skill
85,444,826✔
2841
      for(auto ss : src->m_card->m_skills)
107,208,000✔
2842
      {
2843
          if(ss.id != Skill::enhance)continue;
80,405,850✔
2844
          if(early ^ (ss.s == Skill::allegiance || ss.s == Skill::absorb ||ss.s == Skill::stasis || ss.s == Skill::bravery))continue; //only specified skills are 'early'
27,434,110✔
2845
          skill_table[ss.id](fd,src,ss);
13,401,075✔
2846
      }
2847
      return true;
26,802,150✔
2848
}
2849
bool check_and_perform_early_enhance(Field* fd, CardStatus* src)
66,405,337✔
2850
{
2851
      return check_and_perform_enhance(fd,src,true);
66,405,337✔
2852
}
2853
bool check_and_perform_later_enhance(Field* fd, CardStatus* src)
66,405,337✔
2854
{
2855
      return check_and_perform_enhance(fd,src,false);
66,405,337✔
2856
}
2857
/**
2858
 * @brief Perform drain skill
2859
 * 
2860
 * @param fd Field
2861
 * @param att_status Attacker status
2862
 * @param def_status Defender status
2863
 * @param att_dmg damage dealt by attacker
2864
 */
2865
template<>
2866
void PerformAttack::perform_swipe_drain<CardType::assault>(Field* fd, CardStatus* att_status, CardStatus* def_status,unsigned att_dmg) {
12,210,057✔
2867
        unsigned swipe_value = att_status->skill(Skill::swipe);
12,210,057✔
2868
        unsigned drain_value = att_status->skill(Skill::drain);
12,210,057✔
2869
        if (swipe_value || drain_value)
12,210,057✔
2870
        {
2871
            Storage<CardStatus>& def_assaults(fd->tip->assaults);
737,124✔
2872
            bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
737,124✔
2873
            auto drain_total_dmg = att_dmg;
737,124✔
2874
            unsigned adj_size = 1 + (unsigned)(critical_reach);
737,124✔
2875
            unsigned host_idx = def_status->m_index;
737,124✔
2876
            unsigned from_idx = safe_minus(host_idx, adj_size);
737,124✔
2877
            unsigned till_idx = std::min(host_idx + adj_size, safe_minus(def_assaults.size(), 1));
1,234,745✔
2878
            for (; from_idx <= till_idx; ++ from_idx)
2,040,614✔
2879
            {
2880
                if (from_idx == host_idx) { continue; }
1,303,490✔
2881
                CardStatus* adj_status = &def_assaults[from_idx];
566,366✔
2882
                if (!is_alive(adj_status)) { continue; }
566,366✔
2883
                _DEBUG_ASSERT(adj_status->m_card->m_type == CardType::assault); //only assaults
459,601✔
2884
                //unsigned swipe_dmg = safe_minus(
2885
                //    swipe_value + drain_value + def_status->m_enfeebled,
2886
                //    def_status->protected_value());
2887
                unsigned remaining_dmg = remove_absorption(fd,adj_status,swipe_value + drain_value + adj_status->m_enfeebled);
459,601✔
2888
                remaining_dmg = safe_minus(remaining_dmg,adj_status->protected_value());
459,601✔
2889
                _DEBUG_MSG(1, "%s swipes %s for %u damage\n",
459,601✔
2890
                        status_description(att_status).c_str(),
2891
                        status_description(adj_status).c_str(), remaining_dmg);
459,601✔
2892

2893
                remove_hp(fd, adj_status, remaining_dmg);
459,601✔
2894
                drain_total_dmg += remaining_dmg;
459,601✔
2895
            }
2896
            if (drain_value && skill_check<Skill::drain>(fd, att_status, nullptr))
737,124✔
2897
            {
2898
                _DEBUG_MSG(1, "%s drains %u hp\n",
128,666✔
2899
                        status_description(att_status).c_str(), drain_total_dmg);
128,666✔
2900
                att_status->add_hp(drain_total_dmg);
128,666✔
2901
            }
2902
            prepend_on_death(fd);
737,124✔
2903
            resolve_skill(fd);
737,124✔
2904
        }
2905
}
12,210,057✔
2906
/**
2907
 * @brief Perform Hunt skill
2908
 * 
2909
 * @param fd Field 
2910
 * @param att_status Attacker status
2911
 * @param def_status Defender status
2912
 */
2913
void perform_hunt(Field* fd, CardStatus* att_status, CardStatus* def_status) {
18,353,637✔
2914
    unsigned hunt_value = att_status->skill(Skill::hunt);
18,353,637✔
2915
        if(hunt_value)
18,353,637✔
2916
        {
2917
            CardStatus* hunted_status{select_first_enemy_assault(fd)};
63,836✔
2918
            if (hunted_status != nullptr)
63,836✔
2919
            {
2920
                unsigned remaining_dmg = remove_absorption(fd,hunted_status,hunt_value + hunted_status->m_enfeebled);
39,460✔
2921
                remaining_dmg = safe_minus(remaining_dmg,hunted_status->protected_value());
39,460✔
2922
                _DEBUG_MSG(1, "%s hunts %s for %u damage\n",
39,460✔
2923
                        status_description(att_status).c_str(),
2924
                        status_description(hunted_status).c_str(), remaining_dmg);
39,460✔
2925

2926
                remove_hp(fd, hunted_status, remaining_dmg);
39,460✔
2927

2928
                prepend_on_death(fd);
39,460✔
2929
                resolve_skill(fd);
39,460✔
2930
            }
2931
        }
2932
}
18,353,637✔
2933
/**
2934
 * @brief Perform Subdue skill
2935
 * 
2936
 * @param fd Field
2937
 * @param att_status Attacker status
2938
 * @param def_status Defender status
2939
 */
2940
void perform_subdue(Field* fd, CardStatus* att_status, CardStatus* def_status)
19,375,061✔
2941
{
2942
                // Skill: Subdue
2943
                unsigned subdue_value = def_status->skill(Skill::subdue);
19,375,061✔
2944
                if (__builtin_expect(subdue_value, false))
19,375,061✔
2945
                {
2946
                    _DEBUG_MSG(1, "%s subdues %s by %u\n",
585,560✔
2947
                            status_description(def_status).c_str(),
2948
                            status_description(att_status).c_str(), subdue_value);
585,560✔
2949
                    att_status->m_subdued += subdue_value;
585,560✔
2950
                    //fix negative attack
2951
                    if(att_status->calc_attack_power()<0)
585,560✔
2952
                    {
2953
                        att_status->m_temp_attack_buff -= att_status->calc_attack_power();
1,396✔
2954
                    }
2955
                    if (att_status->m_hp > att_status->max_hp())
662,206✔
2956
                    {
2957
                        _DEBUG_MSG(1, "%s loses %u HP due to subdue (max hp: %u)\n",
96,009✔
2958
                                status_description(att_status).c_str(),
2959
                                (att_status->m_hp - att_status->max_hp()),
2960
                                att_status->max_hp());
96,009✔
2961
                        att_status->m_hp = att_status->max_hp();
166,397✔
2962
                    }
2963
                }
2964
}
19,375,061✔
2965
bool check_and_perform_valor(Field* fd, CardStatus* src)
11,910,500✔
2966
{
2967
    unsigned valor_value = src->skill(Skill::valor);
11,910,500✔
2968
    if (valor_value && !src->m_sundered && skill_check<Skill::valor>(fd, src, nullptr))
23,821,000✔
2969
    {
2970
        _DEBUG_ASSERT(src->m_card->m_type == CardType::assault); //only assaults
50,079✔
2971
        unsigned opponent_player = opponent(src->m_player);
50,079✔
2972
        const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ?
50,079✔
2973
            &fd->players[opponent_player]->assaults[src->m_index] :
21,525✔
2974
            nullptr;
50,079✔
2975
        if (dst == nullptr || dst->m_hp <= 0)
21,525✔
2976
        {
2977
            _DEBUG_MSG(1, "%s loses Valor (no blocker)\n", status_description(src).c_str());
28,554✔
2978
            return false;
28,554✔
2979
        }
2980
        else if (dst->attack_power() <= src->attack_power())
21,525✔
2981
        {
2982
            _DEBUG_MSG(1, "%s loses Valor (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str());
2,377✔
2983
            return false;
2,377✔
2984
        }
2985
#ifndef NQUEST
2986
        if (src->m_player == 0)
2987
        {
2988
            fd->inc_counter(QuestType::skill_use, Skill::valor);
2989
        }
2990
#endif
2991
        _DEBUG_MSG(1, "%s activates Valor %u\n", status_description(src).c_str(), valor_value);
19,148✔
2992
        src->m_perm_attack_buff += valor_value;
19,148✔
2993
        return true;
19,148✔
2994
    }
2995
    return false;
2996
}
2997

2998
bool check_and_perform_bravery(Field* fd, CardStatus* src)
52,119,963✔
2999
{
3000
    unsigned bravery_value = src->skill(Skill::bravery);
52,119,963✔
3001
    if (bravery_value && !src->m_sundered && skill_check<Skill::bravery>(fd, src, nullptr))
104,239,926✔
3002
    {
3003
        _DEBUG_ASSERT(src->m_card->m_type == CardType::assault); //only assaults
1,510,627✔
3004
        unsigned opponent_player = opponent(src->m_player);
1,510,627✔
3005
        const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ?
1,510,627✔
3006
            &fd->players[opponent_player]->assaults[src->m_index] :
772,469✔
3007
            nullptr;
1,510,627✔
3008
        if (dst == nullptr || dst->m_hp <= 0)
772,469✔
3009
        {
3010
            _DEBUG_MSG(1, "%s loses Bravery (no blocker)\n", status_description(src).c_str());
738,158✔
3011
            return false;
738,158✔
3012
        }
3013
        else if (dst->attack_power() <= src->attack_power())
772,469✔
3014
        {
3015
            _DEBUG_MSG(1, "%s loses Bravery (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str());
578,030✔
3016
            return false;
578,030✔
3017
        }
3018
#ifndef NQUEST
3019
        if (src->m_player == 0)
3020
        {
3021
            fd->inc_counter(QuestType::skill_use, Skill::bravery);
3022
        }
3023
#endif
3024
        _DEBUG_MSG(1, "%s activates Bravery %u\n", status_description(src).c_str(), bravery_value);
194,439✔
3025
        src->m_perm_attack_buff += bravery_value;
194,439✔
3026

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

3038
        return true;
194,439✔
3039
    }
3040
    return false;
3041
}
3042

3043
bool check_and_perform_inhibit(Field* fd, CardStatus* att_status,CardStatus* def_status)
27,336,044✔
3044
{
3045
      unsigned inhibit_value = att_status->skill(Skill::inhibit);
27,336,044✔
3046
      if (inhibit_value > def_status->m_inhibited && skill_check<Skill::inhibit>(fd, att_status, def_status))
29,582,273✔
3047
      {
3048
        _DEBUG_MSG(1, "%s inhibits %s by %u\n",
2,246,229✔
3049
                status_description(att_status).c_str(),
3050
                status_description(def_status).c_str(), inhibit_value);
2,246,229✔
3051
        def_status->m_inhibited = inhibit_value;
2,246,229✔
3052
        return true;
2,246,229✔
3053
      }
3054
      return false;
3055
}
3056
bool check_and_perform_sabotage(Field* fd, CardStatus* att_status, CardStatus* def_status)
27,336,044✔
3057
{
3058
    unsigned sabotage_value = att_status->skill(Skill::sabotage);
27,336,044✔
3059
    if (sabotage_value > def_status->m_sabotaged && skill_check<Skill::sabotage>(fd, att_status, def_status))
27,755,315✔
3060
    {
3061
        _DEBUG_MSG(1, "%s sabotages %s by %u\n",
419,271✔
3062
                status_description(att_status).c_str(),
3063
                status_description(def_status).c_str(), sabotage_value);
419,271✔
3064
        def_status->m_sabotaged = sabotage_value;
419,271✔
3065
        return true;
419,271✔
3066
    }
3067
    return false;
3068
}
3069
bool check_and_perform_disease(Field* fd, CardStatus* att_status,CardStatus* def_status)
27,336,044✔
3070
{
3071
    unsigned disease_base = att_status->skill(Skill::disease);
27,336,044✔
3072
    if(disease_base && skill_check<Skill::disease>(fd, att_status, def_status)) {
27,634,789✔
3073
        _DEBUG_MSG(1, "%s diseases %s for %u\n",
298,745✔
3074
        status_description(att_status).c_str(), status_description(def_status).c_str(), disease_base);
298,745✔
3075
        def_status->m_diseased += disease_base;
298,745✔
3076
        return true;
298,745✔
3077
    }
3078
    return false;
3079
}
3080

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

3108

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

3133
        default:
108,416,982✔
3134
            n_candidates = select_fast<skill_id>(fd, src, skill_targets<skill_id>(fd, src), s);
108,416,982✔
3135
            break;
3136
    }
3137

3138
    // (false-loop)
3139
    unsigned n_selected = n_candidates;
111,839,574✔
3140
    do
111,292,265✔
3141
    {
3142
        // no candidates
3143
        if (n_candidates == 0)
109,090,008✔
3144
        { break; }
3145

3146
        // show candidates (debug)
3147
        _DEBUG_SELECTION("%s", skill_names[skill_id].c_str());
69,302,186✔
3148

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

3154
        // shuffle & trim
3155
        for (unsigned i = 0; i < n_targets; ++i)
41,613,733✔
3156
        {
3157
            std::swap(fd->selection_array[i], fd->selection_array[fd->rand(i, n_candidates - 1)]);
21,000,325✔
3158
        }
3159
        fd->selection_array.resize(n_targets);
20,613,408✔
3160
        if (n_targets > 1)
20,613,408✔
3161
        {
3162
            std::sort(fd->selection_array.begin(), fd->selection_array.end(),
253,145✔
3163
                    [](const CardStatus * a, const CardStatus * b) { return a->m_index < b->m_index; });
647,374✔
3164
        }
3165
        n_selected = n_targets;
3166

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

3169
    return n_selected;
111,292,265✔
3170
}
3171

3172
    template<Skill::Skill skill_id>
3173
void perform_targetted_allied_fast(Field* fd, CardStatus* src, const SkillSpec& s)
67,919,136✔
3174
{
3175
    select_targets<skill_id>(fd, src, s);
67,919,136✔
3176
    unsigned num_inhibited = 0;
67,919,136✔
3177
#ifndef NQUEST
3178
    bool has_counted_quest = false;
3179
#endif
3180
    bool src_overloaded = src->m_overloaded;
67,919,136✔
3181
    std::vector<CardStatus*> selection_array = fd->selection_array;
67,919,136✔
3182
    for (CardStatus * dst: selection_array)
134,677,113✔
3183
    {
3184
        if (dst->m_inhibited > 0 && !src_overloaded)
66,757,977✔
3185
        {
3186
            _DEBUG_MSG(1, "%s %s on %s but it is inhibited\n",
4,025,844✔
3187
                    status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
3188
                    status_description(dst).c_str());
3189
            -- dst->m_inhibited;
3,487,998✔
3190
            ++ num_inhibited;
3,487,998✔
3191
            continue;
3,487,998✔
3192
        }
3,487,998✔
3193
        check_and_perform_skill<skill_id>(fd, src, dst, s, false
63,269,979✔
3194
#ifndef NQUEST
3195
                , has_counted_quest
3196
#endif
3197
                );
3198
    }
3199

3200
    // Passive BGE: Divert
3201
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::divert], false)
67,919,136✔
3202
            && (num_inhibited > 0))
67,919,136✔
3203
    {
3204
        SkillSpec diverted_ss = s;
6,022✔
3205
        diverted_ss.y = allfactions;
6,022✔
3206
        diverted_ss.n = 1;
6,022✔
3207
        diverted_ss.all = false;
6,022✔
3208
        for (unsigned i = 0; i < num_inhibited; ++ i)
12,085✔
3209
        {
3210
            select_targets<skill_id>(fd, &fd->tip->commander, diverted_ss);
6,063✔
3211
            std::vector<CardStatus*> selection_array = fd->selection_array;
6,063✔
3212
            for (CardStatus * dst: selection_array)
10,193✔
3213
            {
3214
                if (dst->m_inhibited > 0)
4,130✔
3215
                {
3216
                    _DEBUG_MSG(1, "%s %s (Diverted) on %s but it is inhibited\n",
606✔
3217
                            status_description(src).c_str(), skill_short_description(fd->cards, diverted_ss).c_str(),
3218
                            status_description(dst).c_str());
3219
                    -- dst->m_inhibited;
202✔
3220
                    continue;
202✔
3221
                }
202✔
3222
                _DEBUG_MSG(1, "%s %s (Diverted) on %s\n",
11,784✔
3223
                        status_description(src).c_str(), skill_short_description(fd->cards, diverted_ss).c_str(),
3224
                        status_description(dst).c_str());
3225
                perform_skill<skill_id>(fd, src, dst, diverted_ss);
3,946✔
3226
            }
3227
        }
3228
    }
3229
}
67,919,136✔
3230

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

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

3259
    // apply skill to each target(dst)
3260
    unsigned selection_array_len = fd->selection_array.size();
43,367,066✔
3261
    std::vector<CardStatus*> selection_array = fd->selection_array;
43,367,066✔
3262
    for (CardStatus * dst: selection_array)
98,965,076✔
3263
    {
3264
        // TurningTides
3265
        if (__builtin_expect(has_turningtides, false))
13,067,919✔
3266
        {
3267
            old_attack = dst->attack_power();
11,733✔
3268
        }
3269

3270
        // check & apply skill to target(dst)
3271
        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,122,539✔
3272
#ifndef NQUEST
3273
                    , has_counted_quest
3274
#endif
3275
                    ))
3276
        {
3277
            // TurningTides: get max attack decreasing
3278
            if (__builtin_expect(has_turningtides, false))
8,893,992✔
3279
            {
3280
                turningtides_value = std::max(turningtides_value, safe_minus(old_attack, dst->attack_power()));
26,886✔
3281
            }
3282

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

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

3301
    prepend_on_death(fd);  // skills
43,367,066✔
3302

3303
    // Payback/Revenge
3304
    for (CardStatus * pb_status: paybackers)
44,381,167✔
3305
    {
3306
        turningtides_value = 0;
1,014,101✔
3307

3308
        // apply Revenge
3309
        if (pb_status->skill(Skill::revenge))
1,014,101✔
3310
        {
3311
            unsigned revenged_count(0);
3312
            for (unsigned case_index(0); case_index < 3; ++ case_index)
4,054,236✔
3313
            {
3314
                CardStatus * target_status;
3315
#ifndef NDEBUG
3316
                const char * target_desc;
3317
#endif
3318
                switch (case_index)
3,040,677✔
3319
                {
3320
                    // revenge to left
3321
                    case 0:
1,013,559✔
3322
                        if (!(target_status = fd->left_assault(src))) { continue; }
227,813✔
3323
#ifndef NDEBUG
3324
                        target_desc = "left";
3325
#endif
3326
                        break;
3327

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

3336
                        // revenge to right
3337
                    case 2:
1,013,559✔
3338
                        if (!(target_status = fd->right_assault(src))) { continue; }
2,457,685✔
3339
#ifndef NDEBUG
3340
                        target_desc = "right";
3341
#endif
3342
                        break;
3343

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

3349
                // skip illegal target
3350
                if (!skill_predicate<skill_id>(fd, target_status, target_status, s))
2,555,191✔
3351
                {
3352
                    continue;
146,615✔
3353
                }
3354

3355
                // skip dead target
3356
                if (!is_alive(target_status))
2,408,576✔
3357
                {
3358
#ifndef NDEBUG
3359
                    _DEBUG_MSG(1, "(CANCELLED: target unit dead) %s Revenge (to %s) %s on %s\n",
×
3360
                            status_description(pb_status).c_str(), target_desc,
3361
                            skill_short_description(fd->cards, s).c_str(), status_description(target_status).c_str());
3362
#endif
3363
                    continue;
×
3364
                }
×
3365

3366
                // TurningTides
3367
                if (__builtin_expect(has_turningtides, false))
54,705✔
3368
                {
3369
                    old_attack = target_status->attack_power();
847✔
3370
                }
3371

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

3381
                // revenged TurningTides: get max attack decreasing
3382
                if (__builtin_expect(has_turningtides, false))
54,705✔
3383
                {
3384
                    turningtides_value = std::max(turningtides_value, safe_minus(old_attack, target_status->attack_power()));
2,225✔
3385
                }
3386
            }
3387
            if (revenged_count)
1,013,559✔
3388
            {
3389
                // consume remaining payback/revenge
3390
                ++ pb_status->m_paybacked;
992,247✔
3391

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

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

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

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

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

3445
    prepend_on_death(fd,true);  // paybacked skills
43,367,066✔
3446
}
43,367,066✔
3447

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

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

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

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

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

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

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

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

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

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

3507

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

3511

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

3523

3524

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

3531

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

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

3579

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

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

3605

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

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

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

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

3764
        // Play dominion & fortresses
3765
        for (unsigned _(0), ai(fd->tapi); _ < 2; ++_)
3,117,507✔
3766
        {
3767
            if (fd->players[ai]->deck->alpha_dominion)
2,078,338✔
3768
            { PlayCard(fd->players[ai]->deck->alpha_dominion, fd, ai, &fd->players[ai]->commander).op<CardType::structure>(); }
1,166,533✔
3769
            for (const Card* played_card: fd->players[ai]->deck->shuffled_forts)
4,156,676✔
3770
            {
3771

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

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

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

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

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

3819
            // Begin 'Play Card' phase action
3820
            fd->prepare_action();
13,728,750✔
3821

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

3845

3846

3847
        }
3848
        if (__builtin_expect(fd->end, false)) { break; }
15,615,626✔
3849

3850
        //-------------------------------------------------
3851
        // Phase: (Later-) Enhance, Inhibit, Sabotage, Disease
3852
        //-------------------------------------------------
3853
        //Skill: Enhance
3854
        //Perform later enhance for commander
3855
        if(!fd->fixes[Fix::enhance_early]) {
15,615,626✔
3856
        check_and_perform_later_enhance(fd,&fd->tap->commander);
×
3857
        auto& structures(fd->tap->structures);
×
3858
        for(unsigned index(0); index < structures.size(); ++index)
×
3859
        {
3860
            CardStatus * status = &structures[index];
×
3861
            //enhance everything else after card was played
3862
            check_and_perform_later_enhance(fd,status);
×
3863
        }
3864
        }
3865
        //Perform Inhibit, Sabotage, Disease
3866
        auto& assaults(fd->tap->assaults);
15,615,626✔
3867
        for(unsigned index(0); index < assaults.size(); ++index)
60,636,021✔
3868
        {
3869
            CardStatus * att_status = &assaults[index];
45,020,395✔
3870
            if(att_status->m_index >= fd->tip->assaults.size())continue; //skip no enemy
45,020,395✔
3871
            auto def_status = &fd->tip->assaults[att_status->m_index];
27,336,224✔
3872
            if(!is_alive(def_status))continue; //skip dead
27,336,224✔
3873

3874
            check_and_perform_inhibit(fd,att_status,def_status);
27,336,044✔
3875
            check_and_perform_sabotage(fd,att_status,def_status);
27,336,044✔
3876
            check_and_perform_disease(fd,att_status,def_status);
27,336,044✔
3877
        }
3878
        //-------------------------------------------------
3879

3880
        // Evaluate Passive BGE Heroism skills
3881
        if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::heroism], false))
15,615,626✔
3882
        {
3883
            for (CardStatus * dst: fd->tap->assaults.m_indirect)
251,051✔
3884
            {
3885
                unsigned bge_value = (dst->skill(Skill::valor) + dst->skill(Skill::bravery)+ 1) / 2;
157,366✔
3886
                if (bge_value <= 0)
157,366✔
3887
                { continue; }
137,462✔
3888
                SkillSpec ss_protect{Skill::protect, bge_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
20,028✔
3889
                if (dst->m_inhibited > 0)
20,028✔
3890
                {
3891
                    _DEBUG_MSG(1, "Heroism: %s on %s but it is inhibited\n",
124✔
3892
                            skill_short_description(fd->cards, ss_protect).c_str(), status_description(dst).c_str());
124✔
3893
                    -- dst->m_inhibited;
124✔
3894

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

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

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

3951
        // Evaluate structures
3952
        fd->current_phase = Field::structures_phase;
15,615,626✔
3953
        for (fd->current_ci = 0; !fd->end && (fd->current_ci < fd->tap->structures.size()); ++fd->current_ci)
38,081,988✔
3954
        {
3955
            CardStatus* current_status(&fd->tap->structures[fd->current_ci]);
22,466,362✔
3956
            if (!is_active(current_status))
22,466,362✔
3957
            {
3958
                _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str());
6,929,489✔
3959
            }
3960
            else
3961
            {
3962
                evaluate_skills<CardType::structure>(fd, current_status, current_status->m_card->m_skills);
15,536,873✔
3963
            }
3964
        }
3965

3966
        // Evaluate assaults
3967
        fd->current_phase = Field::assaults_phase;
15,615,626✔
3968
        fd->bloodlust_value = 0;
15,615,626✔
3969
        for (fd->current_ci = 0; !fd->end && (fd->current_ci < fd->tap->assaults.size()); ++fd->current_ci)
56,595,598✔
3970
        {
3971
            CardStatus* current_status(&fd->tap->assaults[fd->current_ci]);
41,998,748✔
3972
            bool attacked = false;
41,998,748✔
3973
            if (!is_active(current_status))
41,998,748✔
3974
            {
3975
                _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str());
20,618,894✔
3976
                // Passive BGE: HaltedOrders
3977
                /*
3978
                unsigned inhibit_value;
3979
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::haltedorders], false)
3980
                        && (current_status->m_delay > 0) // still frozen
3981
                        && (fd->current_ci < fd->tip->assaults.size()) // across slot isn't empty
3982
                        && is_alive(&fd->tip->assaults[fd->current_ci]) // across assault is alive
3983
                        && ((inhibit_value = current_status->skill(Skill::inhibit))
3984
                            > fd->tip->assaults[fd->current_ci].m_inhibited)) // inhibit/re-inhibit(if higher)
3985
                        {
3986
                            CardStatus* across_status(&fd->tip->assaults[fd->current_ci]);
3987
                            _DEBUG_MSG(1, "Halted Orders: %s inhibits %s by %u\n",
3988
                                    status_description(current_status).c_str(),
3989
                                    status_description(across_status).c_str(), inhibit_value);
3990
                            across_status->m_inhibited = inhibit_value;
3991
                        }
3992
                        */
3993
            }
3994
            else
3995
            {
3996
                if (current_status->m_protected_stasis)
21,379,854✔
3997
                {
3998
                    _DEBUG_MSG(1, "%s loses Stasis protection (activated)\n",
1,606,013✔
3999
                            status_description(current_status).c_str());
4000
                }
4001
                current_status->m_protected_stasis = 0;
21,379,854✔
4002
                fd->assault_bloodlusted = false;
21,379,854✔
4003
                current_status->m_step = CardStep::attacking;
21,379,854✔
4004
                evaluate_skills<CardType::assault>(fd, current_status, current_status->m_card->m_skills, &attacked);
21,379,854✔
4005
                if (__builtin_expect(fd->end, false)) { break; }
21,379,854✔
4006
                if (__builtin_expect(!is_alive(current_status), false)) { continue; }
20,361,078✔
4007
            }
4008

4009
            current_status->m_step = CardStep::attacked;
40,213,749✔
4010
        }
4011
        fd->current_phase = Field::end_phase;
15,615,626✔
4012
        turn_end_phase(fd);
15,615,626✔
4013
        if (__builtin_expect(fd->end, false)) { break; }
15,615,626✔
4014
        _DEBUG_MSG(1, "TURN %u ends for %s\n", fd->turn, status_description(&fd->tap->commander).c_str());
14,596,850✔
4015
        std::swap(fd->tapi, fd->tipi);
14,596,850✔
4016
        std::swap(fd->tap, fd->tip);
14,596,850✔
4017
        ++fd->turn;
14,596,850✔
4018
    }
4019

4020
    return evaluate_sim_result(fd,turns_both!= 0);
1,039,169✔
4021
}
4022

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