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

APN-Pucky / tyrant_optimize / 20663094174

02 Jan 2026 05:28PM UTC coverage: 70.345% (-0.02%) from 70.364%
20663094174

push

github

APN-Pucky
init strap debug mode

8 of 16 new or added lines in 3 files covered. (50.0%)

4787 of 6805 relevant lines covered (70.35%)

9151755.19 hits per line

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

84.1
/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); }
14,481,805✔
39
inline bool is_alive(CardStatus* c) { return (c->m_hp > 0); }
15,237,558✔
40
inline bool can_act(CardStatus* c) { return is_alive(c) && !c->m_jammed; }
408,357,545✔
41
inline bool is_active(CardStatus* c) { return can_act(c) && (c->m_delay == 0); }
341,170,444✔
42
inline bool is_active_next_turn(CardStatus* c) { return can_act(c) && (c->m_delay <= 1); }
48,046,840✔
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)));}
257,504✔
44
// Can be healed / repaired
45
inline bool can_be_healed(CardStatus* c) { return is_alive(c) && (c->m_hp < c->max_hp()); }
35,630✔
46
// Strange Transmission [Gilians] features
47
#ifdef TUO_GILIAN
48
inline bool is_gilian(CardStatus* c) { return(
49
        (c->m_card->m_id >= 25054 && c->m_card->m_id <= 25063) // Gilian Commander
50
        ||  (c->m_card->m_id >= 38348 && c->m_card->m_id <= 38388) // Gilian assaults plus the Gil's Shard
51
        ); }
52
inline bool is_alive_gilian(CardStatus* c) { return(is_alive(c) && is_gilian(c)); }
53
#else
54
# define is_gilian(c) (false)
55
# define is_alive_gilian(c) (false)
56
#endif
57

58
//------------------------------------------------------------------------------
59
inline std::string status_description(const CardStatus* status)
55,193,267✔
60
{
61
    return status->description();
21,971,006✔
62
}
63
//------------------------------------------------------------------------------
NEW
64
inline std::string strap_string(const CardStatus* status)
×
65
{
66
    // Replace ' ' with '_' for strap key
NEW
67
    return boost::replace_all_copy(status->m_card->m_name, " ", "_");
×
68
}
69
//------------------------------------------------------------------------------
70
template <typename CardsIter, typename Functor>
71
inline unsigned Field::make_selection_array(CardsIter first, CardsIter last, Functor f)
118,438,265✔
72
{
73
    this->selection_array.clear();
118,438,265✔
74
    for(auto c = first; c != last; ++c)
478,168,257✔
75
    {
76
        if (f(*c))
724,684,613✔
77
        {
78
            this->selection_array.push_back(*c);
187,121,873✔
79
        }
80
    }
81
    return(this->selection_array.size());
118,438,265✔
82
}
83
inline CardStatus * Field::left_assault(const CardStatus * status)
1,015,591✔
84
{
85
    return left_assault(status, 1);
3,815,611✔
86
}
87
inline CardStatus * Field::left_assault(const CardStatus * status, const unsigned n)
1,015,591✔
88
{
89
    auto & assaults = this->players[status->m_player]->assaults;
1,015,591✔
90
    if (status->m_index >= n)
1,015,591✔
91
    {
92
        auto left_status = &assaults[status->m_index - n];
790,732✔
93
        if (is_alive(left_status))
790,732✔
94
        {
95
            return left_status;
96
        }
97
    }
98
    return nullptr;
99
}
100
inline CardStatus * Field::right_assault(const CardStatus * status)
1,015,591✔
101
{
102
    return right_assault(status, 1);
3,808,505✔
103
}
104
inline CardStatus * Field::right_assault(const CardStatus * status, const unsigned n)
1,015,591✔
105
{
106
    auto & assaults = this->players[status->m_player]->assaults;
1,015,591✔
107
    if ((status->m_index + n) < assaults.size())
1,015,591✔
108
    {
109
        auto right_status = &assaults[status->m_index + n];
771,660✔
110
        if (is_alive(right_status))
771,660✔
111
        {
112
            return right_status;
113
        }
114
    }
115
    return nullptr;
116
}
117
inline void Field::print_selection_array()
×
118
{
119
#ifndef NDEBUG
120
    for(auto c: this->selection_array)
×
121
    {
122
        _DEBUG_MSG(2, "+ %s\n", status_description(c).c_str());
×
123
    }
124
#endif
125
}
×
126

127
//------------------------------------------------------------------------------
128
inline void Field::prepare_action()
98,857,514✔
129
{
130
    damaged_units_to_times.clear();
197,715,028✔
131
}
132

133
//------------------------------------------------------------------------------
134
inline void Field::finalize_action()
97,521,269✔
135
{
136
    for (auto unit_it = damaged_units_to_times.begin(); unit_it != damaged_units_to_times.end(); ++ unit_it)
97,736,279✔
137
    {
138
        if (__builtin_expect(!unit_it->second, false))
215,010✔
139
        { continue; }
87,531✔
140
        CardStatus * dmg_status = unit_it->first;
215,010✔
141
        if (__builtin_expect(!is_alive(dmg_status), false))
215,010✔
142
        { continue; }
87,531✔
143
        unsigned barrier_base = dmg_status->skill(Skill::barrier);
127,479✔
144
        if (barrier_base)
127,479✔
145
        {
146
            unsigned protect_value = barrier_base * unit_it->second;
127,479✔
147
            _DEBUG_STRAP(
127,479✔
148
                    "turn", turn,
149
                    "active_player", tapi,
150
                    "barrier_"+strap_string(dmg_status), 1.0,
151
                    "barrier_protect", protect_value,
152
                    "barrier_base", barrier_base,
153
                    "barrier_times", unit_it->second
154
            );
127,479✔
155
            _DEBUG_MSG(1, "%s protects itself for %u (barrier %u x %u damage taken)\n",
127,479✔
156
                    status_description(dmg_status).c_str(), protect_value, barrier_base, unit_it->second);
127,479✔
157
            dmg_status->m_protected += protect_value;
127,479✔
158
        }
159
    }
160
}
97,521,269✔
161

162
//------------------------------------------------------------------------------
163
inline unsigned CardStatus::skill_base_value(Skill::Skill skill_id) const
1,195,667,313✔
164
{
165
    return m_card->m_skill_value[skill_id + m_primary_skill_offset[skill_id]]
1,195,667,313✔
166
        + (skill_id == Skill::berserk ? m_enraged : 0)
×
167
        + (skill_id == Skill::counter ? m_entrapped : 0)
32,224,900✔
168
        ;
169
}
170
//------------------------------------------------------------------------------
171
inline unsigned CardStatus::skill(Skill::Skill skill_id) const
756,310,584✔
172
{
173
    return (is_activation_skill_with_x(skill_id)
756,310,584✔
174
            ? safe_minus(skill_base_value(skill_id), m_sabotaged)
175
            : skill_base_value(skill_id))
758,400,249✔
176
        + enhanced(skill_id);
758,400,249✔
177
}
178
//------------------------------------------------------------------------------
179
inline bool CardStatus::has_skill(Skill::Skill skill_id) const
437,336,964✔
180
{
181
    return skill_base_value(skill_id);
53,336✔
182
}
183
//------------------------------------------------------------------------------
184
inline unsigned CardStatus::enhanced(Skill::Skill skill_id) const
862,207,040✔
185
{
186
    return m_enhanced_value[skill_id + m_primary_skill_offset[skill_id]];
127,479✔
187
}
188
//------------------------------------------------------------------------------
189
inline unsigned CardStatus::protected_value() const
51,271,197✔
190
{
191
    return m_protected + m_protected_stasis;
51,271,197✔
192
}
193
//------------------------------------------------------------------------------
194
/**
195
 * @brief Maximum health.
196
 * This takes subduing into account by reducing the permanent health buffs by subdue.
197
 * 
198
 * @return unsigned maximum health.
199
 */
200
inline unsigned CardStatus::max_hp() const
94,981,194✔
201
{
202
    return (m_card->m_health + safe_minus(m_perm_health_buff, m_subdued));
91,693,532✔
203
}
204
/** @brief Increase of current health.
205
 *  This takes disease into account and removes as needed.
206
 *
207
 *  @param [in] value increase of health.
208
 *  @return applied increase of health.
209
 */
210
inline unsigned CardStatus::add_hp(unsigned value)
21,108,610✔
211
{
212
    value = remove_disease(this,value);
21,108,610✔
213
    return (m_hp = std::min(m_hp + value, max_hp()));
37,722,615✔
214
}
215
/** @brief Permanent increase of maximum health.
216
 *  This takes disease into account and removes as needed.
217
 *  The increase of the maximum health entails an increase of current health by the same amount.
218
 *
219
 *  @param [in] value increase of maximum health.
220
 *  @return applied increase of maximum health.
221
 */ 
222
inline unsigned CardStatus::ext_hp(unsigned value)
12,868,184✔
223
{
224
    value = remove_disease(this,value);
12,868,184✔
225
    m_perm_health_buff += value;
12,868,184✔
226
    // we can safely call add_hp without worring about the second call to remove_disease because
227
    // the first call will have already removed the disease or the value will be 0
228
    return add_hp(value);
12,868,184✔
229
}
230
//------------------------------------------------------------------------------
231
inline void CardStatus::set(const Card* card)
24,062,334✔
232
{
233
    this->set(*card);
24,062,334✔
234
}
235
//------------------------------------------------------------------------------
236
inline void CardStatus::set(const Card& card)
24,062,334✔
237
{
238
    m_card = &card;
24,062,334✔
239
    m_index = 0;
24,062,334✔
240
    m_action_index=0;
24,062,334✔
241
    m_player = 0;
24,062,334✔
242
    m_delay = card.m_delay;
24,062,334✔
243
    m_hp = card.m_health;
24,062,334✔
244
    m_absorption = 0;
24,062,334✔
245
    m_step = CardStep::none;
24,062,334✔
246
    m_perm_health_buff = 0;
24,062,334✔
247
    m_perm_attack_buff = 0;
24,062,334✔
248
    m_temp_attack_buff = 0;
24,062,334✔
249
    m_corroded_rate = 0;
24,062,334✔
250
    m_corroded_weakened = 0;
24,062,334✔
251
    m_subdued = 0;
24,062,334✔
252
    m_enfeebled = 0;
24,062,334✔
253
    m_evaded = 0;
24,062,334✔
254
    m_inhibited = 0;
24,062,334✔
255
    m_sabotaged = 0;
24,062,334✔
256
    m_jammed = false;
24,062,334✔
257
    m_overloaded = false;
24,062,334✔
258
    m_paybacked = 0;
24,062,334✔
259
    m_tributed = 0;
24,062,334✔
260
    m_poisoned = 0;
24,062,334✔
261
    m_protected = 0;
24,062,334✔
262
    m_protected_stasis = 0;
24,062,334✔
263
    m_enraged = 0;
24,062,334✔
264
    m_entrapped = 0;
24,062,334✔
265
    m_marked = 0;
24,062,334✔
266
    m_diseased = 0;
24,062,334✔
267
    m_rush_attempted = false;
24,062,334✔
268
    m_sundered = false;
24,062,334✔
269
    //APN
270
    m_summoned = false;
24,062,334✔
271

272
    std::memset(m_primary_skill_offset, 0, sizeof m_primary_skill_offset);
24,062,334✔
273
    std::memset(m_evolved_skill_offset, 0, sizeof m_evolved_skill_offset);
24,062,334✔
274
    std::memset(m_enhanced_value, 0, sizeof m_enhanced_value);
24,062,334✔
275
    std::memset(m_skill_cd, 0, sizeof m_skill_cd);
24,062,334✔
276
}
24,062,334✔
277
//------------------------------------------------------------------------------
278
/**
279
 * @brief Calculate the attack power of the CardStatus.
280
 * 
281
 * @return unsigned 
282
 */
283
inline unsigned CardStatus::attack_power() const
183,403,030✔
284
{
285
    signed attack = calc_attack_power();
183,403,030✔
286
    if(__builtin_expect(attack <0,false))
183,403,030✔
287
    {
288
        std::cout << m_card->m_name << " " << m_card->m_attack  << " " << attack << " " << m_temp_attack_buff << " " << m_corroded_weakened << std::endl;
×
289
    }
290
    _DEBUG_ASSERT(attack >= 0);
183,403,030✔
291
    return (unsigned)attack;
183,403,030✔
292
}
293

294
/**
295
 * @brief Calculate the attack power of the CardStatus.
296
 * 
297
 * The attack power is the base attack plus. 
298
 * The subdued value gets subtracted from the permanent attack buff, if this is above zero it is added to the attack power.
299
 * The corroded value gets subtracted from the attack power, but not below zero.
300
 * Finally the temporary attack buff is added.
301
 * 
302
 * @return signed attack power.
303
 */
304
inline signed CardStatus::calc_attack_power() const
184,171,292✔
305
{
306
    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."
184,171,292✔
307
    {
308
        return
184,171,292✔
309
        (signed)safe_minus(
368,342,584✔
310
                m_card->m_attack + safe_minus(m_perm_attack_buff, m_subdued)+ m_temp_attack_buff,
184,171,292✔
311
                m_corroded_weakened
184,171,292✔
312
                );
184,171,292✔
313
    }
314
    else{
315
        return
×
316
        (signed)safe_minus(
×
317
                m_card->m_attack + safe_minus(m_perm_attack_buff, m_subdued),
×
318
                m_corroded_weakened
×
319
                )
320
        + m_temp_attack_buff;
×
321
    }
322

323
}
324
//------------------------------------------------------------------------------
325
const Card* card_by_id_safe(const Cards& cards, const unsigned card_id)
×
326
{
327
    const auto cardIter = cards.cards_by_id.find(card_id);
×
328
    if (cardIter == cards.cards_by_id.end()) assert(false);//"UnknownCard.id[" + to_string(card_id) + "]"); }
×
329
    return cardIter->second;
×
330
}
331
std::string card_name_by_id_safe(const Cards& cards, const unsigned card_id)
108,331✔
332
{
333
    const auto cardIter = cards.cards_by_id.find(card_id);
108,331✔
334
    if (cardIter == cards.cards_by_id.end()) { return "UnknownCard.id[" + tuo::to_string(card_id) + "]"; }
108,331✔
335
    return cardIter->second->m_name;
216,662✔
336
}
337
//------------------------------------------------------------------------------
338
std::string card_description(const Cards& cards, const Card* c)
2,511,535✔
339
{
340
    std::string desc;
2,511,535✔
341
    desc = c->m_name;
2,511,535✔
342
    switch(c->m_type)
2,511,535✔
343
    {
344
        case CardType::assault:
2,074,705✔
345
            desc += ": " + tuo::to_string(c->m_attack) + "/" + tuo::to_string(c->m_health) + "/" + tuo::to_string(c->m_delay);
10,373,525✔
346
            break;
2,074,705✔
347
        case CardType::structure:
436,266✔
348
            desc += ": " + tuo::to_string(c->m_health) + "/" + tuo::to_string(c->m_delay);
1,745,064✔
349
            break;
436,266✔
350
        case CardType::commander:
564✔
351
            desc += ": hp:" + tuo::to_string(c->m_health);
1,692✔
352
            break;
564✔
353
        case CardType::num_cardtypes:
×
354
            assert(false);
×
355
            break;
356
    }
357
    if (c->m_rarity >= 4) { desc += " " + rarity_names[c->m_rarity]; }
4,215,588✔
358
    if (c->m_faction != allfactions) { desc += " " + faction_names[c->m_faction]; }
5,023,070✔
359
    for (auto& skill: c->m_skills_on_play) { desc += ", " + skill_description(cards, skill, Skill::Trigger::play); }
2,644,329✔
360
    for (auto& skill: c->m_skills) { desc += ", " + skill_description(cards, skill, Skill::Trigger::activate); }
16,611,859✔
361
    //APN
362
    for (auto& skill: c->m_skills_on_attacked) { desc += ", " + skill_description(cards, skill, Skill::Trigger::attacked); }
3,075,355✔
363
    for (auto& skill: c->m_skills_on_death) { desc += ", " + skill_description(cards, skill, Skill::Trigger::death); }
2,686,499✔
364
    return desc;
2,511,535✔
365
}
×
366
//------------------------------------------------------------------------------
367
std::string CardStatus::description() const
55,193,267✔
368
{
369
    std::string desc = "P" + tuo::to_string(m_player) + " ";
165,579,801✔
370
    switch(m_card->m_type)
55,193,267✔
371
    {
372
        case CardType::commander: desc += "Commander "; break;
17,017,377✔
373
        case CardType::assault: desc += "Assault " + tuo::to_string(m_index) + " "; break;
127,093,796✔
374
        case CardType::structure: desc += "Structure " + tuo::to_string(m_index) + " "; break;
25,609,764✔
375
        case CardType::num_cardtypes: assert(false); break;
×
376
    }
377
    desc += "[" + m_card->m_name;
110,386,534✔
378
    switch (m_card->m_type)
55,193,267✔
379
    {
380
        case CardType::assault:
31,773,449✔
381
            desc += " att:[[" + tuo::to_string(m_card->m_attack) + "(base)";
127,093,796✔
382
            if (m_perm_attack_buff)
31,773,449✔
383
            {
384
                desc += "+[" + tuo::to_string(m_perm_attack_buff) + "(perm)";
28,940,472✔
385
                if (m_subdued) { desc += "-" + tuo::to_string(m_subdued) + "(subd)"; }
8,495,685✔
386
                desc += "]";
7,235,118✔
387
            }
388
            if (m_corroded_weakened) { desc += "-" + tuo::to_string(m_corroded_weakened) + "(corr)"; }
33,184,988✔
389
            desc += "]";
31,773,449✔
390
            if (m_temp_attack_buff) { desc += (m_temp_attack_buff > 0 ? "+" : "") + tuo::to_string(m_temp_attack_buff) + "(temp)"; }
48,773,678✔
391
            desc += "]=" + tuo::to_string(attack_power());
95,320,347✔
392
        case CardType::structure:
55,193,267✔
393
        case CardType::commander:
55,193,267✔
394
            desc += " hp:" + tuo::to_string(m_hp);
165,579,801✔
395
            if(m_absorption)desc += " absorb:" + tuo::to_string(m_absorption);
60,898,485✔
396
            break;
397
        case CardType::num_cardtypes:
×
398
            assert(false);
×
399
            break;
400
    }
401
    if (m_delay) { desc += " cd:" + tuo::to_string(m_delay); }
79,829,985✔
402
    // Status w/o value
403
    if (m_jammed) { desc += ", jammed"; }
55,193,267✔
404
    if (m_overloaded) { desc += ", overloaded"; }
55,193,267✔
405
    if (m_sundered) { desc += ", sundered"; }
55,193,267✔
406
    if (m_summoned) { desc+= ", summoned"; }
55,193,267✔
407
    // Status w/ value
408
    if (m_corroded_weakened || m_corroded_rate) { desc += ", corroded " + tuo::to_string(m_corroded_weakened) + " (rate: " + tuo::to_string(m_corroded_rate) + ")"; }
57,210,003✔
409
    if (m_subdued) { desc += ", subdued " + tuo::to_string(m_subdued); }
57,328,577✔
410
    if (m_enfeebled) { desc += ", enfeebled " + tuo::to_string(m_enfeebled); }
60,455,163✔
411
    if (m_inhibited) { desc += ", inhibited " + tuo::to_string(m_inhibited); }
58,191,743✔
412
    if (m_sabotaged) { desc += ", sabotaged " + tuo::to_string(m_sabotaged); }
58,389,923✔
413
    if (m_poisoned) { desc += ", poisoned " + tuo::to_string(m_poisoned); }
56,987,357✔
414
    if (m_protected) { desc += ", protected " + tuo::to_string(m_protected); }
76,252,213✔
415
    if (m_protected_stasis) { desc += ", stasis " + tuo::to_string(m_protected_stasis); }
60,015,569✔
416
    if (m_enraged) { desc += ", enraged " + tuo::to_string(m_enraged); }
61,355,631✔
417
    if (m_entrapped) { desc += ", entrapped " + tuo::to_string(m_entrapped); }
74,824,095✔
418
    if (m_marked) { desc += ", marked " + tuo::to_string(m_marked); }
55,496,831✔
419
    if (m_diseased) { desc += ", diseased " + tuo::to_string(m_diseased); }
56,285,295✔
420
    //    if(m_step != CardStep::none) { desc += ", Step " + to_string(static_cast<int>(m_step)); }
421
    //APN
422
    const Skill::Trigger s_triggers[] = { Skill::Trigger::play, Skill::Trigger::activate, Skill::Trigger::death , Skill::Trigger::attacked};
55,193,267✔
423
    for (const Skill::Trigger& trig: s_triggers)
275,966,335✔
424
    {
425
        std::vector<SkillSpec> card_skills(
220,773,068✔
426
                (trig == Skill::Trigger::play) ? m_card->m_skills_on_play :
220,773,068✔
427
                (trig == Skill::Trigger::activate) ? m_card->m_skills :
165,579,801✔
428
                //APN
429
                (trig == Skill::Trigger::attacked) ? m_card->m_skills_on_attacked :
110,386,534✔
430
                (trig == Skill::Trigger::death) ? m_card->m_skills_on_death :
55,193,267✔
431
                std::vector<SkillSpec>());
220,773,068✔
432

433
        // emulate Berserk/Counter by status Enraged/Entrapped unless such skills exist (only for normal skill triggering)
434
        if (trig == Skill::Trigger::activate)
220,773,068✔
435
        {
436
            if (m_enraged && !std::count_if(card_skills.begin(), card_skills.end(), [](const SkillSpec ss) { return (ss.id == Skill::berserk); }))
58,274,449✔
437
            {
438
                SkillSpec ss{Skill::berserk, m_enraged, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
3,040,179✔
439
                card_skills.emplace_back(ss);
3,040,179✔
440
            }
441
            if (m_entrapped && !std::count_if(card_skills.begin(), card_skills.end(), [](const SkillSpec ss) { return (ss.id == Skill::counter); }))
65,008,681✔
442
            {
443
                SkillSpec ss{Skill::counter, m_entrapped, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
9,610,027✔
444
                card_skills.emplace_back(ss);
9,610,027✔
445
            }
446
        }
447
        for (const auto& ss : card_skills)
398,266,522✔
448
        {
449
            std::string skill_desc;
177,493,454✔
450
            if (m_evolved_skill_offset[ss.id]) { skill_desc += "->" + skill_names[ss.id + m_evolved_skill_offset[ss.id]]; }
177,891,981✔
451
            if (m_enhanced_value[ss.id]) { skill_desc += " +" + tuo::to_string(m_enhanced_value[ss.id]); }
180,367,060✔
452
            if (!skill_desc.empty())
177,493,454✔
453
            {
454
                desc += ", " + (
3,670,660✔
455
                        (trig == Skill::Trigger::play) ? "(On Play)" :
3,670,660✔
456
                        (trig == Skill::Trigger::attacked) ? "(On Attacked)" :
1,835,330✔
457
                        (trig == Skill::Trigger::death) ? "(On Death)" :
1,835,330✔
458
                        std::string("")) + skill_names[ss.id] + skill_desc;
9,176,650✔
459
            }
460
        }
177,493,454✔
461
    }
220,773,068✔
462
    return desc + "]";
55,193,267✔
463
}
55,193,267✔
464
//------------------------------------------------------------------------------
465
void Hand::reset(std::mt19937& re)
2,714,426✔
466
{
467
    assaults.reset();
2,714,426✔
468
    structures.reset();
2,714,426✔
469
    deck->shuffle(re);
2,714,426✔
470
    commander.set(deck->shuffled_commander);
2,714,426✔
471
    total_cards_destroyed = 0;
2,714,426✔
472
    total_nonsummon_cards_destroyed = 0;
2,714,426✔
473
    if (commander.skill(Skill::stasis))
2,714,426✔
474
    {
475
        stasis_faction_bitmap |= (1u << commander.m_card->m_faction);
×
476
    }
477
}
2,714,426✔
478

479
//---------------------- $40 Game rules implementation -------------------------
480
// Everything about how a battle plays out, except the following:
481
// the implementation of the attack by an assault card is in the next section;
482
// the implementation of the active skills is in the section after that.
483
unsigned turn_limit{50};
484

485
//------------------------------------------------------------------------------
486
inline unsigned opponent(unsigned player)
51,001,084✔
487
{
488
    return((player + 1) % 2);
51,001,084✔
489
}
490

491
//------------------------------------------------------------------------------
492
SkillSpec apply_evolve(const SkillSpec& s, signed offset)
246,410✔
493
{
494
    SkillSpec evolved_s = s;
246,410✔
495
    evolved_s.id = static_cast<Skill::Skill>(evolved_s.id + offset);
246,410✔
496
    return(evolved_s);
246,410✔
497
}
498

499
//------------------------------------------------------------------------------
500
SkillSpec apply_enhance(const SkillSpec& s, unsigned enhanced_value)
×
501
{
502
    SkillSpec enahnced_s = s;
×
503
    enahnced_s.x += enhanced_value;
×
504
    return(enahnced_s);
×
505
}
506

507
//------------------------------------------------------------------------------
508
SkillSpec apply_sabotage(const SkillSpec& s, unsigned sabotaged_value)
139,726✔
509
{
510
    SkillSpec sabotaged_s = s;
139,726✔
511
    sabotaged_s.x -= std::min(sabotaged_s.x, sabotaged_value);
×
512
    return(sabotaged_s);
139,726✔
513
}
514

515
//------------------------------------------------------------------------------
516
inline void resolve_scavenge(Storage<CardStatus>& store)
37,414,672✔
517
{
518
    for(auto status : store.m_indirect)
142,540,899✔
519
    {
520
        if(!is_alive(status))continue;
105,126,227✔
521
        unsigned scavenge_value = status->skill(Skill::scavenge);
91,958,530✔
522
        if(!scavenge_value)continue;
91,958,530✔
523

524
        _DEBUG_MSG(1, "%s activates Scavenge %u\n",
6,346,900✔
525
                status_description(status).c_str(), scavenge_value);
6,346,900✔
526
        status->ext_hp(scavenge_value);
6,346,900✔
527
    }
528
}
37,414,672✔
529
//------------------------------------------------------------------------------
530
/**
531
 * @brief Handle death of a card
532
 * 
533
 * @param fd Field pointer 
534
 * @param paybacked Is the death caused by payback?
535
 */
536
void prepend_on_death(Field* fd, bool paybacked=false)
121,288,017✔
537
{
538
    if (fd->killed_units.empty())
121,288,017✔
539
        return;
540
    bool skip_all_except_summon = fd->fixes[Fix::death_from_bge] && (fd->current_phase == Field::bge_phase);
9,241,477✔
541
    if (skip_all_except_summon)
×
542
    {
543
        _DEBUG_MSG(2, "Death from BGE Fix (skip all death depended triggers except summon)\n");
×
544
    }
545
    auto& assaults = fd->players[fd->killed_units[0]->m_player]->assaults;
9,241,477✔
546
    unsigned stacked_poison_value = 0;
9,241,477✔
547
    unsigned last_index = 99999;
9,241,477✔
548
    CardStatus* left_virulence_victim = nullptr;
9,241,477✔
549
    for (auto status: fd->killed_units)
18,595,145✔
550
    {
551
        // Skill: Scavenge
552
        // Any unit dies => perm-hp-buff
553
        if (__builtin_expect(!skip_all_except_summon, true))
9,353,668✔
554
        {
555
            resolve_scavenge(fd->players[0]->assaults);
9,353,668✔
556
            resolve_scavenge(fd->players[1]->assaults);
9,353,668✔
557
            resolve_scavenge(fd->players[0]->structures);
9,353,668✔
558
            resolve_scavenge(fd->players[1]->structures);
9,353,668✔
559
        }
560

561
        if ((status->m_card->m_type == CardType::assault) && (!skip_all_except_summon))
9,353,668✔
562
        {
563
            // Skill: Avenge
564
            const unsigned host_idx = status->m_index;
8,389,737✔
565
            unsigned from_idx, till_idx;
8,389,737✔
566
            //scan all assaults for Avenge
567
            from_idx = 0;
8,389,737✔
568
            till_idx = assaults.size() - 1;
8,389,737✔
569
            for (; from_idx <= till_idx; ++ from_idx)
32,626,505✔
570
            {
571
                if (from_idx == host_idx) { continue; }
36,816,426✔
572
                CardStatus* adj_status = &assaults[from_idx];
15,847,031✔
573
                if (!is_alive(adj_status)) { continue; }
15,847,031✔
574
                unsigned avenge_value = adj_status->skill(Skill::avenge);
13,912,522✔
575
                if (!avenge_value) { continue; }
13,912,522✔
576

577
                // Use half value rounded up
578
                // (for distance > 1, i. e. non-standard Avenge triggering)
579
                if (std::abs((signed)from_idx - (signed)host_idx) > 1)
3,267,373✔
580
                {
581
                    avenge_value = (avenge_value + 1) / 2;
1,802,938✔
582
                }
583
                _DEBUG_STRAP(
3,267,373✔
584
                        "turn", fd->turn,
585
                        "active_player", fd->tapi,
586
                        "avenge_"+strap_string(adj_status), 1.0,
587
                        "avenge_value", avenge_value,
588
                        "avenge_half", (std::abs((signed)from_idx - (signed)host_idx) > 1 ? 1 : 0)
589
                );
3,267,373✔
590
                _DEBUG_MSG(1, "%s activates %sAvenge %u\n",
3,424,386✔
591
                        status_description(adj_status).c_str(),
592
                        (std::abs((signed)from_idx - (signed)host_idx) > 1 ? "Half-" : ""),
593
                         avenge_value);
3,267,373✔
594
                if (!adj_status->m_sundered)
3,267,373✔
595
                { adj_status->m_perm_attack_buff += avenge_value; }
3,044,186✔
596
                adj_status->ext_hp(avenge_value);
3,267,373✔
597
            }
598

599
            // Passive BGE: Virulence
600
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::virulence], false))
8,389,737✔
601
            {
602
                if (status->m_index != last_index + 1)
34,293✔
603
                {
604
                    stacked_poison_value = 0;
33,879✔
605
                    left_virulence_victim = nullptr;
33,879✔
606
                    if (status->m_index > 0)
33,879✔
607
                    {
608
                        auto left_status = &assaults[status->m_index - 1];
7,165✔
609
                        if (is_alive(left_status))
7,165✔
610
                        {
611
                            left_virulence_victim = left_status;
34,293✔
612
                        }
613
                    }
614
                }
615
                if (status->m_poisoned > 0)
34,293✔
616
                {
617
                    if (left_virulence_victim != nullptr)
1,471✔
618
                    {
619
                        _DEBUG_MSG(1, "Virulence: %s spreads left poison +%u to %s\n",
135✔
620
                                status_description(status).c_str(), status->m_poisoned,
621
                                status_description(left_virulence_victim).c_str());
135✔
622
                        left_virulence_victim->m_poisoned += status->m_poisoned;
135✔
623
                    }
624
                    stacked_poison_value += status->m_poisoned;
1,471✔
625
                    _DEBUG_MSG(1, "Virulence: %s spreads right poison +%u = %u\n",
1,471✔
626
                            status_description(status).c_str(), status->m_poisoned, stacked_poison_value);
627
                }
628
                if (status->m_index + 1 < assaults.size())
34,293✔
629
                {
630
                    auto right_status = &assaults[status->m_index + 1];
10,980✔
631
                    if (is_alive(right_status))
10,980✔
632
                    {
633
                        _DEBUG_MSG(1, "Virulence: spreads stacked poison +%u to %s\n",
10,109✔
634
                                stacked_poison_value, status_description(right_status).c_str());
10,109✔
635
                        right_status->m_poisoned += stacked_poison_value;
10,109✔
636
                    }
637
                }
638
                last_index = status->m_index;
34,293✔
639
            }
640
        }
641

642
        // Passive BGE: Revenge
643
        // Fix::death_from_bge: should not affect passive BGE, keep it as was before
644
        if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::revenge], false))
9,353,668✔
645
        {
646
            if (fd->bg_effects[fd->tapi][PassiveBGE::revenge] < 0)
72,910✔
647
                throw std::runtime_error("BGE Revenge: value must be defined & positive");
×
648
            SkillSpec ss_heal{Skill::heal, (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::revenge], allfactions, 0, 0, Skill::no_skill, Skill::no_skill, true, 0,};
72,910✔
649
            SkillSpec ss_rally{Skill::rally, (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::revenge], allfactions, 0, 0, Skill::no_skill, Skill::no_skill, true, 0,};
72,910✔
650
            CardStatus* commander = &fd->players[status->m_player]->commander;
72,910✔
651
            _DEBUG_MSG(2, "Revenge: Preparing (head) skills  %s and %s\n",
72,910✔
652
                    skill_description(fd->cards, ss_heal).c_str(),
653
                    skill_description(fd->cards, ss_rally).c_str());
72,910✔
654
            fd->skill_queue.emplace(fd->skill_queue.begin()+0, commander, ss_heal);
72,910✔
655
            fd->skill_queue.emplace(fd->skill_queue.begin()+1, commander, ss_rally); // +1: keep ss_heal at first place
72,910✔
656
        }
657

658
        // resolve On-Death skills
659
        for (auto& ss: status->m_card->m_skills_on_death)
9,737,190✔
660
        {
661
            if (__builtin_expect(skip_all_except_summon && (ss.id != Skill::summon), false))
383,522✔
662
            { continue; }
×
663
                SkillSpec tss = ss;
383,522✔
664
            _DEBUG_MSG(2, "On Death %s: Preparing (tail) skill %s\n",
383,522✔
665
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
383,522✔
666
            if(fd->fixes[Fix::revenge_on_death] && is_activation_harmful_skill(ss.id) && paybacked)
383,522✔
667
            {
668
                    _DEBUG_MSG(2, "On Death Revenge Fix\n");
286✔
669
                    tss.s2 = Skill::revenge;
286✔
670
            }
671
            fd->skill_queue.emplace_back(status, tss);
383,522✔
672
        }
673
    }
674
    fd->killed_units.clear();
130,529,494✔
675
}
676

677
//------------------------------------------------------------------------------
678
void(*skill_table[Skill::num_skills])(Field*, CardStatus* src, const SkillSpec&);
679
void resolve_skill(Field* fd)
174,537,770✔
680
{
681
    while (!fd->skill_queue.empty())
283,100,432✔
682
    {
683
        auto skill_instance(fd->skill_queue.front());
108,562,662✔
684
        auto& status(std::get<0>(skill_instance));
108,562,662✔
685
        const auto& ss(std::get<1>(skill_instance));
108,562,662✔
686
        fd->skill_queue.pop_front();
108,562,662✔
687
        if (__builtin_expect(status->m_card->m_skill_trigger[ss.id] == Skill::Trigger::activate, true))
108,562,662✔
688
        {
689
            if (!is_alive(status))
104,318,273✔
690
            {
691
                _DEBUG_MSG(2, "%s failed to %s because it is dead.\n",
4,001✔
692
                        status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
4,001✔
693
                continue;
3,044,935✔
694
            }
4,001✔
695
            if (status->m_jammed)
104,314,272✔
696
            {
697
                _DEBUG_MSG(2, "%s failed to %s because it is Jammed.\n",
1,402✔
698
                        status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
1,402✔
699
                continue;
1,402✔
700
            }
1,402✔
701
        }
702

703
        // is summon? (non-activation skill)
704
        if (ss.id == Skill::summon)
108,557,259✔
705
        {
706
            check_and_perform_summon(fd, status);
2,969,566✔
707
            continue;
2,969,566✔
708
        }
709
        _DEBUG_ASSERT(is_activation_skill(ss.id) || ss.id == Skill::enhance); // enhance is no trigger, but  queues the skill
105,680,332✔
710

711
        SkillSpec modified_s = ss;
105,587,693✔
712

713
        // apply evolve
714
        signed evolved_offset = status->m_evolved_skill_offset[modified_s.id];
105,587,693✔
715
        if (evolved_offset != 0)
105,587,693✔
716
        { modified_s = apply_evolve(modified_s, evolved_offset); }
246,410✔
717

718
        // apply sabotage (only for X-based activation skills)
719
        unsigned sabotaged_value = status->m_sabotaged;
105,587,693✔
720
        if ((sabotaged_value > 0) && is_activation_skill_with_x(modified_s.id))
105,587,693✔
721
        { modified_s = apply_sabotage(modified_s, sabotaged_value); }
209,486✔
722

723
        // apply enhance
724
        unsigned enhanced_value = status->enhanced(modified_s.id);
105,587,693✔
725
        if (enhanced_value > 0)
105,587,693✔
726
        { modified_s = apply_enhance(modified_s, enhanced_value); }
×
727

728
        // perform skill (if it is still applicable)
729
        if (is_activation_skill_with_x(modified_s.id) && !modified_s.x)
105,587,693✔
730
        {
731
            _DEBUG_MSG(2, "%s failed to %s because its X value is zeroed (sabotaged).\n",
69,966✔
732
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
69,966✔
733
            continue;
69,966✔
734
        }
69,966✔
735
        else { skill_table[modified_s.id](fd, status, modified_s); }
105,517,727✔
736
    }
737
}
174,537,770✔
738

739
void apply_corrosion(CardStatus * status)
19,894,395✔
740
{
741
      if (status->m_corroded_rate)
19,894,395✔
742
      {
743
        unsigned v = std::min(status->m_corroded_rate, status->attack_power());
98,677✔
744
        unsigned corrosion = std::min(v, status->m_card->m_attack
197,354✔
745
                + status->m_perm_attack_buff - status->m_corroded_weakened);
98,677✔
746
        _DEBUG_MSG(1, "%s loses Attack by %u (+corrosion %u).\n", status_description(status).c_str(), v, corrosion);
98,677✔
747
        status->m_corroded_weakened += corrosion;
98,677✔
748
      }
749
}
19,894,395✔
750
void remove_corrosion(CardStatus * status)
2,790,568✔
751
{
752
       if (status->m_corroded_rate)
2,790,568✔
753
       {
754
           _DEBUG_MSG(1, "%s loses Status corroded.\n", status_description(status).c_str());
4,006✔
755
           status->m_corroded_rate = 0;
4,006✔
756
           status->m_corroded_weakened = 0;
4,006✔
757
       }
758
}
2,790,568✔
759
//------------------------------------------------------------------------------
760
bool attack_phase(Field* fd);
761

762
template<Skill::Skill skill_id>
763
inline bool check_and_perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s, bool is_evadable
764
#ifndef NQUEST
765
        , bool & has_counted_quest
766
#endif
767
        );
768

769
    template <enum CardType::CardType type>
770
void evaluate_skills(Field* fd, CardStatus* status, const std::vector<SkillSpec>& skills, bool* attacked=nullptr)
58,005,426✔
771
{
772
    _DEBUG_ASSERT(status);
58,005,426✔
773
    unsigned num_actions(1);
774
    for (unsigned action_index(0); action_index < num_actions; ++ action_index)
120,509,867✔
775
    {
776
        status->m_action_index = action_index;
63,840,686✔
777
        fd->prepare_action();
63,840,686✔
778
        _DEBUG_ASSERT(fd->skill_queue.size() == 0);
63,840,686✔
779
        for (auto & ss: skills)
246,281,811✔
780
        {
781
            if (!is_activation_skill(ss.id)) { continue; }
259,989,293✔
782
            if (status->m_skill_cd[ss.id] > 0) { continue; }
104,892,957✔
783
            _DEBUG_MSG(2, "Evaluating %s skill %s\n",
104,079,814✔
784
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
785
            fd->skill_queue.emplace_back(status, ss);
104,079,814✔
786
            resolve_skill(fd);
104,079,814✔
787
        }
788
        if (type == CardType::assault)
789
        {
790
            // Attack
791
            if (can_act(status))
24,021,208✔
792
            {
793
                if (attack_phase(fd))
23,845,309✔
794
                {
795
                    *attacked = true;
21,230,640✔
796
                    if (__builtin_expect(fd->end, false)) { break; }
21,230,640✔
797
                    //Apply corrosion
798
                    apply_corrosion(status);
19,894,395✔
799
                }
800
                else
801
                {
802
                  // Remove Corrosion
803
                  remove_corrosion(status);
2,614,669✔
804
                }
805
            }
806
            else
807
            {
808
                _DEBUG_MSG(2, "%s cannot take attack.\n", status_description(status).c_str());
175,899✔
809
                // Remove Corrosion
810
                remove_corrosion(status);
175,899✔
811
            }
812
        }
813
        fd->finalize_action();
62,504,441✔
814
        // Flurry
815
        if (can_act(status) && status->has_skill(Skill::flurry) && (status->m_skill_cd[Skill::flurry] == 0))
126,683,425✔
816
        {
817
#ifndef NQUEST
818
            if (status->m_player == 0)
819
            {
820
                fd->inc_counter(QuestType::skill_use, Skill::flurry);
821
            }
822
#endif
823
            _DEBUG_MSG(1, "%s activates Flurry x %d\n",
2,555,252✔
824
                    status_description(status).c_str(), status->skill_base_value(Skill::flurry));
825
            num_actions += status->skill_base_value(Skill::flurry);
2,423,999✔
826
            for (const auto & ss : skills)
9,692,849✔
827
            {
828
                Skill::Skill evolved_skill_id = static_cast<Skill::Skill>(ss.id + status->m_evolved_skill_offset[ss.id]);
7,268,850✔
829
                if (evolved_skill_id == Skill::flurry)
7,268,850✔
830
                {
831
                    status->m_skill_cd[ss.id] = ss.c;
2,423,999✔
832
                }
833
            }
834
        }
835
    }
836
}
58,005,426✔
837

838
struct PlayCard
839
{
840
    const Card* card;
841
    Field* fd;
842
    CardStatus* status;
843
    Storage<CardStatus>* storage;
844
    const unsigned actor_index;
845
    const CardStatus* actor_status;
846

847
    PlayCard(const Card* card_, Field* fd_, unsigned ai_, CardStatus* as_) :
21,347,908✔
848
        card{card_},
21,347,908✔
849
        fd{fd_},
21,347,908✔
850
        status{nullptr},
21,347,908✔
851
        storage{nullptr},
21,347,908✔
852
        actor_index{ai_},
21,347,908✔
853
        actor_status{as_}
21,347,908✔
854
    {}
855

856
    template <enum CardType::CardType type>
857
        CardStatus* op()
17,995,382✔
858
        {
859
            return op<type>(false);
×
860
        }
861

862
    template <enum CardType::CardType type>
863
        CardStatus* op(bool summoned)
21,347,908✔
864
        {
865
            setStorage<type>();
21,347,908✔
866
            placeCard<type>(summoned);
21,347,908✔
867

868
            unsigned played_faction_mask(0);
21,347,908✔
869
            unsigned same_faction_cards_count(0);
21,347,908✔
870
            bool bge_megamorphosis = fd->bg_effects[status->m_player][PassiveBGE::megamorphosis];
21,347,908✔
871
            //played_status = status;
872
            //played_card = card;
873
            if(__builtin_expect(fd->fixes[Fix::barrier_each_turn],true) && status->has_skill(Skill::barrier)){
21,347,908✔
874
                _DEBUG_MSG(1,"%s gets barrier protection %u per turn\n",status_description(status).c_str(),status->skill(Skill::barrier));
394,754✔
875
                status->m_protected += status->skill(Skill::barrier);
281,864✔
876
            }
877
            if (status->m_delay == 0)
21,347,908✔
878
            {
879
                    check_and_perform_bravery(fd,status);
1,321,941✔
880
                check_and_perform_valor(fd, status);
1,321,941✔
881
            }
882
            
883

884
            //refresh/init absorb
885
            if(status->has_skill(Skill::absorb))
21,347,908✔
886
            {
887
                status->m_absorption = status->skill_base_value(Skill::absorb);
1,252,175✔
888
            }
889

890

891
            // 1. Evaluate skill Allegiance & count assaults with same faction (structures will be counted later)
892
            // 2. Passive BGE Cold Sleep
893
            for (CardStatus* status_i : fd->players[status->m_player]->assaults.m_indirect)
82,016,305✔
894
            {
895
                if (status_i == status || !is_alive(status_i)) { continue; } // except itself
60,668,397✔
896
                //std::cout << status_description(status_i).c_str();
897
                _DEBUG_ASSERT(is_alive(status_i));
43,720,533✔
898
                if (bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction))
43,720,533✔
899
                {
900
                    ++ same_faction_cards_count;
17,728,138✔
901
                    unsigned allegiance_value = status_i->skill(Skill::allegiance);
17,728,138✔
902
                    if (__builtin_expect(allegiance_value, false) && !status->m_summoned)
17,728,138✔
903
                    {
904
                        _DEBUG_MSG(1, "%s activates Allegiance %u\n", status_description(status_i).c_str(), allegiance_value);
858,152✔
905
                        if (! status_i->m_sundered)
770,464✔
906
                        { status_i->m_perm_attack_buff += allegiance_value; }
739,829✔
907
                        status_i->ext_hp(allegiance_value);
770,464✔
908
                    }
909
                }
910
                if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::coldsleep], false)
43,720,533✔
911
                        && status_i->m_protected_stasis && can_be_healed(status_i))
104,388,930✔
912
                {
913
                    unsigned bge_value = (status_i->m_protected_stasis + 1) / 2;
2,073✔
914
                    _DEBUG_MSG(1, "Cold Sleep: %s heals itself for %u\n", status_description(status_i).c_str(), bge_value);
6,219✔
915
                    status_i->add_hp(bge_value);
2,073✔
916
                }
917
            }
918

919
            // Setup faction marks (bitmap) for stasis (skill Stasis / Passive BGE TemporalBacklash)
920
            // unless Passive BGE Megamorphosis is enabled
921
            if (__builtin_expect(!bge_megamorphosis, true))
21,347,908✔
922
            {
923
                played_faction_mask = (1u << card->m_faction);
21,283,029✔
924
                // do played card have stasis? mark this faction for stasis check
925
                if (__builtin_expect(status->skill(Skill::stasis), false)
21,283,029✔
926
                        || __builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::temporalbacklash] && status->skill(Skill::counter), false))
21,283,029✔
927
                {
928
                    fd->players[status->m_player]->stasis_faction_bitmap |= played_faction_mask;
3,845,951✔
929
                }
930
            }
931

932
            // Evaluate Passive BGE Oath-of-Loyalty
933
            unsigned allegiance_value;
934
            if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::oath_of_loyalty], false)
21,347,908✔
935
                    && ((allegiance_value = status->skill(Skill::allegiance)) > 0))
21,347,908✔
936
            {
937
                // count structures with same faction (except fortresses, dominions and other non-normal structures)
938
                for (CardStatus * status_i : fd->players[status->m_player]->structures.m_indirect)
2,097✔
939
                {
940
                    if ((status_i->m_card->m_category == CardCategory::normal)
889✔
941
                            && (bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)))
889✔
942
                    {
943
                        ++ same_faction_cards_count;
×
944
                    }
945
                }
946

947
                // apply Passive BGE Oath-of-Loyalty when multiplier isn't zero
948
                if (same_faction_cards_count)
1,208✔
949
                {
950
                    unsigned bge_value = allegiance_value * same_faction_cards_count;
454✔
951
                    _DEBUG_MSG(1, "Oath of Loyalty: %s activates Allegiance %u x %u = %u\n",
908✔
952
                            status_description(status).c_str(), allegiance_value, same_faction_cards_count, bge_value);
953
                    status->m_perm_attack_buff += bge_value;
454✔
954
                    status->ext_hp(bge_value);
454✔
955
                }
956
            }
957

958
            // summarize stasis when:
959
            //  1. Passive BGE Megamorphosis is enabled
960
            //  2. current faction is marked for it
961
            if ((card->m_delay > 0) && (card->m_type == CardType::assault)
20,025,967✔
962
                    && __builtin_expect(bge_megamorphosis || (fd->players[status->m_player]->stasis_faction_bitmap & played_faction_mask), false))
37,056,198✔
963
            {
964
                unsigned stacked_stasis = (bge_megamorphosis || (fd->players[status->m_player]->commander.m_card->m_faction == card->m_faction))
7,519,888✔
965
                    ? fd->players[status->m_player]->commander.skill(Skill::stasis)
13,804,929✔
966
                    : 0u;
967
#ifndef NDEBUG
968
                if (stacked_stasis > 0)
6,285,041✔
969
                {
970
                    _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
×
971
                            faction_names[card->m_faction].c_str(), stacked_stasis,
972
                            status_description(&fd->players[status->m_player]->commander).c_str(), stacked_stasis);
973
                }
974
#endif
975
                for (CardStatus * status_i : fd->players[status->m_player]->structures.m_indirect)
14,121,191✔
976
                {
977
                    if ((bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)) && is_alive(status_i))
6,558,231✔
978
                    {
979
                        stacked_stasis += status_i->skill(Skill::stasis);
1,839,956✔
980
#ifndef NDEBUG
981
                        if (status_i->skill(Skill::stasis) > 0)
1,839,956✔
982
                        {
983
                            _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
6,558,231✔
984
                                    faction_names[card->m_faction].c_str(), status_i->skill(Skill::stasis),
985
                                    status_description(status_i).c_str(), stacked_stasis);
986
                        }
987
#endif
988
                    }
989
                }
990
                for (CardStatus * status_i : fd->players[status->m_player]->assaults.m_indirect)
29,123,423✔
991
                {
992
                    if ((bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)) && is_alive(status_i))
21,560,463✔
993
                    {
994
                        stacked_stasis += status_i->skill(Skill::stasis);
16,516,145✔
995
#ifndef NDEBUG
996
                        if (status_i->skill(Skill::stasis) > 0)
16,516,145✔
997
                        {
998
                            _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
7,297,239✔
999
                                    faction_names[card->m_faction].c_str(), status_i->skill(Skill::stasis),
1000
                                    status_description(status_i).c_str(), stacked_stasis);
1001
                        }
1002
#endif
1003
                        if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::temporalbacklash] && status_i->skill(Skill::counter), false))
16,516,145✔
1004
                        {
1005
                            stacked_stasis += (status_i->skill(Skill::counter) + 1) / 2;
3,003✔
1006
#ifndef NDEBUG
1007
                            _DEBUG_MSG(2, "Temporal Backlash: + Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
21,563,466✔
1008
                                    faction_names[card->m_faction].c_str(), (status_i->skill(Skill::counter) + 1) / 2,
1009
                                    status_description(status_i).c_str(), stacked_stasis);
1010
#endif
1011
                        }
1012
                    }
1013
                }
1014
                status->m_protected_stasis = stacked_stasis;
7,562,960✔
1015
#ifndef NDEBUG
1016
                if (stacked_stasis > 0)
7,562,960✔
1017
                {
1018
                    _DEBUG_MSG(1, "%s gains %u stasis protection\n",
6,168,794✔
1019
                            status_description(status).c_str(), stacked_stasis);
1020
                }
1021
#endif
1022
                // no more stasis for current faction: do unmark (if no Passive BGE Megamorphosis)
1023
                if (__builtin_expect(!bge_megamorphosis, true) && __builtin_expect(!stacked_stasis, false))
7,562,960✔
1024
                {
1025
                    fd->players[status->m_player]->stasis_faction_bitmap &= ~played_faction_mask;
1,628,368✔
1026
                    _DEBUG_MSG(1, "- Stasis [%s]: no more units with stasis from %s\n",
1,637,798✔
1027
                            faction_names[card->m_faction].c_str(),status_description(&fd->players[status->m_player]->commander).c_str());
1028
                }
1029

1030
            }
1031
            //Devotion BGE
1032
            if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::devotion], false)
21,347,908✔
1033
                    && !summoned && card->m_category == CardCategory::normal
64,540✔
1034
                    && fd->players[status->m_player]->commander.m_card->m_faction == card->m_faction)
21,407,628✔
1035
            {
1036
                unsigned devotion_percent = fd->bg_effects[status->m_player][PassiveBGE::devotion];
9,314✔
1037
                unsigned bge_buff = (card->m_health*devotion_percent+99)/100;
9,314✔
1038
                _DEBUG_MSG(1, "Devotion %s: Gains %u HP\n",
18,628✔
1039
                        status_description(status).c_str(), bge_buff);
1040
                status->ext_hp(bge_buff); // <bge_value>% bonus health (rounded up)
9,314✔
1041
            }
1042

1043

1044
            // resolve On-Play skills
1045
            // Fix Death on BGE: [On Play] skills during BGE phase can be invoked only by means of [On Death] trigger
1046
            if (__builtin_expect(fd->fixes[Fix::death_from_bge] && (fd->current_phase != Field::bge_phase), true))
21,347,908✔
1047
            {
1048
                for (const auto& ss: card->m_skills_on_play)
25,010,660✔
1049
                {
1050
                    _DEBUG_MSG(2, "On Play %s: Preparing (tail) skill %s\n",
3,662,752✔
1051
                            status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
1052
                    fd->skill_queue.emplace_back(status, ss);
3,662,752✔
1053
                }
1054
            }
1055
            else
1056
            {
1057
                _DEBUG_MSG(2, "Death from BGE Fix: suppress [On Play] skills invoked by [On Death] summon\n");
×
1058
            }
1059

1060
            return status;
21,347,908✔
1061
        }
1062

1063
    template <enum CardType::CardType>
1064
        void setStorage()
1065
        {
1066
        }
1067

1068
    template <enum CardType::CardType type>
1069
        void placeCard(bool summoned)
21,347,908✔
1070
        {
1071
            status = &storage->add_back();
21,347,908✔
1072
            status->set(card);
21,347,908✔
1073
            status->m_index = storage->size() - 1;
21,347,908✔
1074
            status->m_player = actor_index;
21,347,908✔
1075
            status->m_field = fd;
21,347,908✔
1076
            status->m_summoned = summoned;
21,347,908✔
1077

1078
            // reduce delay for summoned card by tower (structure) for normal (non-triggered) summon skill
1079
            if (summoned && __builtin_expect(fd->fixes[Fix::reduce_delay_summoned_by_tower], true)
21,347,908✔
1080
                && actor_status->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate
3,338,632✔
1081
                && actor_status->m_card->m_type == CardType::structure
372,425✔
1082
                && status->m_delay > 0)
8,352✔
1083
            {
1084
                --status->m_delay;
8,352✔
1085

1086
                _DEBUG_MSG(1, "%s reduces its timer (as summoned by tower)\n", status_description(status).c_str());
16,704✔
1087
            }
1088

1089
#ifndef NQUEST
1090
            if (actor_index == 0)
1091
            {
1092
                if (card->m_type == CardType::assault)
1093
                {
1094
                    fd->inc_counter(QuestType::faction_assault_card_use, card->m_faction);
1095
                }
1096
                fd->inc_counter(QuestType::type_card_use, type);
1097
            }
1098
#endif
1099
            _DEBUG_MSG(1, "%s plays %s %u [%s]\n",
23,855,086✔
1100
                    status_description(actor_status).c_str(), cardtype_names[type].c_str(),
1101
                    static_cast<unsigned>(storage->size() - 1), card_description(fd->cards, card).c_str());
1102
        }
21,347,908✔
1103
};
1104
// assault
1105
    template <>
1106
void PlayCard::setStorage<CardType::assault>()
16,893,926✔
1107
{
1108
    storage = &fd->players[actor_index]->assaults;
16,893,926✔
1109
}
×
1110
// structure
1111
    template <>
1112
void PlayCard::setStorage<CardType::structure>()
4,453,982✔
1113
{
1114
    storage = &fd->players[actor_index]->structures;
4,453,982✔
1115
}
×
1116

1117
// Check if a skill actually proc'ed.
1118
    template<Skill::Skill skill_id>
1119
inline bool skill_check(Field* fd, CardStatus* c, CardStatus* ref)
237,856,625✔
1120
{
1121
    return is_defensive_skill(skill_id) || is_alive(c);
2,640,950✔
1122
}
1123

1124
    template<>
1125
inline bool skill_check<Skill::heal>(Field* fd, CardStatus* c, CardStatus* ref)
69,306,965✔
1126
{
1127
    return can_be_healed(c);
38,453✔
1128
}
1129

1130
    template<>
1131
inline bool skill_check<Skill::mend>(Field* fd, CardStatus* c, CardStatus* ref)
159,557✔
1132
{
1133
    return can_be_healed(c);
×
1134
}
1135

1136
    template<>
1137
inline bool skill_check<Skill::rally>(Field* fd, CardStatus* c, CardStatus* ref)
35,546,775✔
1138
{
1139
    return !c->m_sundered;
35,546,775✔
1140
}
1141

1142
    template<>
1143
inline bool skill_check<Skill::overload>(Field* fd, CardStatus* c, CardStatus* ref)
839,500✔
1144
{
1145
    return is_active(c) && !c->m_overloaded && !has_attacked(c);
658,296✔
1146
}
1147

1148
    template<>
1149
inline bool skill_check<Skill::jam>(Field* fd, CardStatus* c, CardStatus* ref)
10,446,336✔
1150
{
1151
    if(__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::ironwill], false) && c->has_skill(Skill::armor))return false;
10,446,336✔
1152
    // active player performs Jam
1153
    if (fd->tapi == ref->m_player)
10,444,078✔
1154
    { return is_active_next_turn(c); }
10,415,610✔
1155

1156

1157
    // inactive player performs Jam
1158
    return will_activate_this_turn(c);
28,468✔
1159
}
1160

1161
    template<>
1162
inline bool skill_check<Skill::leech>(Field* fd, CardStatus* c, CardStatus* ref)
48,261✔
1163
{
1164
    return can_be_healed(c) || (fd->fixes[Fix::leech_increase_max_hp] && is_alive(c));
42,616✔
1165
}
1166

1167
    template<>
1168
inline bool skill_check<Skill::coalition>(Field* fd, CardStatus* c, CardStatus* ref)
1169
{
1170
    return is_active(c);
1171
}
1172

1173
    template<>
1174
inline bool skill_check<Skill::payback>(Field* fd, CardStatus* c, CardStatus* ref)
2,702,459✔
1175
{
1176
    return (ref->m_card->m_type == CardType::assault);
2,702,459✔
1177
}
1178

1179
    template<>
1180
inline bool skill_check<Skill::revenge>(Field* fd, CardStatus* c, CardStatus* ref)
1181
{
1182
    return skill_check<Skill::payback>(fd, c, ref);
1183
}
1184

1185
    template<>
1186
inline bool skill_check<Skill::tribute>(Field* fd, CardStatus* c, CardStatus* ref)
112,907,154✔
1187
{
1188
    return (ref->m_card->m_type == CardType::assault) && (c != ref);
112,907,154✔
1189
}
1190

1191
    template<>
1192
inline bool skill_check<Skill::refresh>(Field* fd, CardStatus* c, CardStatus* ref)
2,890,700✔
1193
{
1194
    return can_be_healed(c);
2,890,700✔
1195
}
1196

1197
    template<>
1198
inline bool skill_check<Skill::drain>(Field* fd, CardStatus* c, CardStatus* ref)
643,979✔
1199
{
1200
    return can_be_healed(c);
643,979✔
1201
}
1202

1203
    template<>
1204
inline bool skill_check<Skill::mark>(Field* fd, CardStatus* c, CardStatus* ref)
58,527✔
1205
{
1206
    return (ref->m_card->m_type == CardType::assault);
58,527✔
1207
}
1208

1209
    template<>
1210
inline bool skill_check<Skill::disease>(Field* fd, CardStatus* c, CardStatus* ref)
204,501✔
1211
{
1212
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
204,501✔
1213
}
1214
    template<>
1215
inline bool skill_check<Skill::inhibit>(Field* fd, CardStatus* c, CardStatus* ref)
2,687,141✔
1216
{
1217
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
2,687,141✔
1218
}
1219
    template<>
1220
inline bool skill_check<Skill::sabotage>(Field* fd, CardStatus* c, CardStatus* ref)
421,826✔
1221
{
1222
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
421,826✔
1223
}
1224
inline unsigned remove_disease(CardStatus* status, unsigned heal)
33,976,794✔
1225
{
1226
    unsigned remaining_heal(heal);
33,976,794✔
1227
    if(__builtin_expect(status->m_diseased == 0,true))
33,976,794✔
1228
    {
1229
        //skip
1230
    }
1231
    else if (heal > status->m_diseased)
75,894✔
1232
    {
1233
        _DEBUG_MSG(1, "%s disease-blocked %u heal\n", status_description(status).c_str(), status->m_diseased);
18,985✔
1234
        remaining_heal = heal - status->m_diseased;
18,985✔
1235
        status->m_diseased = 0;
18,985✔
1236
    }
1237
    else
1238
    {
1239
        _DEBUG_MSG(1, "%s disease-blocked %u heal\n", status_description(status).c_str(), heal);
56,909✔
1240
        status->m_diseased -= heal;
56,909✔
1241
        remaining_heal = 0;
56,909✔
1242
    }
1243
    return remaining_heal;
33,976,794✔
1244
}
1245

1246
// Field is currently not needed for remove_absorption, but is here for similiar structure as remove_hp
1247
inline unsigned remove_absorption(Field* fd, CardStatus* status, unsigned dmg)
24,622,033✔
1248
{
1249
    return remove_absorption(status,dmg);
24,622,033✔
1250
}
1251
/**
1252
 * @brief Remove absorption from a CardStatus
1253
 * 
1254
 * @param status CardStatus to remove absorption from
1255
 * @param dmg damage to remove absorption from
1256
 * @return unsigned remaining damage after absorption is removed
1257
 */
1258
inline unsigned remove_absorption(CardStatus* status, unsigned dmg)
24,622,033✔
1259
{
1260
    unsigned remaining_dmg(dmg);
24,622,033✔
1261
    if(__builtin_expect(status->m_absorption == 0,true))
24,622,033✔
1262
    {
1263
        //skip
1264
    }
1265
    else if(dmg > status->m_absorption)
2,540,721✔
1266
    {
1267
        _DEBUG_MSG(1, "%s absorbs %u damage\n", status_description(status).c_str(), status->m_absorption);
359,367✔
1268
        remaining_dmg = dmg - status->m_absorption;
359,367✔
1269
        status->m_absorption = 0;
359,367✔
1270
    }
1271
    else
1272
    {
1273
        _DEBUG_MSG(1, "%s absorbs %u damage\n", status_description(status).c_str(), dmg);
2,181,354✔
1274
        status->m_absorption -= dmg;
2,181,354✔
1275
        remaining_dmg = 0;
2,181,354✔
1276
    }
1277
    return remaining_dmg;
24,622,033✔
1278
}
1279

1280
void remove_hp(Field* fd, CardStatus* status, unsigned dmg)
35,494,090✔
1281
{
1282
    if (__builtin_expect(!dmg, false)) { return; }
35,494,090✔
1283
    _DEBUG_ASSERT(is_alive(status));
25,057,723✔
1284
    _DEBUG_MSG(2, "%s takes %u damage\n", status_description(status).c_str(), dmg);
25,057,723✔
1285
    status->m_hp = safe_minus(status->m_hp, dmg);
25,057,723✔
1286
    if (fd->current_phase < Field::end_phase && status->has_skill(Skill::barrier))
25,057,723✔
1287
    {
1288
        ++fd->damaged_units_to_times[status];
218,696✔
1289
        _DEBUG_MSG(2, "%s damaged %u times\n",
218,696✔
1290
                status_description(status).c_str(), fd->damaged_units_to_times[status]);
1291
    }
1292
    if (status->m_hp == 0)
25,057,723✔
1293
    {
1294
#ifndef NQUEST
1295
        if (status->m_player == 1)
1296
        {
1297
            if (status->m_card->m_type == CardType::assault)
1298
            {
1299
                fd->inc_counter(QuestType::faction_assault_card_kill, status->m_card->m_faction);
1300
            }
1301
            fd->inc_counter(QuestType::type_card_kill, status->m_card->m_type);
1302
        }
1303
#endif
1304
        _DEBUG_MSG(1, "%s dies\n", status_description(status).c_str());
9,353,668✔
1305
        _DEBUG_ASSERT(status->m_card->m_type != CardType::commander);
9,353,668✔
1306
        fd->killed_units.push_back(status);
9,353,668✔
1307
        ++fd->players[status->m_player]->total_cards_destroyed;
9,353,668✔
1308
        if(!status->m_summoned)++fd->players[status->m_player]->total_nonsummon_cards_destroyed;
9,353,668✔
1309
        if (__builtin_expect((status->m_player == 0) && (fd->players[0]->deck->vip_cards.count(status->m_card->m_id)), false))
12,263,713✔
1310
        {
1311
            fd->players[0]->commander.m_hp = 0;
×
1312
            fd->end = true;
×
1313
        }
1314
    }
1315
}
1316

1317
inline bool is_it_dead(CardStatus& c)
143,302,468✔
1318
{
1319
    if (c.m_hp == 0) // yes it is
143,302,468✔
1320
    {
1321
        _DEBUG_MSG(1, "Dead and removed: %s\n", status_description(&c).c_str());
9,353,668✔
1322
        return true;
9,353,668✔
1323
    }
1324
    return false; // nope still kickin'
1325
}
1326

1327
inline bool is_it_dominion(CardStatus* c)
×
1328
{
1329
    return (c->m_card->m_category == CardCategory::dominion_alpha);
×
1330
}
1331

1332
inline void remove_dead(Storage<CardStatus>& storage)
73,607,476✔
1333
{
1334
    storage.remove(is_it_dead);
73,607,476✔
1335
}
1336

1337
void cooldown_skills(CardStatus * status)
47,480,016✔
1338
{
1339
    for (const auto & ss : status->m_card->m_skills)
183,421,877✔
1340
    {
1341
        if (status->m_skill_cd[ss.id] > 0)
135,941,861✔
1342
        {
1343
            _DEBUG_MSG(2, "%s reduces timer (%u) of skill %s\n",
3,392,892✔
1344
                    status_description(status).c_str(), status->m_skill_cd[ss.id], skill_names[ss.id].c_str());
3,392,892✔
1345
            -- status->m_skill_cd[ss.id];
3,392,892✔
1346
        }
1347
    }
1348
}
47,480,016✔
1349
/**
1350
 * Handle:
1351
 * Absorb, (Activation)Summon, Bravery, (Initial)Valor, Inhibit, Sabotage, Disease, Enhance, (Cooldown/Every X) Reductions
1352
 * 
1353
 * Does not handle these skills for newly summoned units ( i.e. valor, barrier)
1354
 **/
1355
void turn_start_phase_update(Field*fd, CardStatus * status)
57,208,551✔
1356
{
1357
            //apply Absorb + Triggered\{Valor} Enhances
1358
            check_and_perform_early_enhance(fd,status);
114,417,102✔
1359
            if(fd->fixes[Fix::enhance_early])check_and_perform_later_enhance(fd,status);
57,208,551✔
1360
            //refresh absorb
1361
            if(status->has_skill(Skill::absorb))
57,208,551✔
1362
            {
1363
                status->m_absorption = status->skill_base_value(Skill::absorb);
3,950,418✔
1364
            }
1365
            if(__builtin_expect(fd->fixes[Fix::barrier_each_turn],true) && status->has_skill(Skill::barrier)){
57,208,551✔
1366
                _DEBUG_MSG(1,"%s gets barrier protection %u per turn\n",status_description(status).c_str(),status->skill(Skill::barrier));
742,535✔
1367
                status->m_protected += status->skill(Skill::barrier);
742,535✔
1368
            }
1369
            check_and_perform_bravery(fd,status);
57,208,551✔
1370
            if (status->m_delay > 0)
57,208,551✔
1371
            {
1372
                _DEBUG_MSG(1, "%s reduces its timer\n", status_description(status).c_str());
28,130,404✔
1373
                --status->m_delay;
28,130,404✔
1374
                if (status->m_delay == 0)
28,130,404✔
1375
                {
1376
                    check_and_perform_valor(fd, status);
12,311,495✔
1377
                    if (status->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate)
12,311,495✔
1378
                    {
1379
                        check_and_perform_summon(fd, status);
10,092,167✔
1380
                    }
1381
                }
1382
            }
1383
            else
1384
            {
1385
                cooldown_skills(status);
29,078,147✔
1386
            }
1387
}
57,208,551✔
1388

1389
void turn_start_phase(Field* fd)
18,401,869✔
1390
{
1391
    // Active player's commander card:
1392
    cooldown_skills(&fd->tap->commander);
18,401,869✔
1393
    //grab assaults before new ones get summoned
1394
    auto& assaults(fd->tap->assaults);
18,401,869✔
1395
    unsigned end(assaults.size());
18,401,869✔
1396

1397
    //Perform early enhance for commander
1398
    check_and_perform_early_enhance(fd,&fd->tap->commander);
36,803,738✔
1399
    if(fd->fixes[Fix::enhance_early])check_and_perform_later_enhance(fd,&fd->tap->commander);
18,401,869✔
1400

1401
    // Active player's structure cards:
1402
    // update index
1403
    // reduce delay; reduce skill cooldown
1404
    {
18,401,869✔
1405
        auto& structures(fd->tap->structures);
18,401,869✔
1406
        for(unsigned index(0); index < structures.size(); ++index)
40,331,708✔
1407
        {
1408
            CardStatus * status = &structures[index];
21,929,839✔
1409
            status->m_index = index;
21,929,839✔
1410
            turn_start_phase_update(fd,status);
21,929,839✔
1411
        }
1412
    }
1413
    // Active player's assault cards:
1414
    // update index
1415
    // reduce delay; reduce skill cooldown
1416
    {
1417
        for(unsigned index(0); index < end; ++index)
53,680,581✔
1418
        {
1419
            CardStatus * status = &assaults[index];
35,278,712✔
1420
            status->m_index = index;
35,278,712✔
1421
            turn_start_phase_update(fd,status);
35,278,712✔
1422
        }
1423
    }
1424
    // Defending player's structure cards:
1425
    // update index
1426
    {
18,401,869✔
1427
        auto& structures(fd->tip->structures);
18,401,869✔
1428
        for(unsigned index(0), end(structures.size()); index < end; ++index)
41,103,776✔
1429
        {
1430
            CardStatus& status(structures[index]);
22,701,907✔
1431
            status.m_index = index;
22,701,907✔
1432
        }
1433
    }
1434
    // Defending player's assault cards:
1435
    // update index
1436
    {
18,401,869✔
1437
        auto& assaults(fd->tip->assaults);
18,401,869✔
1438
        for(unsigned index(0), end(assaults.size()); index < end; ++index)
61,919,033✔
1439
        {
1440
            CardStatus& status(assaults[index]);
43,517,164✔
1441
            status.m_index = index;
43,517,164✔
1442
        }
1443
    }
1444
}
18,401,869✔
1445

1446
void turn_end_phase(Field* fd)
18,401,869✔
1447
{
1448
    // Inactive player's assault cards:
1449
    {
18,401,869✔
1450
        auto& assaults(fd->tip->assaults);
18,401,869✔
1451
        for(unsigned index(0), end(assaults.size()); index < end; ++ index)
61,980,153✔
1452
        {
1453
            CardStatus& status(assaults[index]);
43,578,284✔
1454
            if (status.m_hp <= 0)
43,578,284✔
1455
            {
1456
                continue;
7,536,339✔
1457
            }
1458
            status.m_enfeebled = 0;
36,041,945✔
1459
            status.m_protected = 0;
36,041,945✔
1460
            std::memset(status.m_primary_skill_offset, 0, sizeof status.m_primary_skill_offset);
36,041,945✔
1461
            std::memset(status.m_evolved_skill_offset, 0, sizeof status.m_evolved_skill_offset);
36,041,945✔
1462
            std::memset(status.m_enhanced_value, 0, sizeof status.m_enhanced_value);
36,041,945✔
1463
            status.m_evaded = 0;  // so far only useful in Inactive turn
36,041,945✔
1464
            status.m_paybacked = 0;  // ditto
36,041,945✔
1465
            status.m_entrapped = 0;
36,041,945✔
1466
        }
1467
    }
1468
    // Inactive player's structure cards:
1469
    {
18,401,869✔
1470
        auto& structures(fd->tip->structures);
18,401,869✔
1471
        for(unsigned index(0), end(structures.size()); index < end; ++ index)
41,107,321✔
1472
        {
1473
            CardStatus& status(structures[index]);
22,705,452✔
1474
            if (status.m_hp <= 0)
22,705,452✔
1475
            {
1476
                continue;
963,556✔
1477
            }
1478
            // reset the structure's (barrier) protect
1479
            status.m_protected = 0;
21,741,896✔
1480
            status.m_evaded = 0;  // so far only useful in Inactive turn
21,741,896✔
1481
        }
1482
    }
1483

1484
    // Active player's assault cards:
1485
    {
18,401,869✔
1486
        auto& assaults(fd->tap->assaults);
18,401,869✔
1487
        for(unsigned index(0), end(assaults.size()); index < end; ++ index)
70,512,946✔
1488
        {
1489
            CardStatus& status(assaults[index]);
52,111,077✔
1490
            if (status.m_hp <= 0)
52,111,077✔
1491
            {
1492
                continue;
832,761✔
1493
            }
1494
            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
51,278,316✔
1495

1496
            if (refresh_value && skill_check<Skill::refresh>(fd, &status, nullptr))
51,278,316✔
1497
            {
1498
                _DEBUG_MSG(1, "%s refreshes %u health\n", status_description(&status).c_str(), refresh_value);
129,238✔
1499
                status.add_hp(refresh_value);
129,238✔
1500
            }
1501
            if (status.m_poisoned > 0)
51,278,316✔
1502
            {
1503
                if(! __builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
111,336✔
1504
                {
1505
                    unsigned poison_dmg = remove_absorption(fd,&status,status.m_poisoned + status.m_enfeebled);
×
1506
                    poison_dmg = safe_minus(poison_dmg, status.protected_value());
×
1507
                    if (poison_dmg > 0)
×
1508
                    {
1509
#ifndef NQUEST
1510
                    if (status.m_player == 1)
1511
                    {
1512
                        fd->inc_counter(QuestType::skill_damage, Skill::poison, 0, poison_dmg);
1513
                    }
1514
#endif
1515
                        _DEBUG_MSG(1, "%s takes poison damage %u\n", status_description(&status).c_str(), poison_dmg);
×
1516
                        remove_hp(fd, &status, poison_dmg);  // simultaneous
×
1517
                    }
1518
                }
1519
                else {
1520
                    unsigned poison_dmg = status.m_poisoned;
111,336✔
1521
                    _DEBUG_MSG(1, "%s takes poison damage %u\n", status_description(&status).c_str(), poison_dmg);
111,336✔
1522
                    remove_hp(fd, &status, poison_dmg);  // simultaneous
111,336✔
1523
                    status.m_poisoned = status.m_poisoned - (poison_dmg+1)/2;
111,336✔
1524
                }
1525
            }
1526
            // end of the opponent's next turn for enemy units
1527
            status.m_temp_attack_buff = 0;
51,278,316✔
1528
            status.m_jammed = false;
51,278,316✔
1529
            status.m_enraged = 0;
51,278,316✔
1530
            status.m_sundered = false;
51,278,316✔
1531
            status.m_inhibited = 0;
51,278,316✔
1532
            status.m_sabotaged = 0;
51,278,316✔
1533
            status.m_tributed = 0;
51,278,316✔
1534
            status.m_overloaded = false;
51,278,316✔
1535
            status.m_step = CardStep::none;
51,278,316✔
1536
        }
1537
    }
1538
    // Active player's structure cards:
1539
    // nothing so far
1540

1541
    prepend_on_death(fd);  // poison
18,401,869✔
1542
    resolve_skill(fd);
18,401,869✔
1543
    remove_dead(fd->tap->assaults);
18,401,869✔
1544
    remove_dead(fd->tap->structures);
18,401,869✔
1545
    remove_dead(fd->tip->assaults);
18,401,869✔
1546
    remove_dead(fd->tip->structures);
18,401,869✔
1547
}
18,401,869✔
1548

1549
//---------------------- $50 attack by assault card implementation -------------
1550
/**
1551
 * @brief Return the damage dealt to the attacker (att) by defender (def) through counter skill
1552
 * The counter is increased by the attacker's enfeebled value, while the attacker's protected value is subtracted from the damage.
1553
 * The absorption is removed from the damage before the protected value is subtracted.
1554
 * 
1555
 * @param fd Field pointer 
1556
 * @param att attacker CardStatus
1557
 * @param def defender CardStatus
1558
 * @return unsigned 
1559
 */
1560
inline unsigned counter_damage(Field* fd, CardStatus* att, CardStatus* def)
2,086,662✔
1561
{
1562
    _DEBUG_ASSERT(att->m_card->m_type == CardType::assault);
2,086,662✔
1563
    _DEBUG_ASSERT(def->skill(Skill::counter) > 0); // counter skill must be present otherwise enfeeble is wrongly applied
2,086,662✔
1564

1565
    return safe_minus(remove_absorption(fd,att,def->skill(Skill::counter) + att->m_enfeebled), att->protected_value());
2,086,662✔
1566
}
1567

1568
inline CardStatus* select_first_enemy_wall(Field* fd)
7,664,908✔
1569
{
1570
    for(unsigned i(0); i < fd->tip->structures.size(); ++i)
14,495,522✔
1571
    {
1572
        CardStatus* c(&fd->tip->structures[i]);
7,622,768✔
1573
        if (c->has_skill(Skill::wall) && is_alive(c)) { return c; }
7,622,768✔
1574
    }
1575
    return nullptr;
1576
}
1577

1578
inline CardStatus* select_first_enemy_assault(Field* fd)
1579
{
1580
    for(unsigned i(0); i < fd->tip->assaults.size(); ++i)
47,519✔
1581
    {
1582
        CardStatus* c(&fd->tip->assaults[i]);
37,731✔
1583
        if (is_alive(c)) { return c; }
37,731✔
1584
    }
1585
    return nullptr;
1586
}
1587

1588

1589
inline bool alive_assault(Storage<CardStatus>& assaults, unsigned index)
21,230,640✔
1590
{
1591
    return(assaults.size() > index && is_alive(&assaults[index]));
15,237,558✔
1592
}
1593

1594
void remove_commander_hp(Field* fd, CardStatus& status, unsigned dmg)
6,871,031✔
1595
{
1596
    _DEBUG_ASSERT(is_alive(&status));
6,871,031✔
1597
    _DEBUG_ASSERT(status.m_card->m_type == CardType::commander);
6,871,031✔
1598
    _DEBUG_MSG(2, "%s takes %u damage\n", status_description(&status).c_str(), dmg);
6,871,031✔
1599
    status.m_hp = safe_minus(status.m_hp, dmg);
6,871,031✔
1600
    if (status.m_hp == 0)
6,871,031✔
1601
    {
1602
        _DEBUG_MSG(1, "%s dies\n", status_description(&status).c_str());
1,336,245✔
1603
        fd->end = true;
1,336,245✔
1604
    }
1605
}
6,871,031✔
1606

1607
//------------------------------------------------------------------------------
1608
// implementation of one attack by an assault card, against either an enemy
1609
// assault card, the first enemy wall, or the enemy commander.
1610
struct PerformAttack
1611
{
1612
    Field* fd;
1613
    CardStatus* att_status;
1614
    CardStatus* def_status;
1615
    unsigned att_dmg;
1616

1617
    PerformAttack(Field* fd_, CardStatus* att_status_, CardStatus* def_status_) :
21,230,640✔
1618
        fd(fd_), att_status(att_status_), def_status(def_status_), att_dmg(0)
21,230,640✔
1619
    {}
1620
    /**
1621
     * @brief Perform the attack
1622
     * 
1623
     * New Evaluation order:
1624
     *  Flying
1625
     *  Subdue
1626
     *  Attack Damage
1627
     *  On-Attacked Skills
1628
     *  Counter
1629
     *  Leech
1630
     *  Drain
1631
     *  Berserk
1632
     *  Mark
1633
     * 
1634
     * Old Evaluation order:
1635
     *  check flying
1636
     *  modify damage
1637
     *  deal damage
1638
     *  assaults only: (poison,inihibit, sabotage)
1639
     *  on-attacked skills
1640
     *  counter, berserk
1641
     *  assaults only: (leech if still alive)
1642
     *  bge
1643
     *  subdue
1644
     * 
1645
     * @tparam def_cardtype 
1646
     * @return unsigned 
1647
     */
1648
    template<enum CardType::CardType def_cardtype>
1649
        unsigned op()
21,230,640✔
1650
        {
1651
            // Bug fix? 2023-04-03 a card with zero attack power can not attack and won't trigger subdue
1652
            // Confirmed subdue behaviour by MK 2023-07-12
1653
            if(att_status->attack_power()== 0) { return 0; }
21,230,640✔
1654

1655

1656
            if(__builtin_expect(def_status->has_skill(Skill::flying),false) && fd->flip()) {
21,230,640✔
1657
                _DEBUG_MSG(1, "%s flies away from %s\n",
103,072✔
1658
                        status_description(def_status).c_str(),
1659
                        status_description(att_status).c_str());
1660
                return 0;
51,536✔
1661
            }
1662

1663
            if ( __builtin_expect(fd->fixes[Fix::subdue_before_attack],true)) {
21,179,104✔
1664
                perform_subdue(fd, att_status, def_status);
21,179,104✔
1665
                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."
21,179,104✔
1666
                { 
1667
                    return 0; 
1668
                }
1669
            }
1670
            
1671
            // APN Bug fix for subdue not reducing attack damage
1672
            // https://github.com/APN-Pucky/tyrant_optimize/issues/79
1673
            unsigned pre_modifier_dmg = att_status->attack_power();
21,174,327✔
1674

1675
            modify_attack_damage<def_cardtype>(pre_modifier_dmg);
21,174,327✔
1676

1677
            if(att_dmg) {
21,174,327✔
1678
                // Deal the attack damage
1679
                attack_damage<def_cardtype>();
17,631,752✔
1680

1681
                // Game Over -> return
1682
                if (__builtin_expect(fd->end, false)) { return att_dmg; }
17,631,752✔
1683
                damage_dependant_pre_oa<def_cardtype>();
9,968,567✔
1684
            }
1685
            // on attacked does also trigger if the attack damage is blocked to zero
1686
            on_attacked<def_cardtype>();
19,838,082✔
1687

1688
            // only if alive attacker
1689
            if (is_alive(att_status) && (__builtin_expect(fd->fixes[Fix::counter_without_damage],true) || att_dmg) )
19,838,082✔
1690
            {
1691
                perform_counter<def_cardtype>(fd, att_status, def_status);    
19,826,497✔
1692
            }
1693
            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"
19,838,082✔
1694
            {
1695
                perform_corrosive(fd, att_status, def_status);
19,100,326✔
1696
            }
1697
            if (is_alive(att_status) && att_dmg)
19,838,082✔
1698
            {
1699
                // Bug fix? 2023-04-03 leech after counter but before drain/berserk
1700
                // Skill: Leech
1701
                do_leech<def_cardtype>();
9,802,162✔
1702
            }
1703

1704
            // Bug fix? 2023-04-03 drain now part of the PerformAttack.op() instead of attack_phase, like hunt.
1705
            // perform swipe/drain
1706
            perform_swipe_drain<def_cardtype>(fd, att_status, def_status, att_dmg);
19,838,082✔
1707

1708
            if (is_alive(att_status) && att_dmg) {
19,838,082✔
1709
                perform_berserk(fd, att_status, def_status);
15,622,083✔
1710
            }
1711
            if (is_alive(att_status) )  {
19,838,082✔
1712
                perform_poison(fd, att_status, def_status);
19,100,326✔
1713
            }
1714

1715
            if (is_alive(att_status) && att_dmg) {
19,838,082✔
1716
                perform_mark(fd, att_status, def_status);
15,622,083✔
1717

1718
                perform_bge_heroism<def_cardtype>(fd, att_status, def_status);
15,622,083✔
1719
                perform_bge_devour<def_cardtype>(fd, att_status, def_status);
15,622,083✔
1720

1721
                if ( __builtin_expect(!fd->fixes[Fix::subdue_before_attack],false)) {
15,622,083✔
1722
                    perform_subdue(fd, att_status, def_status);
×
1723
                }
1724
            }
1725
            // perform hunt
1726
            perform_hunt(fd, att_status, def_status);
19,838,082✔
1727

1728
            return att_dmg;
19,838,082✔
1729
        }
1730

1731
        /**
1732
         * @brief Modify attack damage
1733
         * This setts att_dmg in PerformAttack.
1734
         * 
1735
         * @tparam CardType::CardType 
1736
         * @param pre_modifier_dmg base damage
1737
         */
1738
    template<enum CardType::CardType>
1739
        void modify_attack_damage(unsigned pre_modifier_dmg)
21,174,327✔
1740
        {
1741
            _DEBUG_ASSERT(att_status->m_card->m_type == CardType::assault);
21,174,327✔
1742
            att_dmg = pre_modifier_dmg;
21,174,327✔
1743
            if (att_dmg == 0)
21,174,327✔
1744
            { return; }
×
1745
#ifndef NDEBUG
1746
            std::string desc;
21,174,327✔
1747
#endif
1748
            auto& att_assaults = fd->tap->assaults; // (active) attacker assaults
21,174,327✔
1749
            auto& def_assaults = fd->tip->assaults; // (inactive) defender assaults
21,174,327✔
1750
            unsigned legion_value = 0;
21,174,327✔
1751
            unsigned coalition_value = 0;
21,174,327✔
1752

1753
            // Enhance damage (if additional damage isn't prevented)
1754
            if (! att_status->m_sundered)
21,174,327✔
1755
            {
1756
                // Skill: Mark
1757
                unsigned marked_value = def_status->m_marked;
19,947,258✔
1758
                if(marked_value)
19,947,258✔
1759
                {
1760
#ifndef NDEBUG
1761
                    if (debug_print > 0) { desc += "+" + tuo::to_string(marked_value) + "(mark)"; }
47,219✔
1762
#endif
1763
                    att_dmg += marked_value;
11,972✔
1764
                }
1765
                // Skill: Legion
1766
                unsigned legion_base = att_status->skill(Skill::legion);
19,947,258✔
1767
                if (__builtin_expect(legion_base, false))
19,947,258✔
1768
                {
1769
                    unsigned itr_idx, till_idx;
1770
                    bool bge_megamorphosis = fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis] && !fd->fixes[Fix::legion_under_mega];
2,601,607✔
1771
                    //scan all assaults for Global Legion
1772
                    itr_idx = 0;
2,601,607✔
1773
                    till_idx = att_assaults.size() - 1;
2,601,607✔
1774
                    for (; itr_idx <= till_idx; ++ itr_idx)
16,868,987✔
1775
                    {
1776
                        if(itr_idx == att_status->m_index)continue; //legion doesn't count itself, unlike coalition
14,267,380✔
1777
                        legion_value += is_alive(&att_assaults[itr_idx])
23,309,223✔
1778
                          && (bge_megamorphosis || (att_assaults[itr_idx].m_card->m_faction == att_status->m_card->m_faction));
11,665,773✔
1779
                    }
1780
                    if (legion_value)
2,601,607✔
1781
                    {
1782
                        legion_value *= legion_base;
2,412,546✔
1783
#ifndef NDEBUG
1784
                        if (debug_print > 0) { desc += "+" + tuo::to_string(legion_value) + "(legion)"; }
2,674,926✔
1785
#endif
1786
                        att_dmg += legion_value;
2,412,546✔
1787
                    }
1788
                }
1789

1790
                // Skill: Coalition
1791
                unsigned coalition_base = att_status->skill(Skill::coalition);
19,947,258✔
1792
                if (__builtin_expect(coalition_base, false))
19,947,258✔
1793
                {
1794
                    uint8_t factions_bitmap = 0;
1,798,198✔
1795
                    for (CardStatus * status : att_assaults.m_indirect)
9,264,771✔
1796
                    {
1797
                        if (! is_alive(status)) { continue; }
7,466,573✔
1798
                        factions_bitmap |= (1 << (status->m_card->m_faction));
7,461,482✔
1799
                    }
1800
                    _DEBUG_ASSERT(factions_bitmap);
1,798,198✔
1801
                    unsigned uniq_factions = byte_bits_count(factions_bitmap);
1,798,198✔
1802
                    coalition_value = coalition_base * uniq_factions;
1,798,198✔
1803
#ifndef NDEBUG
1804
                    if (debug_print > 0) { desc += "+" + tuo::to_string(coalition_value) + "(coalition/x" + tuo::to_string(uniq_factions) + ")"; }
2,331,082✔
1805
#endif
1806
                    att_dmg += coalition_value;
1,798,198✔
1807
                }
1808

1809
                // Skill: Rupture
1810
                unsigned rupture_value = att_status->skill(Skill::rupture);
19,947,258✔
1811
                if (rupture_value > 0)
19,947,258✔
1812
                {
1813
#ifndef NDEBUG
1814
                    if (debug_print > 0) { desc += "+" + tuo::to_string(rupture_value) + "(rupture)"; }
×
1815
#endif
1816
                    att_dmg += rupture_value;
×
1817
                }
1818

1819
                // Skill: Venom
1820
                unsigned venom_value = att_status->skill(Skill::venom);
19,947,258✔
1821
                if (venom_value > 0 && def_status->m_poisoned > 0)
19,947,258✔
1822
                {
1823
#ifndef NDEBUG
1824
                    if (debug_print > 0) { desc += "+" + tuo::to_string(venom_value) + "(venom)"; }
86,918✔
1825
#endif
1826
                    att_dmg += venom_value;
35,624✔
1827
                }
1828

1829
                // Passive BGE: Bloodlust
1830
                if (fd->bloodlust_value > 0)
19,947,258✔
1831
                {
1832
#ifndef NDEBUG
1833
                    if (debug_print > 0) { desc += "+" + tuo::to_string(fd->bloodlust_value) + "(bloodlust)"; }
205,668✔
1834
#endif
1835
                    att_dmg += fd->bloodlust_value;
51,417✔
1836
                }
1837

1838
                // State: Enfeebled
1839
                if (def_status->m_enfeebled > 0)
19,947,258✔
1840
                {
1841
#ifndef NDEBUG
1842
                    if (debug_print > 0) { desc += "+" + tuo::to_string(def_status->m_enfeebled) + "(enfeebled)"; }
2,769,593✔
1843
#endif
1844
                    att_dmg += def_status->m_enfeebled;
1,852,220✔
1845
                }
1846
            }
1847
            // prevent damage
1848
#ifndef NDEBUG
1849
            std::string reduced_desc;
21,174,327✔
1850
#endif
1851
            unsigned reduced_dmg(0); // damage reduced by armor, protect and in turn again canceled by pierce, etc.
21,174,327✔
1852
            unsigned armor_value = 0;
21,174,327✔
1853
            // Armor
1854
            if (def_status->m_card->m_type == CardType::assault) {
21,174,327✔
1855
                // Passive BGE: Fortification (adj step -> 1 (1 left, host, 1 right)
1856
                unsigned adj_size = (unsigned)(fd->bg_effects[fd->tapi][PassiveBGE::fortification]);
13,511,142✔
1857
                unsigned host_idx = def_status->m_index;
13,511,142✔
1858
                unsigned from_idx = safe_minus(host_idx, adj_size);
13,511,142✔
1859
                unsigned till_idx = std::min(host_idx + adj_size, safe_minus(def_assaults.size(), 1));
25,241,525✔
1860
                for (; from_idx <= till_idx; ++ from_idx)
27,049,934✔
1861
                {
1862
                    CardStatus* adj_status = &def_assaults[from_idx];
13,538,792✔
1863
                    if (!is_alive(adj_status)) { continue; }
13,538,792✔
1864
                    armor_value = std::max(armor_value, adj_status->skill(Skill::armor));
17,351,754✔
1865
                }
1866
            }
1867
            if (armor_value > 0)
21,174,327✔
1868
            {
1869
#ifndef NDEBUG
1870
                if(debug_print > 0) { reduced_desc += tuo::to_string(armor_value) + "(armor)"; }
4,149,765✔
1871
#endif
1872
                reduced_dmg += armor_value;
1873
            }
1874
            if (def_status->protected_value() > 0)
21,174,327✔
1875
            {
1876
#ifndef NDEBUG
1877
                if(debug_print > 0) { reduced_desc += (reduced_desc.empty() ? "" : "+") + tuo::to_string(def_status->protected_value()) + "(protected)"; }
10,785,336✔
1878
#endif
1879
                reduced_dmg += def_status->protected_value();
8,253,711✔
1880
            }
1881
            unsigned pierce_value = att_status->skill(Skill::pierce);
21,174,327✔
1882
            if (reduced_dmg > 0 && pierce_value > 0)
21,174,327✔
1883
            {
1884
#ifndef NDEBUG
1885
                if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(pierce_value) + "(pierce)"; }
404,452✔
1886
#endif
1887
                reduced_dmg = safe_minus(reduced_dmg, pierce_value);
21,174,327✔
1888
            }
1889
            unsigned rupture_value = att_status->skill(Skill::rupture);
21,174,327✔
1890
            if (reduced_dmg > 0 && rupture_value > 0)
21,174,327✔
1891
            {
1892
#ifndef NDEBUG
1893
                if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(rupture_value) + "(rupture)"; }
×
1894
#endif
1895
                reduced_dmg = safe_minus(reduced_dmg, rupture_value);
21,174,327✔
1896
            }
1897
            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."
21,174,327✔
1898
            {
1899
                unsigned corrosive_value = def_status->m_corroded_rate;
21,174,327✔
1900
                if (reduced_dmg > 0 && corrosive_value > 0)
21,174,327✔
1901
                {
1902
#ifndef NDEBUG
1903
                    if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(corrosive_value) + "(corrosive)"; }
30,124✔
1904
#endif
1905
                    reduced_dmg = safe_minus(reduced_dmg, corrosive_value);
21,174,327✔
1906
                }
1907
            }
1908
            att_dmg = safe_minus(att_dmg, reduced_dmg);
21,174,327✔
1909
#ifndef NDEBUG
1910
            if (debug_print > 0)
21,174,327✔
1911
            {
1912
                if(!reduced_desc.empty()) { desc += "-[" + reduced_desc + "]"; }
4,713,769✔
1913
                if(!desc.empty()) { desc += "=" + tuo::to_string(att_dmg); }
5,295,599✔
1914
                else { assert(att_dmg == pre_modifier_dmg); }
1,659,953✔
1915
                _DEBUG_STRAP(
2,871,835✔
1916
                    "turn", fd->turn,
1917
                    "active_player", fd->tapi,
1918
                    "attacker_"+strap_string(att_status), 1.0,
1919
                    "defender_"+strap_string(def_status), 1.0,
1920
                    "pre_modifier_dmg", pre_modifier_dmg
1921
                );
1922
                _DEBUG_MSG(1, "%s attacks %s for %u%s damage\n",
5,743,670✔
1923
                        status_description(att_status).c_str(),
1924
                        status_description(def_status).c_str(), pre_modifier_dmg, desc.c_str());
1925
            }
1926
#endif
1927
            // Passive BGE: Brigade
1928
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::brigade] && legion_value, false)
21,174,327✔
1929
                    && can_be_healed(att_status))
×
1930
            {
1931
                _DEBUG_MSG(1, "Brigade: %s heals itself for %u\n",
×
1932
                        status_description(att_status).c_str(), legion_value);
1933
                att_status->add_hp(legion_value);
×
1934
            }
1935

1936
            // Passive BGE: Unity
1937
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::unity] && coalition_value, false)
21,174,327✔
1938
                    && can_be_healed(att_status))
21,177,774✔
1939
            {
1940
                _DEBUG_MSG(1, "Unity: %s heals itself for %u\n",
878✔
1941
                        status_description(att_status).c_str(), (coalition_value + 1)/2);
1942
                att_status->add_hp((coalition_value + 1)/2);
439✔
1943
            }
1944
        }
21,174,327✔
1945

1946
    template<enum CardType::CardType>
1947
        void attack_damage()
10,760,721✔
1948
        {
1949

1950
            remove_hp(fd, def_status, att_dmg);
10,760,721✔
1951
            prepend_on_death(fd);
10,760,721✔
1952
            resolve_skill(fd);
10,760,721✔
1953
        }
10,760,721✔
1954

1955
    template<enum CardType::CardType>
1956
        void on_attacked() {
19,838,082✔
1957
            //APN
1958
            // resolve On-Attacked skills
1959
            for (const auto& ss: def_status->m_card->m_skills_on_attacked)
20,036,197✔
1960
            {
1961
                _DEBUG_MSG(1, "On Attacked %s: Preparing (tail) skill %s\n",
396,230✔
1962
                        status_description(def_status).c_str(), skill_description(fd->cards, ss).c_str());
1963
                fd->skill_queue.emplace_back(def_status, ss);
198,115✔
1964
                resolve_skill(fd);
198,115✔
1965
            }
1966
        }
19,838,082✔
1967

1968
    template<enum CardType::CardType>
1969
        void damage_dependant_pre_oa() {}
1970

1971
    template<enum CardType::CardType>
1972
        void do_leech() {}
1973

1974
    template<enum CardType::CardType>
1975
        void perform_swipe_drain(Field* fd, CardStatus* att_status, CardStatus* def_status,unsigned att_dmg) {}
1976
    };
1977

1978

1979
    template<>
1980
void PerformAttack::attack_damage<CardType::commander>()
6,871,031✔
1981
{
1982
    remove_commander_hp(fd, *def_status, att_dmg);
6,871,031✔
1983
}
×
1984

1985
    template<>
1986
void PerformAttack::damage_dependant_pre_oa<CardType::assault>()
9,968,567✔
1987
{
1988
    if (!__builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
9,968,567✔
1989
    {
1990
    unsigned poison_value = std::max(att_status->skill(Skill::poison), att_status->skill(Skill::venom));
×
1991
    
1992
    if (poison_value > def_status->m_poisoned && skill_check<Skill::poison>(fd, att_status, def_status))
×
1993
    {
1994
        // perform_skill_poison
1995
#ifndef NQUEST
1996
        if (att_status->m_player == 0)
1997
        {
1998
            fd->inc_counter(QuestType::skill_use, Skill::poison);
1999
        }
2000
#endif
2001
        _DEBUG_MSG(1, "%s poisons %s by %u\n",
×
2002
                status_description(att_status).c_str(),
2003
                status_description(def_status).c_str(), poison_value);
×
2004
        def_status->m_poisoned = poison_value;
×
2005
    }
2006
    }
2007
    else {            
2008
        unsigned venom_value = att_status->skill(Skill::venom);
9,968,567✔
2009
        if (venom_value > 0 && skill_check<Skill::venom>(fd, att_status, def_status)) {
9,968,567✔
2010
#ifndef NQUEST
2011
              if (att_status->m_player == 0)
2012
              {
2013
                 fd->inc_counter(QuestType::skill_use, Skill::venom);
2014
              }
2015
#endif
2016

2017
              _DEBUG_MSG(1, "%s venoms %s by %u\n",
233,633✔
2018
                  status_description(att_status).c_str(),
2019
                  status_description(def_status).c_str(), venom_value);
233,633✔
2020
              // new venom keeps adding stacks
2021
              def_status->m_poisoned += venom_value;
233,633✔
2022
        }
2023
    }
2024
}
9,968,567✔
2025

2026

2027
    template<>
2028
void PerformAttack::do_leech<CardType::assault>()
9,802,162✔
2029
{
2030
    unsigned leech_value = std::min(att_dmg, att_status->skill(Skill::leech));
9,802,162✔
2031
    if(leech_value > 0 && skill_check<Skill::leech>(fd, att_status, nullptr))
9,802,162✔
2032
    {
2033
#ifndef NQUEST
2034
        if (att_status->m_player == 0)
2035
        {
2036
            fd->inc_counter(QuestType::skill_use, Skill::leech);
2037
        }
2038
#endif
2039
        _DEBUG_MSG(1, "%s leeches %u health\n", status_description(att_status).c_str(), leech_value);
48,261✔
2040
        if (__builtin_expect(fd->fixes[Fix::leech_increase_max_hp],true)) {
48,261✔
2041
            att_status->ext_hp(leech_value);
48,261✔
2042
        }
2043
        else {
2044
            att_status->add_hp(leech_value);
×
2045
        }
2046
    }
2047
}
9,802,162✔
2048

2049
// General attack phase by the currently evaluated assault, taking into accounts exotic stuff such as flurry, etc.
2050
unsigned attack_commander(Field* fd, CardStatus* att_status)
7,664,908✔
2051
{
2052
    CardStatus* def_status{select_first_enemy_wall(fd)}; // defending wall
7,664,908✔
2053
    if (def_status != nullptr)
7,664,908✔
2054
    {
2055
        return PerformAttack{fd, att_status, def_status}.op<CardType::structure>();
792,154✔
2056
    }
2057
    else
2058
    {
2059
        return PerformAttack{fd, att_status, &fd->tip->commander}.op<CardType::commander>();
6,872,754✔
2060
    }
2061
}
2062
// Return true if actually attacks
2063
bool attack_phase(Field* fd)
23,845,309✔
2064
{
2065
    CardStatus* att_status(&fd->tap->assaults[fd->current_ci]); // attacking card
23,845,309✔
2066
    Storage<CardStatus>& def_assaults(fd->tip->assaults);
23,845,309✔
2067

2068
    if (!att_status->attack_power())
23,845,309✔
2069
    {
2070
        _DEBUG_MSG(1, "%s cannot take attack (zeroed)\n", status_description(att_status).c_str());
2,614,669✔
2071
        return false;
2,614,669✔
2072
    }
2073

2074
    unsigned att_dmg = 0;
21,230,640✔
2075
    if (alive_assault(def_assaults, fd->current_ci))
21,230,640✔
2076
    {
2077
        CardStatus* def_status = &def_assaults[fd->current_ci];
13,565,732✔
2078
        att_dmg = PerformAttack{fd, att_status, def_status}.op<CardType::assault>();
13,565,732✔
2079
        
2080
    }
2081
    else
2082
    {
2083
        // might be blocked by walls
2084
        att_dmg = attack_commander(fd, att_status);
7,664,908✔
2085
    }
2086

2087
    // Passive BGE: Bloodlust
2088
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::bloodlust], false)
21,230,640✔
2089
            && !fd->assault_bloodlusted && (att_dmg > 0))
21,230,640✔
2090
    {
2091
        fd->bloodlust_value += fd->bg_effects[fd->tapi][PassiveBGE::bloodlust];
113,307✔
2092
        fd->assault_bloodlusted = true;
113,307✔
2093
    }
2094

2095
    return true;
2096
}
2097

2098
//---------------------- $65 active skills implementation ----------------------
2099
template<
2100
bool C
2101
, typename T1
2102
, typename T2
2103
>
2104
struct if_
2105
{
2106
    typedef T1 type;
2107
};
2108

2109
template<
2110
typename T1
2111
, typename T2
2112
>
2113
struct if_<false,T1,T2>
2114
{
2115
    typedef T2 type;
2116
};
2117

2118
    template<unsigned skill_id>
2119
inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
270✔
2120
{ return skill_check<static_cast<Skill::Skill>(skill_id)>(fd, dst, src); }
360,224,338✔
2121

2122
    template<>
2123
inline bool skill_predicate<Skill::enhance>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
28,436,310✔
2124
{
2125
    if (!is_alive(dst)) return false;
28,436,310✔
2126
    if (!dst->has_skill(s.s)) return false;
56,872,620✔
2127
    if (is_active(dst)) return true;
6,940,156✔
2128
    if (is_defensive_skill(s.s)) return true;
3,648,477✔
2129
    if (is_instant_debuff_skill(s.s)) return true; // Enhance Sabotage, Inhibit, Disease also without dst being active
615,408✔
2130
    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)
615,308✔
2131

2132
    /* Strange Transmission [Gilians]: strange gillian's behavior implementation:
2133
     * The Gillian commander and assaults can enhance any skills on any assaults
2134
     * regardless of jammed/delayed states. But what kind of behavior is in the case
2135
     * when gilians are played among standard assaults, I don't know. :)
2136
     */
2137
    return is_alive_gilian(src);
2138
}
2139

2140
    template<>
2141
inline bool skill_predicate<Skill::evolve>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,904,027✔
2142
{
2143
    if (!is_alive(dst)) return false;
2,904,027✔
2144
    if (!dst->has_skill(s.s)) return false;
5,802,134✔
2145
    if (dst->has_skill(s.s2)) return false;
1,775,046✔
2146
    if (is_active(dst)) return true;
887,523✔
2147
    if (is_defensive_skill(s.s2)) return true;
508,509✔
2148

2149
    /* Strange Transmission [Gilians]: strange gillian's behavior implementation:
2150
     * The Gillian commander and assaults can enhance any skills on any assaults
2151
     * regardless of jammed/delayed states. But what kind of behavior is in the case
2152
     * when gilians are played among standard assaults, I don't know. :)
2153
     */
2154
    return is_alive_gilian(src);
2155
}
2156

2157
    template<>
2158
inline bool skill_predicate<Skill::mimic>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
714,668✔
2159
{
2160
    // skip dead units
2161
    if (!is_alive(dst)) return false;
714,668✔
2162

2163
    //include on activate/attacked/death
2164
    //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})
2165
    //
2166
    std::vector<std::vector<SkillSpec>> all;
669,346✔
2167
    all.emplace_back(dst->m_card->m_skills);
669,346✔
2168
    all.emplace_back(dst->m_card->m_skills_on_attacked);
669,346✔
2169

2170
    for(std::vector<SkillSpec> & a  : all)
1,068,521✔
2171
    {
2172
    // scan all enemy skills until first activation
2173
    for (const SkillSpec & ss: a)
2,053,240✔
2174
    {
2175
        // get skill
2176
        Skill::Skill skill_id = static_cast<Skill::Skill>(ss.id);
1,654,065✔
2177

2178
        // skip non-activation skills and Mimic (Mimic can't be mimicked)
2179
        if (!is_activation_skill(skill_id) || (skill_id == Skill::mimic))
2,133,435✔
2180
        { continue; }
1,174,695✔
2181

2182
        // skip mend for non-assault mimickers
2183
        if ((skill_id == Skill::mend || skill_id == Skill::fortify) && (src->m_card->m_type != CardType::assault))
479,370✔
2184
        { continue; }
2,800✔
2185

2186
        // enemy has at least one activation skill that can be mimicked, so enemy is eligible target for Mimic
2187
        return true;
669,346✔
2188
    }
2189
    }
2190

2191
    // found nothing (enemy has no skills to be mimicked, so enemy isn't eligible target for Mimic)
2192
    return false;
2193
}
669,346✔
2194

2195
    template<>
2196
inline bool skill_predicate<Skill::overload>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
620,027✔
2197
{
2198
    // basic skill check
2199
    if (!skill_check<Skill::overload>(fd, dst, src))
992,628✔
2200
    { return false; }
2201

2202
    // check skills
2203
    bool inhibited_searched = false;
313,803✔
2204
    for (const auto& ss: dst->m_card->m_skills)
868,207✔
2205
    {
2206
        // skip cooldown skills
2207
        if (dst->m_skill_cd[ss.id] > 0)
799,619✔
2208
        { continue; }
8,511✔
2209

2210
        // get evolved skill
2211
        Skill::Skill evolved_skill_id = static_cast<Skill::Skill>(ss.id + dst->m_evolved_skill_offset[ss.id]);
791,108✔
2212

2213
        // unit with an activation hostile skill is always valid target for OL
2214
        if (is_activation_hostile_skill(evolved_skill_id))
1,035,582✔
2215
        { return true; }
2216

2217
        // unit with an activation helpful skill is valid target only when there are inhibited units
2218
        // TODO check mend/fortify valid overload target?!?
2219
        if ((evolved_skill_id != Skill::mend && evolved_skill_id != Skill::fortify)
548,877✔
2220
                && is_activation_helpful_skill(evolved_skill_id)
554,404✔
2221
                && __builtin_expect(!inhibited_searched, true))
714,681✔
2222
        {
2223
            for (const auto & c: fd->players[dst->m_player]->assaults.m_indirect)
884,747✔
2224
            {
2225
                if (is_alive(c) && c->m_inhibited)
724,137✔
2226
                { return true; }
245,215✔
2227
            }
2228
            inhibited_searched = true;
2229
        }
2230
    }
2231
    return false;
2232
}
2233

2234
    template<>
2235
inline bool skill_predicate<Skill::rally>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
24,983,355✔
2236
{
2237
    return skill_check<Skill::rally>(fd, dst, src) // basic skill check
24,983,355✔
2238
        && (__builtin_expect((fd->tapi == dst->m_player), true) // is target on the active side?
24,983,355✔
2239
                ? is_active(dst) && !has_attacked(dst) // normal case
13,823,825✔
2240
                : is_active_next_turn(dst) // diverted case / on-death activation
24,983,355✔
2241
           )
24,983,355✔
2242
        ;
2243
}
2244

2245
    template<>
2246
inline bool skill_predicate<Skill::enrage>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
33,280,870✔
2247
{
2248
    return (__builtin_expect((fd->tapi == dst->m_player), true) // is target on the active side?
33,280,870✔
2249
            ? is_active(dst) && (dst->m_step == CardStep::none) // normal case
15,335,072✔
2250
            : is_active_next_turn(dst) // on-death activation
48,076,694✔
2251
           )
2252
        && (dst->attack_power()) // card can perform direct attack
48,076,694✔
2253
        ;
2254
}
2255

2256
    template<>
2257
inline bool skill_predicate<Skill::rush>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
×
2258
{
2259
    return (!src->m_rush_attempted)
×
2260
        && (dst->m_delay >= ((src->m_card->m_type == CardType::assault) && (dst->m_index < src->m_index) ? 2u : 1u));
×
2261
}
2262

2263
    template<>
2264
inline bool skill_predicate<Skill::weaken>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
37,448,321✔
2265
{
2266
    if(__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::ironwill], false) && dst->has_skill(Skill::armor))return false;
37,448,321✔
2267
    if (!dst->attack_power()) { return false; }
37,444,483✔
2268

2269
    // active player performs Weaken (normal case)
2270
    if (__builtin_expect((fd->tapi == src->m_player), true))
33,068,054✔
2271
    { return is_active_next_turn(dst); }
32,952,229✔
2272

2273
    // APN - On-Attacked/Death don't target the attacking card
2274

2275
    // inactive player performs Weaken (inverted case (on-death activation))
2276
    return will_activate_this_turn(dst);
115,825✔
2277
}
2278

2279
    template<>
2280
inline bool skill_predicate<Skill::sunder>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
18,443,276✔
2281
{
2282
    return skill_predicate<Skill::weaken>(fd, src, dst, s);
80✔
2283
}
2284

2285
    template<unsigned skill_id>
2286
inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2287
{ assert(false); }
2288

2289
    template<>
2290
inline void perform_skill<Skill::enfeeble>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
7,756,648✔
2291
{
2292
    dst->m_enfeebled += s.x;
7,756,648✔
2293
}
×
2294

2295
    template<>
2296
inline void perform_skill<Skill::enhance>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
5,912,815✔
2297
{
2298
    dst->m_enhanced_value[s.s + dst->m_primary_skill_offset[s.s]] += s.x;
5,912,815✔
2299
}
36✔
2300

2301
    template<>
2302
inline void perform_skill<Skill::evolve>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
366,360✔
2303
{
2304
    auto primary_s1 = dst->m_primary_skill_offset[s.s] + s.s;
366,360✔
2305
    auto primary_s2 = dst->m_primary_skill_offset[s.s2] + s.s2;
366,360✔
2306
    dst->m_primary_skill_offset[s.s] = primary_s2 - s.s;
366,360✔
2307
    dst->m_primary_skill_offset[s.s2] = primary_s1 - s.s2;
366,360✔
2308
    dst->m_evolved_skill_offset[primary_s1] = s.s2 - primary_s1;
366,360✔
2309
    dst->m_evolved_skill_offset[primary_s2] = s.s - primary_s2;
366,360✔
2310
}
×
2311

2312
    template<>
2313
inline void perform_skill<Skill::heal>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
8,015,917✔
2314
{
2315
    dst->add_hp(s.x);
8,015,917✔
2316

2317
    // Passive BGE: ZealotsPreservation
2318
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::zealotspreservation], false)
8,015,917✔
2319
            && (src->m_card->m_type == CardType::assault))
8,015,917✔
2320
    {
2321
        unsigned bge_value = (s.x + 1) / 2;
294✔
2322
        _DEBUG_MSG(1, "Zealot's Preservation: %s Protect %u on %s\n",
294✔
2323
                status_description(src).c_str(), bge_value,
2324
                status_description(dst).c_str());
294✔
2325
        dst->m_protected += bge_value;
294✔
2326
    }
2327
}
8,015,917✔
2328

2329
    template<>
2330
inline void perform_skill<Skill::jam>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
1,799,967✔
2331
{
2332
    dst->m_jammed = true;
1,799,967✔
2333
}
×
2334

2335
    template<>
2336
inline void perform_skill<Skill::mend>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
4,014✔
2337
{
2338
    dst->add_hp(s.x);
4,013✔
2339
}
1✔
2340

2341
    template<>
2342
inline void perform_skill<Skill::fortify>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,424,450✔
2343
{
2344
    dst->ext_hp(s.x);
1,764,887✔
2345
}
659,563✔
2346

2347
    template<>
2348
inline void perform_skill<Skill::siege>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,490,376✔
2349
{ 
2350
    //only structures can be sieged
2351
    _DEBUG_ASSERT(dst->m_card->m_type != CardType::assault);
2,490,376✔
2352
    _DEBUG_ASSERT(dst->m_card->m_type != CardType::commander);
2,490,376✔
2353
    unsigned siege_dmg = remove_absorption(fd,dst,s.x);
2,490,376✔
2354
    // structure should not have protect normally..., but let's allow it for barrier support
2355
    siege_dmg = safe_minus(siege_dmg, src->m_overloaded ? 0 : dst->m_protected);
2,490,376✔
2356
    remove_hp(fd, dst, siege_dmg);
2,490,376✔
2357
}
2,490,376✔
2358

2359
    template<>
2360
inline void perform_skill<Skill::mortar>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,990,488✔
2361
{
2362
    if (dst->m_card->m_type == CardType::structure)
2,990,488✔
2363
    {
2364
        perform_skill<Skill::siege>(fd, src, dst, s);
2,308,323✔
2365
    }
2366
    else
2367
    {
2368
        unsigned strike_dmg = remove_absorption(fd,dst,(s.x + 1) / 2 + dst->m_enfeebled);
682,165✔
2369
        strike_dmg = safe_minus(strike_dmg, src->m_overloaded ? 0 : dst->protected_value());
682,165✔
2370
        remove_hp(fd, dst, strike_dmg);
682,165✔
2371
    }
2372
}
2,990,488✔
2373

2374
    template<>
2375
inline void perform_skill<Skill::overload>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
219,477✔
2376
{
2377
    dst->m_overloaded = true;
219,477✔
2378
}
4✔
2379

2380
    template<>
2381
inline void perform_skill<Skill::protect>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
31,805,160✔
2382
{
2383
    dst->m_protected += s.x;
31,805,160✔
2384
}
390✔
2385

2386
    template<>
2387
inline void perform_skill<Skill::rally>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
10,491,091✔
2388
{
2389
    dst->m_temp_attack_buff += s.x;
10,491,091✔
2390
}
226,557✔
2391

2392
    template<>
2393
inline void perform_skill<Skill::enrage>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
9,911,946✔
2394
{
2395
    dst->m_enraged += s.x;
9,911,946✔
2396
    // Passive BGE: Furiosity
2397
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::furiosity], false)
9,911,946✔
2398
            && can_be_healed(dst))
9,911,946✔
2399
    {
2400
        unsigned bge_value = s.x;
6,948✔
2401
        _DEBUG_MSG(1, "Furiosity: %s Heals %s for %u\n",
6,948✔
2402
                status_description(src).c_str(),
2403
                status_description(dst).c_str(), bge_value);
6,948✔
2404
        dst->add_hp(bge_value);
6,948✔
2405
    }
2406
}
9,911,946✔
2407

2408
    template<>
2409
inline void perform_skill<Skill::entrap>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
5,779,724✔
2410
{
2411
    dst->m_entrapped += s.x;
5,779,724✔
2412
}
86,927✔
2413

2414
    template<>
2415
inline void perform_skill<Skill::rush>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
×
2416
{
2417
    dst->m_delay -= std::min(std::max(s.x, 1u), dst->m_delay);
×
2418
    if (dst->m_delay == 0)
×
2419
    {
2420
        check_and_perform_valor(fd, dst);
×
2421
        if(dst->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate)check_and_perform_summon(fd, dst);
×
2422
    }
2423
}
×
2424

2425
    template<>
2426
inline void perform_skill<Skill::strike>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
18,537,797✔
2427
{
2428
    unsigned strike_dmg = remove_absorption(fd,dst,s.x+ dst->m_enfeebled);
18,537,797✔
2429
    strike_dmg = safe_minus(strike_dmg , src->m_overloaded ? 0 : dst->protected_value());
18,537,797✔
2430
    remove_hp(fd, dst, strike_dmg);
18,537,797✔
2431
}
18,537,797✔
2432

2433
    template<>
2434
inline void perform_skill<Skill::weaken>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
9,765,189✔
2435
{
2436
    dst->m_temp_attack_buff -= (unsigned)std::min(s.x, dst->attack_power());
9,765,189✔
2437
}
×
2438

2439
    template<>
2440
inline void perform_skill<Skill::sunder>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,318,901✔
2441
{
2442
    dst->m_sundered = true;
3,318,901✔
2443
    perform_skill<Skill::weaken>(fd, src, dst, s);
3,270,157✔
2444
}
×
2445

2446
    template<>
2447
inline void perform_skill<Skill::mimic>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
308,763✔
2448
{
2449
    // collect all mimickable enemy skills
2450
    std::vector<const SkillSpec *> mimickable_skills;
308,763✔
2451
    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());
308,763✔
2452
    _DEBUG_MSG(2, " * Mimickable skills of %s\n", status_description(dst).c_str());
308,763✔
2453
    //include on activate/attacked
2454
    std::vector<std::vector<SkillSpec>> all;
308,763✔
2455
    all.emplace_back(dst->m_card->m_skills);
308,763✔
2456
    all.emplace_back(dst->m_card->m_skills_on_attacked);
308,763✔
2457
    for(std::vector<SkillSpec> & a : all)
926,289✔
2458
    {
2459
    for (const SkillSpec & ss: a)
1,542,614✔
2460
    {
2461
        // get skill
2462
        Skill::Skill skill_id = static_cast<Skill::Skill>(ss.id);
925,088✔
2463

2464
        // skip non-activation skills and Mimic (Mimic can't be mimicked)
2465
        if (!is_activation_skill(skill_id) || (skill_id == Skill::mimic))
1,330,917✔
2466
        { continue; }
519,259✔
2467

2468
        // skip mend for non-assault mimickers
2469
        if ((skill_id == Skill::mend || skill_id == Skill::fortify) && (src->m_card->m_type != CardType::assault))
405,829✔
2470
        { continue; }
1,200✔
2471

2472
        mimickable_skills.emplace_back(&ss);
404,629✔
2473
        _DEBUG_MSG(2, "  + %s\n", skill_description(fd->cards, ss).c_str());
925,088✔
2474
    }
2475
    }
2476

2477
    // select skill
2478
    unsigned mim_idx = 0;
308,763✔
2479
    switch (mimickable_skills.size())
308,763✔
2480
    {
2481
        case 0: assert(false); break;
×
2482
        case 1: break;
2483
        default: mim_idx = (fd->re() % mimickable_skills.size()); break;
94,018✔
2484
    }
2485
    // prepare & perform selected skill
2486
    const SkillSpec & mim_ss = *mimickable_skills[mim_idx];
308,763✔
2487
    Skill::Skill mim_skill_id = static_cast<Skill::Skill>(mim_ss.id);
308,763✔
2488
    auto skill_value = s.x + src->enhanced(mim_skill_id); //enhanced skill from mimic ?!?
308,763✔
2489
    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,};
308,763✔
2490
    _DEBUG_MSG(1, " * Mimicked skill: %s\n", skill_description(fd->cards, mimicked_ss).c_str());
308,763✔
2491
    skill_table[mim_skill_id](fd, src, mimicked_ss);
308,763✔
2492
}
308,763✔
2493

2494
    template<unsigned skill_id>
2495
inline unsigned select_fast(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
118,438,265✔
2496
{
2497
    if ((s.y == allfactions)
118,438,265✔
2498
            || fd->bg_effects[fd->tapi][PassiveBGE::metamorphosis]
18,941,747✔
2499
            || fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis])
137,224,124✔
2500
    {
2501
        auto pred = [fd, src, s](CardStatus* c) {
381,190,163✔
2502
            return(skill_predicate<skill_id>(fd, src, c, s));
328,784,270✔
2503
        };
2504
        return fd->make_selection_array(cards.begin(), cards.end(), pred);
99,807,513✔
2505
    }
2506
    else
2507
    {
2508
        auto pred = [fd, src, s](CardStatus* c) {
96,978,094✔
2509
            return ((c->m_card->m_faction == s.y || c->m_card->m_faction == progenitor) && skill_predicate<skill_id>(fd, src, c, s));
92,099,222✔
2510
        };
2511
        return fd->make_selection_array(cards.begin(), cards.end(), pred);
18,630,752✔
2512
    }
2513
}
2514

2515
    template<>
2516
inline unsigned select_fast<Skill::mend>(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
109,424✔
2517
{
2518
    fd->selection_array.clear();
109,424✔
2519
    bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
109,424✔
2520
    auto& assaults = fd->players[src->m_player]->assaults;
109,424✔
2521
    unsigned adj_size = 1 + (unsigned)(critical_reach);
109,424✔
2522
    unsigned host_idx = src->m_index;
109,424✔
2523
    unsigned from_idx = safe_minus(host_idx, adj_size);
109,424✔
2524
    unsigned till_idx = std::min(host_idx + adj_size, safe_minus(assaults.size(), 1));
213,437✔
2525
    for (; from_idx <= till_idx; ++ from_idx)
375,986✔
2526
    {
2527
        if (from_idx == host_idx) { continue; }
268,156✔
2528
        CardStatus* adj_status = &assaults[from_idx];
157,138✔
2529
        if (!is_alive(adj_status)) { continue; }
157,138✔
2530
        if (skill_predicate<Skill::mend>(fd, src, adj_status, s))
311,088✔
2531
        {
2532
            fd->selection_array.push_back(adj_status);
4,278✔
2533
        }
2534
    }
2535
    return fd->selection_array.size();
109,424✔
2536
}
2537

2538
    template<>
2539
inline unsigned select_fast<Skill::fortify>(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
1,200,157✔
2540
{
2541
    fd->selection_array.clear();
1,200,157✔
2542
    bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
1,200,157✔
2543
    auto& assaults = fd->players[src->m_player]->assaults;
1,200,157✔
2544
    unsigned adj_size = 1 + (unsigned)(critical_reach);
1,200,157✔
2545
    unsigned host_idx = src->m_index;
1,200,157✔
2546
    unsigned from_idx = safe_minus(host_idx, adj_size);
1,200,157✔
2547
    unsigned till_idx = std::min(host_idx + adj_size, safe_minus(assaults.size(), 1));
2,357,896✔
2548
    for (; from_idx <= till_idx; ++ from_idx)
4,251,402✔
2549
    {
2550
        if (from_idx == host_idx) { continue; }
3,066,241✔
2551
        CardStatus* adj_status = &assaults[from_idx];
1,851,088✔
2552
        if (!is_alive(adj_status)) { continue; }
1,851,088✔
2553
        if (skill_predicate<Skill::fortify>(fd, src, adj_status, s))
1,836,092✔
2554
        {
2555
            fd->selection_array.push_back(adj_status);
1,836,092✔
2556
        }
2557
    }
2558
    return fd->selection_array.size();
1,200,157✔
2559
}
2560
inline std::vector<CardStatus*>& skill_targets_hostile_assault(Field* fd, CardStatus* src)
41,939,658✔
2561
{
2562
    return(fd->players[opponent(src->m_player)]->assaults.m_indirect);
41,939,658✔
2563
}
2564

2565
inline std::vector<CardStatus*>& skill_targets_allied_assault(Field* fd, CardStatus* src)
74,714,722✔
2566
{
2567
    return(fd->players[src->m_player]->assaults.m_indirect);
74,714,722✔
2568
}
2569

2570
inline std::vector<CardStatus*>& skill_targets_hostile_structure(Field* fd, CardStatus* src)
3,093,466✔
2571
{
2572
    return(fd->players[opponent(src->m_player)]->structures.m_indirect);
3,093,466✔
2573
}
2574

2575
inline std::vector<CardStatus*>& skill_targets_allied_structure(Field* fd, CardStatus* src)
2576
{
2577
    return(fd->players[src->m_player]->structures.m_indirect);
2578
}
2579

2580
    template<unsigned skill>
2581
std::vector<CardStatus*>& skill_targets(Field* fd, CardStatus* src)
2582
{
2583
    std::cerr << "skill_targets: Error: no specialization for " << skill_names[skill] << "\n";
2584
    throw;
2585
}
2586

2587
template<> std::vector<CardStatus*>& skill_targets<Skill::enfeeble>(Field* fd, CardStatus* src)
10,614,162✔
2588
{ return(skill_targets_hostile_assault(fd, src)); }
10,614,162✔
2589

2590
template<> std::vector<CardStatus*>& skill_targets<Skill::enhance>(Field* fd, CardStatus* src)
13,345,428✔
2591
{ return(skill_targets_allied_assault(fd, src)); }
13,345,428✔
2592

2593
template<> std::vector<CardStatus*>& skill_targets<Skill::evolve>(Field* fd, CardStatus* src)
1,288,685✔
2594
{ return(skill_targets_allied_assault(fd, src)); }
1,288,685✔
2595

2596
template<> std::vector<CardStatus*>& skill_targets<Skill::heal>(Field* fd, CardStatus* src)
22,363,354✔
2597
{ return(skill_targets_allied_assault(fd, src)); }
22,363,354✔
2598

2599
template<> std::vector<CardStatus*>& skill_targets<Skill::jam>(Field* fd, CardStatus* src)
3,250,952✔
2600
{ return(skill_targets_hostile_assault(fd, src)); }
3,250,952✔
2601

2602
template<> std::vector<CardStatus*>& skill_targets<Skill::mend>(Field* fd, CardStatus* src)
109,424✔
2603
{ return(skill_targets_allied_assault(fd, src)); }
109,424✔
2604

2605
template<> std::vector<CardStatus*>& skill_targets<Skill::fortify>(Field* fd, CardStatus* src)
1,200,157✔
2606
{ return(skill_targets_allied_assault(fd, src)); }
1,200,157✔
2607

2608
template<> std::vector<CardStatus*>& skill_targets<Skill::overload>(Field* fd, CardStatus* src)
148,422✔
2609
{ return(skill_targets_allied_assault(fd, src)); }
148,422✔
2610

2611
template<> std::vector<CardStatus*>& skill_targets<Skill::protect>(Field* fd, CardStatus* src)
10,945,573✔
2612
{ return(skill_targets_allied_assault(fd, src)); }
10,945,573✔
2613

2614
template<> std::vector<CardStatus*>& skill_targets<Skill::rally>(Field* fd, CardStatus* src)
7,097,788✔
2615
{ return(skill_targets_allied_assault(fd, src)); }
7,097,788✔
2616

2617
template<> std::vector<CardStatus*>& skill_targets<Skill::enrage>(Field* fd, CardStatus* src)
13,105,797✔
2618
{ return(skill_targets_allied_assault(fd, src)); }
13,105,797✔
2619

2620
template<> std::vector<CardStatus*>& skill_targets<Skill::entrap>(Field* fd, CardStatus* src)
5,110,094✔
2621
{ return(skill_targets_allied_assault(fd, src)); }
5,110,094✔
2622

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

2626
template<> std::vector<CardStatus*>& skill_targets<Skill::siege>(Field* fd, CardStatus* src)
3,093,466✔
2627
{ return(skill_targets_hostile_structure(fd, src)); }
3,093,466✔
2628

2629
template<> std::vector<CardStatus*>& skill_targets<Skill::strike>(Field* fd, CardStatus* src)
14,639,814✔
2630
{ return(skill_targets_hostile_assault(fd, src)); }
653,409✔
2631

2632
template<> std::vector<CardStatus*>& skill_targets<Skill::sunder>(Field* fd, CardStatus* src)
5,641,888✔
2633
{ return(skill_targets_hostile_assault(fd, src)); }
5,641,888✔
2634

2635
template<> std::vector<CardStatus*>& skill_targets<Skill::weaken>(Field* fd, CardStatus* src)
7,323,273✔
2636
{ return(skill_targets_hostile_assault(fd, src)); }
7,323,273✔
2637

2638
template<> std::vector<CardStatus*>& skill_targets<Skill::mimic>(Field* fd, CardStatus* src)
469,569✔
2639
{ return(skill_targets_hostile_assault(fd, src)); }
469,569✔
2640

2641
    template<Skill::Skill skill_id>
2642
inline bool check_and_perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s, bool is_evadable
128,803,817✔
2643
#ifndef NQUEST
2644
        , bool & has_counted_quest
2645
#endif
2646
        )
2647
{
2648
    if (__builtin_expect(skill_check<skill_id>(fd, dst, src), true))
128,803,817✔
2649
    {
2650
#ifndef NQUEST
2651
        if (src->m_player == 0 && ! has_counted_quest)
2652
        {
2653
            fd->inc_counter(QuestType::skill_use, skill_id, dst->m_card->m_id);
2654
            has_counted_quest = true;
2655
        }
2656
#endif
2657
        if (is_evadable && (dst->m_evaded < dst->skill(Skill::evade)))
128,803,817✔
2658
        {
2659
            ++ dst->m_evaded;
15,896,663✔
2660
            _DEBUG_MSG(1, "%s %s on %s but it evades\n",
17,863,345✔
2661
                    status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2662
                    status_description(dst).c_str());
2663
            return(false);
15,896,663✔
2664
        }
2665
        _DEBUG_STRAP(
112,907,154✔
2666
            "turn", fd->turn,
2667
            "active_player", fd->tapi,
2668
            "skill_source_"+strap_string(src),
2669
            "skill_target_"+strap_string(dst),
2670
            "skill_"+skill_names[s.id],
2671
            "skill_x", s.x
2672
        );
2673
        _DEBUG_MSG(1, "%s %s on %s\n",
138,784,272✔
2674
                status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2675
                status_description(dst).c_str());
2676
        perform_skill<skill_id>(fd, src, dst, s);
112,907,154✔
2677
        if (s.c > 0)
112,907,154✔
2678
        {
2679
            src->m_skill_cd[skill_id] = s.c;
1,424,176✔
2680
        }
2681
        // Skill: Tribute
2682
        if (skill_check<Skill::tribute>(fd, dst, src)
132,448,859✔
2683
                // only activation helpful skills can be tributed (* except Evolve, Enhance, and Rush)
2684
                && is_activation_helpful_skill(s.id) && (s.id != Skill::evolve) && (s.id != Skill::enhance) && (s.id != Skill::rush)
19,541,705✔
2685
                && (dst->m_tributed < dst->skill(Skill::tribute))
7,267,356✔
2686
                && skill_check<skill_id>(fd, src, src))
102,253,257✔
2687
        {
2688
            ++ dst->m_tributed;
970,508✔
2689
            _DEBUG_MSG(1, "%s tributes %s back to %s\n",
970,508✔
2690
                    status_description(dst).c_str(), skill_short_description(fd->cards, s).c_str(),
2691
                    status_description(src).c_str());
2692
            perform_skill<skill_id>(fd, src, src, s);
970,508✔
2693
        }
2694
        return(true);
112,907,154✔
2695
    }
2696
    _DEBUG_MSG(1, "(CANCELLED) %s %s on %s\n",
×
2697
            status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2698
            status_description(dst).c_str());
2699
    return(false);
2700
}
2701
template<enum CardType::CardType def_cardtype>
2702
void perform_bge_devour(Field* fd, CardStatus* att_status, CardStatus* def_status)
15,622,083✔
2703
{
2704
// Passive BGE: Devour
2705
                unsigned leech_value;
2706
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::devour], false)
9,802,162✔
2707
                        && ((leech_value = att_status->skill(Skill::leech) + att_status->skill(Skill::refresh)) > 0)
47,219✔
2708
                        && (def_cardtype == CardType::assault))
9,802,162✔
2709
                {
2710
                    unsigned bge_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::devour] > 0)
1,936✔
2711
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::devour]
968✔
2712
                        : 4;
2713
                    unsigned bge_value = (leech_value - 1) / bge_denominator + 1;
968✔
2714
                    if (! att_status->m_sundered)
968✔
2715
                    {
2716
                        _DEBUG_MSG(1, "Devour: %s gains %u attack\n",
2,841✔
2717
                                status_description(att_status).c_str(), bge_value);
2718
                        att_status->m_perm_attack_buff += bge_value;
947✔
2719
                    }
2720
                    _DEBUG_MSG(1, "Devour: %s extends max hp / heals itself for %u\n",
2,904✔
2721
                            status_description(att_status).c_str(), bge_value);
2722
                    att_status->ext_hp(bge_value);
968✔
2723
                }
2724
}
9,802,162✔
2725
template<enum CardType::CardType def_cardtype>
2726
void perform_bge_heroism(Field* fd, CardStatus* att_status, CardStatus* def_status)
15,622,083✔
2727
{
2728
// Passive BGE: Heroism
2729
                unsigned valor_value;
2730
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::heroism], false)
15,622,083✔
2731
                        && ((valor_value = att_status->skill(Skill::valor) + att_status->skill(Skill::bravery)) > 0)
23,282✔
2732
                        && !att_status->m_sundered
2,547✔
2733
                        && (def_cardtype == CardType::assault) && (def_status->m_hp <= 0))
9,804,698✔
2734
                {
2735
                    _DEBUG_MSG(1, "Heroism: %s gain %u attack\n",
2,112✔
2736
                            status_description(att_status).c_str(), valor_value);
2737
                    att_status->m_perm_attack_buff += valor_value;
704✔
2738
                }
2739
                }
9,802,162✔
2740
/**
2741
 * @brief Perform Mark skill, increases Mark-counter.
2742
 * 
2743
 * @param fd     Field
2744
 * @param att_status Attacker
2745
 * @param def_status Defender
2746
 */
2747
void perform_mark(Field* fd, CardStatus* att_status, CardStatus* def_status)
15,622,083✔
2748
{
2749
    // Bug fix? 2023-04-03 mark should come after berserk
2750
    // Increase Mark-counter
2751
    unsigned mark_base = att_status->skill(Skill::mark);
15,622,083✔
2752
    if(mark_base && skill_check<Skill::mark>(fd,att_status,def_status)) {
15,622,083✔
2753
        _DEBUG_MSG(1, "%s marks %s for %u\n",
35,419✔
2754
                status_description(att_status).c_str(), status_description(def_status).c_str(), mark_base);
35,419✔
2755
        def_status->m_marked += mark_base;
35,419✔
2756
    }
2757
}
15,622,083✔
2758
/**
2759
 * @brief Perform Berserk skill and Passive BGE: EnduringRage
2760
 * 
2761
 * Increases attack by berserk value if not sundered.
2762
 * 
2763
 * @param fd     Field
2764
 * @param att_status Attacker
2765
 * @param def_status Defender
2766
 */
2767
void perform_berserk(Field* fd, CardStatus* att_status, CardStatus* def_status)
15,622,083✔
2768
{
2769
            // Skill: Berserk
2770
            unsigned berserk_value = att_status->skill(Skill::berserk);
15,622,083✔
2771
            if ( !att_status->m_sundered && (berserk_value > 0))
15,622,083✔
2772
            {
2773
                // perform_skill_berserk
2774
                att_status->m_perm_attack_buff += berserk_value;
6,912,380✔
2775
#ifndef NQUEST
2776
                if (att_status->m_player == 0)
2777
                {
2778
                    fd->inc_counter(QuestType::skill_use, Skill::berserk);
2779
                }
2780
#endif
2781

2782
                // Passive BGE: EnduringRage
2783
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::enduringrage], false))
6,912,380✔
2784
                {
2785
                    unsigned bge_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::enduringrage] > 0)
21,769✔
2786
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::enduringrage]
21,769✔
2787
                        : 2;
21,769✔
2788
                    unsigned bge_value = (berserk_value - 1) / bge_denominator + 1;
21,769✔
2789
                    _DEBUG_MSG(1, "EnduringRage: %s heals and protects itself for %u\n",
21,769✔
2790
                            status_description(att_status).c_str(), bge_value);
21,769✔
2791
                    att_status->add_hp(bge_value);
21,769✔
2792
                    att_status->m_protected += bge_value;
21,769✔
2793
                }
2794
            }
2795

2796

2797
}
15,622,083✔
2798
/**
2799
 * @brief Poisoning of a attacking card
2800
 * 
2801
 * @param fd  Field
2802
 * @param att_status Attacking card
2803
 * @param def_status Defending card
2804
 */
2805
void perform_poison(Field* fd, CardStatus* att_status, CardStatus* def_status)
19,100,326✔
2806
{
2807
    if(__builtin_expect(fd->fixes[Fix::poison_after_attacked],true))
19,100,326✔
2808
    {
2809
        if (is_alive(att_status) && def_status->has_skill(Skill::poison))
19,100,326✔
2810
        {
2811
            unsigned poison_value = def_status->skill(Skill::poison);
10,145✔
2812
            _DEBUG_MSG(1, "%s gets poisoned by %u from %s\n",
10,145✔
2813
                            status_description(att_status).c_str(), poison_value,
2814
                            status_description(def_status).c_str());
10,145✔
2815
            att_status->m_poisoned += poison_value; 
10,145✔
2816
        }
2817
    }
2818
}
19,100,326✔
2819

2820
void perform_corrosive(Field* fd, CardStatus* att_status, CardStatus* def_status)
19,100,326✔
2821
{
2822
    // Skill: Corrosive
2823
    unsigned corrosive_value = def_status->skill(Skill::corrosive);
19,100,326✔
2824
    if (corrosive_value > att_status->m_corroded_rate)
19,100,326✔
2825
    {
2826
        // perform_skill_corrosive
2827
        _DEBUG_MSG(1, "%s corrodes %s by %u\n",
42,166✔
2828
                status_description(def_status).c_str(),
2829
                status_description(att_status).c_str(), corrosive_value);
42,166✔
2830
        att_status->m_corroded_rate = corrosive_value;
42,166✔
2831
    }
2832
}
19,100,326✔
2833

2834
template<enum CardType::CardType def_cardtype>
2835
void perform_counter(Field* fd, CardStatus* att_status, CardStatus* def_status)
19,826,497✔
2836
{
2837
            // Enemy Skill: Counter
2838
            if (def_status->has_skill(Skill::counter))
19,826,497✔
2839
            {
2840
                // perform_skill_counter
2841
                unsigned counter_dmg(counter_damage(fd, att_status, def_status));
2,086,662✔
2842
#ifndef NQUEST
2843
                if (def_status->m_player == 0)
2844
                {
2845
                    fd->inc_counter(QuestType::skill_use, Skill::counter);
2846
                    fd->inc_counter(QuestType::skill_damage, Skill::counter, 0, counter_dmg);
2847
                }
2848
#endif
2849
                _DEBUG_STRAP(
2,086,662✔
2850
                    "turn", fd->turn,
2851
                    "active_player", fd->tapi,
2852
                    "counter_attacker_"+strap_string(att_status), 1.0,
2853
                    "counter_defender_"+strap_string(def_status), 1.0,
2854
                    "counter_damage", counter_dmg
2855
                );
2856
                _DEBUG_MSG(1, "%s takes %u counter damage from %s\n",
3,144,140✔
2857
                        status_description(att_status).c_str(), counter_dmg,
2858
                        status_description(def_status).c_str());
2859
                remove_hp(fd, att_status, counter_dmg);
2,086,662✔
2860
                prepend_on_death(fd);
2,086,662✔
2861
                resolve_skill(fd);
2,086,662✔
2862

2863
                // Passive BGE: Counterflux
2864
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::counterflux], false)
2,086,662✔
2865
                        && (def_cardtype == CardType::assault) && is_alive(def_status))
1,236,628✔
2866
                {
2867
                    unsigned flux_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::counterflux] > 0)
8,216✔
2868
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::counterflux]
4,108✔
2869
                        : 4;
2870
                    unsigned flux_value = (def_status->skill(Skill::counter) - 1) / flux_denominator + 1;
4,108✔
2871
                    _DEBUG_MSG(1, "Counterflux: %s heals itself and berserks for %u\n",
12,324✔
2872
                            status_description(def_status).c_str(), flux_value);
2873
                    def_status->add_hp(flux_value);
4,108✔
2874
                    if (! def_status->m_sundered)
4,108✔
2875
                    { def_status->m_perm_attack_buff += flux_value; }
3,712✔
2876
                }
2877
            }
2878
}
19,826,497✔
2879
bool check_and_perform_enhance(Field* fd, CardStatus* src, bool early)
151,220,840✔
2880
{
2881
      if(!is_active(src))return false; // active
151,220,840✔
2882
      if(!src->has_skill(Skill::enhance))return false; // enhance Skill
93,568,624✔
2883
      for(auto ss : src->m_card->m_skills)
106,020,328✔
2884
      {
2885
          if(ss.id != Skill::enhance)continue;
79,515,096✔
2886
          if(early ^ (ss.s == Skill::allegiance || ss.s == Skill::absorb ||ss.s == Skill::stasis || ss.s == Skill::bravery))continue; //only specified skills are 'early'
26,966,208✔
2887
          skill_table[ss.id](fd,src,ss);
13,252,616✔
2888
      }
2889
      return true;
26,505,232✔
2890
}
2891
bool check_and_perform_early_enhance(Field* fd, CardStatus* src)
75,610,420✔
2892
{
2893
      return check_and_perform_enhance(fd,src,true);
75,610,420✔
2894
}
2895
bool check_and_perform_later_enhance(Field* fd, CardStatus* src)
75,610,420✔
2896
{
2897
      return check_and_perform_enhance(fd,src,false);
75,610,420✔
2898
}
2899
/**
2900
 * @brief Perform drain skill
2901
 * 
2902
 * @param fd Field
2903
 * @param att_status Attacker status
2904
 * @param def_status Defender status
2905
 * @param att_dmg damage dealt by attacker
2906
 */
2907
template<>
2908
void PerformAttack::perform_swipe_drain<CardType::assault>(Field* fd, CardStatus* att_status, CardStatus* def_status,unsigned att_dmg) {
13,511,142✔
2909
        unsigned swipe_value = att_status->skill(Skill::swipe);
13,511,142✔
2910
        unsigned drain_value = att_status->skill(Skill::drain);
13,511,142✔
2911
        if (swipe_value || drain_value)
13,511,142✔
2912
        {
2913
            Storage<CardStatus>& def_assaults(fd->tip->assaults);
1,256,790✔
2914
            bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
1,256,790✔
2915
            auto drain_total_dmg = att_dmg;
1,256,790✔
2916
            unsigned adj_size = 1 + (unsigned)(critical_reach);
1,256,790✔
2917
            unsigned host_idx = def_status->m_index;
1,256,790✔
2918
            unsigned from_idx = safe_minus(host_idx, adj_size);
1,256,790✔
2919
            unsigned till_idx = std::min(host_idx + adj_size, safe_minus(def_assaults.size(), 1));
2,126,611✔
2920
            for (; from_idx <= till_idx; ++ from_idx)
3,503,888✔
2921
            {
2922
                if (from_idx == host_idx) { continue; }
2,247,098✔
2923
                CardStatus* adj_status = &def_assaults[from_idx];
990,308✔
2924
                if (!is_alive(adj_status)) { continue; }
990,308✔
2925
                _DEBUG_ASSERT(adj_status->m_card->m_type == CardType::assault); //only assaults
802,488✔
2926
                //unsigned swipe_dmg = safe_minus(
2927
                //    swipe_value + drain_value + def_status->m_enfeebled,
2928
                //    def_status->protected_value());
2929
                unsigned remaining_dmg = remove_absorption(fd,adj_status,swipe_value + drain_value + adj_status->m_enfeebled);
802,488✔
2930
                remaining_dmg = safe_minus(remaining_dmg,adj_status->protected_value());
802,488✔
2931
                _DEBUG_MSG(1, "%s swipes %s for %u damage\n",
802,488✔
2932
                        status_description(att_status).c_str(),
2933
                        status_description(adj_status).c_str(), remaining_dmg);
802,488✔
2934

2935
                remove_hp(fd, adj_status, remaining_dmg);
802,488✔
2936
                drain_total_dmg += remaining_dmg;
802,488✔
2937
            }
2938
            if (drain_value && skill_check<Skill::drain>(fd, att_status, nullptr))
1,256,790✔
2939
            {
2940
                _DEBUG_MSG(1, "%s drains %u hp\n",
55,920✔
2941
                        status_description(att_status).c_str(), drain_total_dmg);
55,920✔
2942
                att_status->add_hp(drain_total_dmg);
55,920✔
2943
            }
2944
            prepend_on_death(fd);
1,256,790✔
2945
            resolve_skill(fd);
1,256,790✔
2946
        }
2947
}
13,511,142✔
2948
/**
2949
 * @brief Perform Hunt skill
2950
 * 
2951
 * @param fd Field 
2952
 * @param att_status Attacker status
2953
 * @param def_status Defender status
2954
 */
2955
void perform_hunt(Field* fd, CardStatus* att_status, CardStatus* def_status) {
19,838,082✔
2956
    unsigned hunt_value = att_status->skill(Skill::hunt);
19,838,082✔
2957
        if(hunt_value)
19,838,082✔
2958
        {
2959
            CardStatus* hunted_status{select_first_enemy_assault(fd)};
32,333✔
2960
            if (hunted_status != nullptr)
32,333✔
2961
            {
2962
                unsigned remaining_dmg = remove_absorption(fd,hunted_status,hunt_value + hunted_status->m_enfeebled);
22,545✔
2963
                remaining_dmg = safe_minus(remaining_dmg,hunted_status->protected_value());
22,545✔
2964
                _DEBUG_MSG(1, "%s hunts %s for %u damage\n",
22,545✔
2965
                        status_description(att_status).c_str(),
2966
                        status_description(hunted_status).c_str(), remaining_dmg);
22,545✔
2967

2968
                remove_hp(fd, hunted_status, remaining_dmg);
22,545✔
2969

2970
                prepend_on_death(fd);
22,545✔
2971
                resolve_skill(fd);
22,545✔
2972
            }
2973
        }
2974
}
19,838,082✔
2975
/**
2976
 * @brief Perform Subdue skill
2977
 * 
2978
 * @param fd Field
2979
 * @param att_status Attacker status
2980
 * @param def_status Defender status
2981
 */
2982
void perform_subdue(Field* fd, CardStatus* att_status, CardStatus* def_status)
21,179,104✔
2983
{
2984
                // Skill: Subdue
2985
                unsigned subdue_value = def_status->skill(Skill::subdue);
21,179,104✔
2986
                if (__builtin_expect(subdue_value, false))
21,179,104✔
2987
                {
2988
                    _DEBUG_MSG(1, "%s subdues %s by %u\n",
768,262✔
2989
                            status_description(def_status).c_str(),
2990
                            status_description(att_status).c_str(), subdue_value);
768,262✔
2991
                    att_status->m_subdued += subdue_value;
768,262✔
2992
                    //fix negative attack
2993
                    if(att_status->calc_attack_power()<0)
768,262✔
2994
                    {
2995
                        att_status->m_temp_attack_buff -= att_status->calc_attack_power();
3,284✔
2996
                    }
2997
                    if (att_status->m_hp > att_status->max_hp())
858,700✔
2998
                    {
2999
                        _DEBUG_MSG(1, "%s loses %u HP due to subdue (max hp: %u)\n",
115,533✔
3000
                                status_description(att_status).c_str(),
3001
                                (att_status->m_hp - att_status->max_hp()),
3002
                                att_status->max_hp());
115,533✔
3003
                        att_status->m_hp = att_status->max_hp();
203,490✔
3004
                    }
3005
                }
3006
}
21,179,104✔
3007
bool check_and_perform_valor(Field* fd, CardStatus* src)
13,633,436✔
3008
{
3009
    unsigned valor_value = src->skill(Skill::valor);
13,633,436✔
3010
    if (valor_value && !src->m_sundered && skill_check<Skill::valor>(fd, src, nullptr))
27,266,872✔
3011
    {
3012
        _DEBUG_ASSERT(src->m_card->m_type == CardType::assault); //only assaults
45,690✔
3013
        unsigned opponent_player = opponent(src->m_player);
45,690✔
3014
        const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ?
45,690✔
3015
            &fd->players[opponent_player]->assaults[src->m_index] :
20,124✔
3016
            nullptr;
45,690✔
3017
        if (dst == nullptr || dst->m_hp <= 0)
20,124✔
3018
        {
3019
            _DEBUG_MSG(1, "%s loses Valor (no blocker)\n", status_description(src).c_str());
25,566✔
3020
            return false;
25,566✔
3021
        }
3022
        else if (dst->attack_power() <= src->attack_power())
20,124✔
3023
        {
3024
            _DEBUG_MSG(1, "%s loses Valor (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str());
2,305✔
3025
            return false;
2,305✔
3026
        }
3027
#ifndef NQUEST
3028
        if (src->m_player == 0)
3029
        {
3030
            fd->inc_counter(QuestType::skill_use, Skill::valor);
3031
        }
3032
#endif
3033
        _DEBUG_MSG(1, "%s activates Valor %u\n", status_description(src).c_str(), valor_value);
17,819✔
3034
        src->m_perm_attack_buff += valor_value;
17,819✔
3035
        return true;
17,819✔
3036
    }
3037
    return false;
3038
}
3039

3040
bool check_and_perform_bravery(Field* fd, CardStatus* src)
58,530,492✔
3041
{
3042
    unsigned bravery_value = src->skill(Skill::bravery);
58,530,492✔
3043
    if (bravery_value && !src->m_sundered && skill_check<Skill::bravery>(fd, src, nullptr))
117,060,984✔
3044
    {
3045
        _DEBUG_ASSERT(src->m_card->m_type == CardType::assault); //only assaults
1,850,631✔
3046
        unsigned opponent_player = opponent(src->m_player);
1,850,631✔
3047
        const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ?
1,850,631✔
3048
            &fd->players[opponent_player]->assaults[src->m_index] :
1,017,254✔
3049
            nullptr;
1,850,631✔
3050
        if (dst == nullptr || dst->m_hp <= 0)
1,017,254✔
3051
        {
3052
            _DEBUG_MSG(1, "%s loses Bravery (no blocker)\n", status_description(src).c_str());
833,377✔
3053
            return false;
833,377✔
3054
        }
3055
        else if (dst->attack_power() <= src->attack_power())
1,017,254✔
3056
        {
3057
            _DEBUG_MSG(1, "%s loses Bravery (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str());
753,761✔
3058
            return false;
753,761✔
3059
        }
3060
#ifndef NQUEST
3061
        if (src->m_player == 0)
3062
        {
3063
            fd->inc_counter(QuestType::skill_use, Skill::bravery);
3064
        }
3065
#endif
3066
        _DEBUG_MSG(1, "%s activates Bravery %u\n", status_description(src).c_str(), bravery_value);
263,493✔
3067
        src->m_perm_attack_buff += bravery_value;
263,493✔
3068

3069
        //BGE: superheroism
3070
        if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::superheroism], false))
263,493✔
3071
        {
3072
            unsigned bge_value = bravery_value * fd->bg_effects[fd->tapi][PassiveBGE::superheroism];
971✔
3073
            const SkillSpec ss_heal{Skill::heal, bge_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, true, 0,};
971✔
3074
            _DEBUG_MSG(1, "%s activates SuperHeroism: %s\n", status_description(src).c_str(),
971✔
3075
                    skill_description(fd->cards, ss_heal).c_str());
971✔
3076
            //fd->skill_queue.emplace(fd->skill_queue.begin()+0, src, ss_heal);
3077
            skill_table[Skill::heal](fd,src,ss_heal); //Probably better to perform the skill direct instead of queuing it
971✔
3078
        }
3079

3080
        return true;
263,493✔
3081
    }
3082
    return false;
3083
}
3084

3085
bool check_and_perform_inhibit(Field* fd, CardStatus* att_status,CardStatus* def_status)
31,614,298✔
3086
{
3087
      unsigned inhibit_value = att_status->skill(Skill::inhibit);
31,614,298✔
3088
      if (inhibit_value > def_status->m_inhibited && skill_check<Skill::inhibit>(fd, att_status, def_status))
34,301,439✔
3089
      {
3090
        _DEBUG_MSG(1, "%s inhibits %s by %u\n",
2,687,140✔
3091
                status_description(att_status).c_str(),
3092
                status_description(def_status).c_str(), inhibit_value);
2,687,140✔
3093
        def_status->m_inhibited = inhibit_value;
2,687,140✔
3094
        return true;
2,687,140✔
3095
      }
3096
      return false;
3097
}
3098
bool check_and_perform_sabotage(Field* fd, CardStatus* att_status, CardStatus* def_status)
31,614,298✔
3099
{
3100
    unsigned sabotage_value = att_status->skill(Skill::sabotage);
31,614,298✔
3101
    if (sabotage_value > def_status->m_sabotaged && skill_check<Skill::sabotage>(fd, att_status, def_status))
32,036,124✔
3102
    {
3103
        _DEBUG_MSG(1, "%s sabotages %s by %u\n",
421,826✔
3104
                status_description(att_status).c_str(),
3105
                status_description(def_status).c_str(), sabotage_value);
421,826✔
3106
        def_status->m_sabotaged = sabotage_value;
421,826✔
3107
        return true;
421,826✔
3108
    }
3109
    return false;
3110
}
3111
bool check_and_perform_disease(Field* fd, CardStatus* att_status,CardStatus* def_status)
31,614,298✔
3112
{
3113
    unsigned disease_base = att_status->skill(Skill::disease);
31,614,298✔
3114
    if(disease_base && skill_check<Skill::disease>(fd, att_status, def_status)) {
31,818,799✔
3115
        _DEBUG_MSG(1, "%s diseases %s for %u\n",
204,501✔
3116
        status_description(att_status).c_str(), status_description(def_status).c_str(), disease_base);
204,501✔
3117
        def_status->m_diseased += disease_base;
204,501✔
3118
        return true;
204,501✔
3119
    }
3120
    return false;
3121
}
3122

3123
CardStatus* check_and_perform_summon(Field* fd, CardStatus* src)
13,061,733✔
3124
{
3125
    unsigned summon_card_id = src->m_card->m_skill_value[Skill::summon];
13,061,733✔
3126
    if (summon_card_id)
13,061,733✔
3127
    {
3128
        const Card* summoned_card(fd->cards.by_id(summon_card_id));
3,352,526✔
3129
        _DEBUG_MSG(1, "%s summons %s\n", status_description(src).c_str(), summoned_card->m_name.c_str());
3,352,526✔
3130
        CardStatus* summoned_status = nullptr;
3,352,526✔
3131
        switch (summoned_card->m_type)
3,352,526✔
3132
        {
3133
            case CardType::assault:
1,157,836✔
3134
                summoned_status = PlayCard(summoned_card, fd, src->m_player, src).op<CardType::assault>(true);
1,157,836✔
3135
                return summoned_status;
1,157,836✔
3136
            case CardType::structure:
2,194,690✔
3137
                summoned_status = PlayCard(summoned_card, fd, src->m_player, src).op<CardType::structure>(true);
2,194,690✔
3138
                return summoned_status;
2,194,690✔
3139
            default:
×
3140
                _DEBUG_MSG(0, "Unknown card type: #%u %s: %u\n",
×
3141
                        summoned_card->m_id, card_description(fd->cards, summoned_card).c_str(),
3142
                        summoned_card->m_type);
×
3143
                _DEBUG_ASSERT(false);
×
3144
                __builtin_unreachable();
3145
        }
3146
    }
3147
    return nullptr;
3148
}
3149

3150

3151
    template<Skill::Skill skill_id>
3152
size_t select_targets(Field* fd, CardStatus* tsrc, const SkillSpec& s)
119,094,437✔
3153
{
3154
    size_t n_candidates;
3155
    CardStatus* src;
3156
    if(fd->fixes[Fix::revenge_on_death] && s.s2 == Skill::revenge)
119,094,437✔
3157
    {
3158
            _DEBUG_MSG(2,"FIX ON DEATH REVENGE SELECTION")
33,938✔
3159
            src = &fd->players[(tsrc->m_player+1)%2]->commander; // selection like enemy commander
33,938✔
3160
    }
33,938✔
3161
    else
3162
    {
3163
            src = tsrc;
3164
    }
3165
    switch (skill_id)
3166
    {
3167
        case Skill::mortar:
2,867,976✔
3168
            n_candidates = select_fast<Skill::siege>(fd, src, skill_targets<Skill::siege>(fd, src), s);
2,867,976✔
3169
            if (n_candidates == 0)
2,867,976✔
3170
            {
3171
                n_candidates = select_fast<Skill::strike>(fd, src, skill_targets<Skill::strike>(fd, src), s);
653,409✔
3172
            }
3173
            break;
3174

3175
        default:
116,226,461✔
3176
            n_candidates = select_fast<skill_id>(fd, src, skill_targets<skill_id>(fd, src), s);
116,226,461✔
3177
            break;
3178
    }
3179

3180
    // (false-loop)
3181
    unsigned n_selected = n_candidates;
119,613,822✔
3182
    do
119,094,437✔
3183
    {
3184
        // no candidates
3185
        if (n_candidates == 0)
116,879,870✔
3186
        { break; }
3187

3188
        // show candidates (debug)
3189
        _DEBUG_SELECTION("%s", skill_names[skill_id].c_str());
73,607,537✔
3190

3191
        // analyze targets count / skill
3192
        unsigned n_targets = s.n > 0 ? s.n : 1;
72,447,948✔
3193
        if (s.all || n_targets >= n_candidates || skill_id == Skill::mend || skill_id == Skill::fortify)  // target all or mend
72,447,948✔
3194
        { break; }
3195

3196
        // shuffle & trim
3197
        for (unsigned i = 0; i < n_targets; ++i)
42,505,997✔
3198
        {
3199
            std::swap(fd->selection_array[i], fd->selection_array[fd->rand(i, n_candidates - 1)]);
21,455,198✔
3200
        }
3201
        fd->selection_array.resize(n_targets);
21,050,799✔
3202
        if (n_targets > 1)
21,050,799✔
3203
        {
3204
            std::sort(fd->selection_array.begin(), fd->selection_array.end(),
278,668✔
3205
                    [](const CardStatus * a, const CardStatus * b) { return a->m_index < b->m_index; });
669,861✔
3206
        }
3207
        n_selected = n_targets;
3208

3209
    } while (false); // (end)
3210

3211
    return n_selected;
119,094,437✔
3212
}
3213

3214
    template<Skill::Skill skill_id>
3215
void perform_targetted_allied_fast(Field* fd, CardStatus* src, const SkillSpec& s)
74,709,124✔
3216
{
3217
    select_targets<skill_id>(fd, src, s);
74,709,124✔
3218
    unsigned num_inhibited = 0;
74,709,124✔
3219
#ifndef NQUEST
3220
    bool has_counted_quest = false;
3221
#endif
3222
    bool src_overloaded = src->m_overloaded;
74,709,124✔
3223
    std::vector<CardStatus*> selection_array = fd->selection_array;
74,709,124✔
3224
    for (CardStatus * dst: selection_array)
153,142,146✔
3225
    {
3226
        if (dst->m_inhibited > 0 && !src_overloaded)
78,433,022✔
3227
        {
3228
            _DEBUG_MSG(1, "%s %s on %s but it is inhibited\n",
4,952,889✔
3229
                    status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
3230
                    status_description(dst).c_str());
3231
            -- dst->m_inhibited;
4,494,087✔
3232
            ++ num_inhibited;
4,494,087✔
3233
            continue;
4,494,087✔
3234
        }
4,494,087✔
3235
        check_and_perform_skill<skill_id>(fd, src, dst, s, false
73,938,935✔
3236
#ifndef NQUEST
3237
                , has_counted_quest
3238
#endif
3239
                );
3240
    }
3241

3242
    // Passive BGE: Divert
3243
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::divert], false)
74,709,124✔
3244
            && (num_inhibited > 0))
74,709,124✔
3245
    {
3246
        SkillSpec diverted_ss = s;
5,550✔
3247
        diverted_ss.y = allfactions;
5,550✔
3248
        diverted_ss.n = 1;
5,550✔
3249
        diverted_ss.all = false;
5,550✔
3250
        for (unsigned i = 0; i < num_inhibited; ++ i)
11,148✔
3251
        {
3252
            select_targets<skill_id>(fd, &fd->tip->commander, diverted_ss);
5,598✔
3253
            std::vector<CardStatus*> selection_array = fd->selection_array;
5,598✔
3254
            for (CardStatus * dst: selection_array)
9,417✔
3255
            {
3256
                if (dst->m_inhibited > 0)
3,819✔
3257
                {
3258
                    _DEBUG_MSG(1, "%s %s (Diverted) on %s but it is inhibited\n",
798✔
3259
                            status_description(src).c_str(), skill_short_description(fd->cards, diverted_ss).c_str(),
3260
                            status_description(dst).c_str());
3261
                    -- dst->m_inhibited;
266✔
3262
                    continue;
266✔
3263
                }
266✔
3264
                _DEBUG_MSG(1, "%s %s (Diverted) on %s\n",
10,659✔
3265
                        status_description(src).c_str(), skill_short_description(fd->cards, diverted_ss).c_str(),
3266
                        status_description(dst).c_str());
3267
                perform_skill<skill_id>(fd, src, dst, diverted_ss);
3,574✔
3268
            }
3269
        }
3270
    }
3271
}
74,709,124✔
3272

3273
void perform_targetted_allied_fast_rush(Field* fd, CardStatus* src, const SkillSpec& s)
×
3274
{
3275
    if (src->m_card->m_type == CardType::commander)
×
3276
    {  // Passive BGE skills are casted as by commander
3277
        perform_targetted_allied_fast<Skill::rush>(fd, src, s);
×
3278
        return;
×
3279
    }
3280
    if (src->m_rush_attempted)
×
3281
    {
3282
        _DEBUG_MSG(2, "%s does not check Rush again.\n", status_description(src).c_str());
×
3283
        return;
×
3284
    }
3285
    _DEBUG_MSG(1, "%s attempts to activate Rush.\n", status_description(src).c_str());
×
3286
    perform_targetted_allied_fast<Skill::rush>(fd, src, s);
×
3287
    src->m_rush_attempted = true;
×
3288
}
3289

3290
    template<Skill::Skill skill_id>
3291
void perform_targetted_hostile_fast(Field* fd, CardStatus* src, const SkillSpec& s)
44,379,715✔
3292
{
3293
    select_targets<skill_id>(fd, src, s);
44,379,715✔
3294
    std::vector<CardStatus *> paybackers;
44,379,715✔
3295
#ifndef NQUEST
3296
    bool has_counted_quest = false;
3297
#endif
3298
    const bool has_turningtides = (fd->bg_effects[fd->tapi][PassiveBGE::turningtides] && (skill_id == Skill::weaken || skill_id == Skill::sunder));
44,379,715✔
3299
    unsigned turningtides_value(0), old_attack(0);
44,379,715✔
3300

3301
    // apply skill to each target(dst)
3302
    unsigned selection_array_len = fd->selection_array.size();
44,379,715✔
3303
    std::vector<CardStatus*> selection_array = fd->selection_array;
44,379,715✔
3304
    for (CardStatus * dst: selection_array)
99,226,639✔
3305
    {
3306
        // TurningTides
3307
        if (__builtin_expect(has_turningtides, false))
14,507,611✔
3308
        {
3309
            old_attack = dst->attack_power();
10,921✔
3310
        }
3311

3312
        // check & apply skill to target(dst)
3313
        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))
55,155,687✔
3314
#ifndef NQUEST
3315
                    , has_counted_quest
3316
#endif
3317
                    ))
3318
        {
3319
            // TurningTides: get max attack decreasing
3320
            if (__builtin_expect(has_turningtides, false))
9,687,901✔
3321
            {
3322
                turningtides_value = std::max(turningtides_value, safe_minus(old_attack, dst->attack_power()));
25,241✔
3323
            }
3324

3325
            // Payback/Revenge: collect paybackers/revengers
3326
            unsigned payback_value = dst->skill(Skill::payback) + dst->skill(Skill::revenge);
38,950,261✔
3327
            if ((s.id != Skill::mimic) && (dst->m_paybacked < payback_value) && skill_check<Skill::payback>(fd, dst, src))
38,950,261✔
3328
            {
3329
                paybackers.reserve(selection_array_len);
1,016,070✔
3330
                paybackers.push_back(dst);
1,016,070✔
3331
            }
3332
        }
3333
    }
3334

3335
    // apply TurningTides
3336
    if (__builtin_expect(has_turningtides, false) && (turningtides_value > 0))
12,965,161✔
3337
    {
3338
        SkillSpec ss_rally{Skill::rally, turningtides_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, s.all, 0,};
8,282✔
3339
        _DEBUG_MSG(1, "TurningTides %u!\n", turningtides_value);
16,564✔
3340
        perform_targetted_allied_fast<Skill::rally>(fd, &fd->players[src->m_player]->commander, ss_rally);
8,282✔
3341
    }
3342

3343
    prepend_on_death(fd);  // skills
44,379,715✔
3344

3345
    // Payback/Revenge
3346
    for (CardStatus * pb_status: paybackers)
45,395,785✔
3347
    {
3348
        turningtides_value = 0;
1,016,070✔
3349

3350
        // apply Revenge
3351
        if (pb_status->skill(Skill::revenge))
1,016,070✔
3352
        {
3353
            unsigned revenged_count(0);
3354
            for (unsigned case_index(0); case_index < 3; ++ case_index)
4,062,364✔
3355
            {
3356
                CardStatus * target_status;
3357
#ifndef NDEBUG
3358
                const char * target_desc;
3359
#endif
3360
                switch (case_index)
3,046,773✔
3361
                {
3362
                    // revenge to left
3363
                    case 0:
1,015,591✔
3364
                        if (!(target_status = fd->left_assault(src))) { continue; }
253,859✔
3365
#ifndef NDEBUG
3366
                        target_desc = "left";
3367
#endif
3368
                        break;
3369

3370
                        // revenge to core
3371
                    case 1:
3372
                        target_status = src;
3373
#ifndef NDEBUG
3374
                        target_desc = "core";
3375
#endif
3376
                        break;
3377

3378
                        // revenge to right
3379
                    case 2:
1,015,591✔
3380
                        if (!(target_status = fd->right_assault(src))) { continue; }
2,361,853✔
3381
#ifndef NDEBUG
3382
                        target_desc = "right";
3383
#endif
3384
                        break;
3385

3386
                        // wtf?
3387
                    default:
3388
                        __builtin_unreachable();
3389
                }
3390

3391
                // skip illegal target
3392
                if (!skill_predicate<skill_id>(fd, target_status, target_status, s))
2,546,161✔
3393
                {
3394
                    continue;
155,829✔
3395
                }
3396

3397
                // skip dead target
3398
                if (!is_alive(target_status))
2,390,332✔
3399
                {
3400
#ifndef NDEBUG
3401
                    _DEBUG_MSG(1, "(CANCELLED: target unit dead) %s Revenge (to %s) %s on %s\n",
×
3402
                            status_description(pb_status).c_str(), target_desc,
3403
                            skill_short_description(fd->cards, s).c_str(), status_description(target_status).c_str());
3404
#endif
3405
                    continue;
×
3406
                }
×
3407

3408
                // TurningTides
3409
                if (__builtin_expect(has_turningtides, false))
77,211✔
3410
                {
3411
                    old_attack = target_status->attack_power();
936✔
3412
                }
3413

3414
                // apply revenged skill
3415
#ifndef NDEBUG
3416
                _DEBUG_MSG(1, "%s Revenge (to %s) %s on %s\n",
2,571,526✔
3417
                        status_description(pb_status).c_str(), target_desc,
3418
                        skill_short_description(fd->cards, s).c_str(), status_description(target_status).c_str());
3419
#endif
3420
                perform_skill<skill_id>(fd, pb_status, target_status, s);
2,390,332✔
3421
                ++ revenged_count;
2,390,332✔
3422

3423
                // revenged TurningTides: get max attack decreasing
3424
                if (__builtin_expect(has_turningtides, false))
77,211✔
3425
                {
3426
                    turningtides_value = std::max(turningtides_value, safe_minus(old_attack, target_status->attack_power()));
2,424✔
3427
                }
3428
            }
3429
            if (revenged_count)
1,015,591✔
3430
            {
3431
                // consume remaining payback/revenge
3432
                ++ pb_status->m_paybacked;
994,469✔
3433

3434
                // apply TurningTides
3435
                if (__builtin_expect(has_turningtides, false) && (turningtides_value > 0))
40,861✔
3436
                {
3437
                    SkillSpec ss_rally{Skill::rally, turningtides_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
480✔
3438
                    _DEBUG_MSG(1, "Paybacked TurningTides %u!\n", turningtides_value);
960✔
3439
                    perform_targetted_allied_fast<Skill::rally>(fd, &fd->players[pb_status->m_player]->commander, ss_rally);
480✔
3440
                }
3441
            }
3442
        }
3443
        // apply Payback
3444
        else
3445
        {
3446
            // skip illegal target(src)
3447
            if (!skill_predicate<skill_id>(fd, src, src, s))
479✔
3448
            {
3449
                continue;
167✔
3450
            }
3451

3452
            // skip dead target(src)
3453
            if (!is_alive(src))
312✔
3454
            {
3455
                _DEBUG_MSG(1, "(CANCELLED: src unit dead) %s Payback %s on %s\n",
×
3456
                        status_description(pb_status).c_str(), skill_short_description(fd->cards, s).c_str(),
3457
                        status_description(src).c_str());
3458
                continue;
×
3459
            }
×
3460

3461
            // TurningTides
3462
            if (__builtin_expect(has_turningtides, false))
77✔
3463
            {
3464
                old_attack = src->attack_power();
×
3465
            }
3466

3467
            // apply paybacked skill
3468
            _DEBUG_MSG(1, "%s Payback %s on %s\n",
936✔
3469
                    status_description(pb_status).c_str(), skill_short_description(fd->cards, s).c_str(), status_description(src).c_str());
3470
            perform_skill<skill_id>(fd, pb_status, src, s);
312✔
3471
            ++ pb_status->m_paybacked;
312✔
3472

3473
            // handle paybacked TurningTides
3474
            if (__builtin_expect(has_turningtides, false))
77✔
3475
            {
3476
                turningtides_value = std::max(turningtides_value, safe_minus(old_attack, src->attack_power()));
×
3477
                if (turningtides_value > 0)
×
3478
                {
3479
                    SkillSpec ss_rally{Skill::rally, turningtides_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
×
3480
                    _DEBUG_MSG(1, "Paybacked TurningTides %u!\n", turningtides_value);
×
3481
                    perform_targetted_allied_fast<Skill::rally>(fd, &fd->players[pb_status->m_player]->commander, ss_rally);
×
3482
                }
3483
            }
3484
        }
3485
    }
3486

3487
    prepend_on_death(fd,true);  // paybacked skills
44,379,715✔
3488
}
44,379,715✔
3489

3490
//------------------------------------------------------------------------------
3491
inline unsigned evaluate_brawl_score(Field* fd, unsigned player)
×
3492
{
3493
    const auto & p = fd->players;
×
3494
    return 55
×
3495
        // - (10 - p[player]->deck->cards.size())
3496
        // + (10 - p[opponent(player)]->deck->cards.size())
3497
        + p[opponent(player)]->total_cards_destroyed
×
3498
        + p[player]->deck->shuffled_cards.size()
×
3499
        - (unsigned)((fd->turn+7)/8);
×
3500
}
3501

3502
inline unsigned evaluate_war_score(Field* fd, unsigned player)
×
3503
{
3504
    return 208 - ((unsigned)(fd->turn)/2)*4;
×
3505
}
3506
int evaluate_card(Field* fd,const Card* cs);
3507
int evaluate_skill(Field* fd,const Card* c , SkillSpec* ss)
×
3508
{
3509
        // TODO optimize this
3510
        int tvalue = ss->x;
×
3511

3512
        if(ss->card_id != 0)tvalue += 1*evaluate_card(fd,card_by_id_safe(fd->cards,ss->card_id));
×
3513
        tvalue += 10*(ss->id==Skill::flurry);
×
3514
        tvalue += 10*(ss->id==Skill::jam);
×
3515
        tvalue += 5*(ss->id==Skill::overload);
×
3516
        tvalue += 2*(ss->id==Skill::flying);
×
3517
        tvalue += 2*(ss->id==Skill::evolve);
×
3518
        tvalue += 2*(ss->id==Skill::wall);
×
3519
        tvalue += 5*(ss->id==Skill::tribute);
×
3520

3521
        tvalue *= 1.+1.5*(ss->id==Skill::flurry);
×
3522
        tvalue *= 1.+1.5*(ss->id==Skill::drain);
×
3523
        tvalue *= 1.+1.5*(ss->id==Skill::mortar);
×
3524
        tvalue *= 1.+1.5*(ss->id==Skill::scavenge);
×
3525
        tvalue *= 1.+1.5*(ss->id==Skill::disease);
×
3526

3527
        tvalue *= 1.+1.3*(ss->id==Skill::rally);
×
3528
        tvalue *= 1.+1.3*(ss->id==Skill::strike);
×
3529

3530
        tvalue *= 1.+1.2*(ss->id==Skill::avenge);
×
3531
        tvalue *= 1.+1.1*(ss->id==Skill::sunder);
×
3532
        tvalue *= 1.+1.1*(ss->id==Skill::venom);
×
3533

3534
        tvalue *= 1.+1.0*(ss->id==Skill::evade);
×
3535
        tvalue *= 1.+1.0*(ss->id==Skill::enfeeble);
×
3536

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

3539
        tvalue *= 1.+0.2*(ss->id==Skill::fortify);
×
3540
        tvalue *= 1.+0.5*(ss->id==Skill::mend);
×
3541

3542
        tvalue *= 1.+0.4*(ss->id==Skill::jam);
×
3543
        tvalue *= 1.+0.4*(ss->id==Skill::overload);
×
3544
        //tvalue *= 1.+0.4*(ss->id==Skill::rupture);
3545
        tvalue *= 1.+0.4*(ss->id==Skill::bravery);
×
3546
        tvalue *= 1.+0.4*(ss->id==Skill::entrap);
×
3547
        tvalue *= 1.+0.4*(ss->id==Skill::heal);
×
3548

3549

3550
        tvalue *= 1.+0.3*(ss->id==Skill::revenge);
×
3551
        tvalue *= 1.+0.3*(ss->id==Skill::enrage);
×
3552

3553

3554
        //tvalue *= 1.+2.1*(ss->id==Skill::hunt);
3555
        tvalue *= 1.+0.1*(ss->id==Skill::mark);
×
3556
        tvalue *= 1.+0.1*(ss->id==Skill::coalition);
×
3557
        tvalue *= 1.+0.1*(ss->id==Skill::legion);
×
3558
        //tvalue *= 1.+1.1*(ss->id==Skill::barrier);
3559
        tvalue *= 1.+0.1*(ss->id==Skill::pierce);
×
3560
        tvalue *= 1.+0.1*(ss->id==Skill::armor);
×
3561
        //tvalue *= 1.+0.1*(ss->id==Skill::swipe);
3562
        //tvalue *= 1.+0.1*(ss->id==Skill::berserk);
3563
        tvalue *= 1.-0.1*(ss->id==Skill::weaken);
×
3564

3565

3566

3567
        tvalue *= 1.-0.5 *(ss->id==Skill::sabotage); //sucks
×
3568
        tvalue *= 1.-0.5 *(ss->id==Skill::inhibit); //sucks
×
3569
        tvalue *= 1.-0.5 *(ss->id==Skill::corrosive); //sucks
×
3570
        tvalue *= 1.-0.5 *(ss->id==Skill::payback); //sucks
×
3571
        tvalue *= 1.-0.5 *(ss->id==Skill::leech); //sucks
×
3572

3573

3574
        tvalue *= 1.+1*ss->all;
×
3575
        tvalue *= 1.-1./5.*ss->all*(ss->y!=0);
×
3576
        tvalue *= 1.+1*std::min<int>(3,ss->n);
×
3577
        tvalue *= 1.-1./3.* ((c->m_skill_trigger[ss->id] == Skill::Trigger::death) + (c->m_skill_trigger[ss->id] == Skill::Trigger::play));
×
3578
        tvalue *= 1./(2.+ss->c);
×
3579
        //if(tvalue == 0) std::cout << ss->id << " "<<tvalue << std::endl;
3580
        //if(tvalue > 10000) std::cout << ss->id <<" "<< tvalue << std::endl;
3581
        return 0.9*tvalue; // 0.85
×
3582
}
3583
int evaluate_card(Field* fd,const Card* cs)
×
3584
{
3585
        int value = 0;
×
3586
        value += cs->m_health;
×
3587
        value += 2*cs->m_attack;
×
3588
        for( auto ss : cs->m_skills) {
×
3589
                value += evaluate_skill(fd,cs,&ss);
×
3590
        }
3591
        int denom_scale = 1+cs->m_delay*0;
×
3592
        //if(value > 10000) std::cout << cs->m_name << value << std::endl;
3593
        return value /denom_scale;
×
3594
}
3595
int evaluate_cardstatus(Field* fd,CardStatus* cs)
×
3596
{
3597
        int value = 0;
×
3598
        value += cs->m_hp;
×
3599
        value += 2*cs->attack_power();
×
3600
        value += cs->protected_value();
×
3601
        for( auto ss : cs->m_card->m_skills) {
×
3602
                value += evaluate_skill(fd,cs->m_card,&ss);
×
3603
        }
3604
        value -= (cs->m_enfeebled);
×
3605
        int denom_scale = 1+cs->m_delay*0;
×
3606
#ifdef DEBUG
3607
        if(value > 10000) std::cout << cs->m_card->m_name << value <<std::endl;
3608
#endif
3609
        return value /denom_scale;
×
3610
}
3611
// calculate a value for current field, high values are better for fd->tap
3612
// dead commander -> the player gets zero value
3613
int evaluate_field(Field* fd)
×
3614
{
3615
        int value = 0;
×
3616

3617
        int scale = is_alive(&fd->tap->commander);
×
3618
        auto& assaults(fd->tap->assaults);
×
3619
        auto& structures(fd->tap->structures);
×
3620

3621

3622
        value += 0.5*scale * evaluate_cardstatus(fd,&fd->tap->commander);
×
3623
        for(unsigned index(0); index < assaults.size();++index)
×
3624
        {
3625
                value += scale * evaluate_cardstatus(fd,&assaults[index]);
×
3626
        }
3627
        for(unsigned index(0); index < structures.size();++index)
×
3628
        {
3629
                value += scale * evaluate_cardstatus(fd,&structures[index]);
×
3630
        }
3631

3632
        scale = is_alive(&fd->tip->commander);
×
3633
        auto& eassaults(fd->tip->assaults);
×
3634
        auto& estructures(fd->tip->structures);
×
3635
        value -= 0.5*scale * evaluate_cardstatus(fd,&fd->tip->commander);
×
3636
        for(unsigned index(0); index < eassaults.size();++index)
×
3637
        {
3638
                value -= (scale * evaluate_cardstatus(fd,&eassaults[index]));
×
3639
        }
3640
        for(unsigned index(0); index < estructures.size();++index)
×
3641
        {
3642
                value -= (scale * evaluate_cardstatus(fd,&estructures[index]));
×
3643
        }
3644
        return value;
×
3645
}
3646

3647

3648
Results<uint64_t> evaluate_sim_result(Field* fd, bool single_turn_both)
1,357,213✔
3649
{
3650
    typedef unsigned points_score_type;
1,357,213✔
3651
    const auto & p = fd->players;
1,357,213✔
3652
    unsigned raid_damage = 0;
1,357,213✔
3653
#ifndef NQUEST
3654
    unsigned quest_score = 0;
3655
#endif
3656

3657
    if(single_turn_both)
1,357,213✔
3658
    {
3659
        bool sign = evaluate_field(fd)<0;
×
3660
        unsigned val = evaluate_field(fd) *(1-2*sign);
×
3661
        return {!is_alive(&fd->players[1]->commander),sign,!is_alive(&fd->players[0]->commander),val,1};
×
3662
    }
3663
    switch (fd->optimization_mode)
1,357,213✔
3664
    {
3665
        case OptimizationMode::raid:
×
3666
            raid_damage = 15
×
3667
                + (p[1]->total_nonsummon_cards_destroyed)
×
3668
                - (10 * p[1]->commander.m_hp / p[1]->commander.max_hp());
×
3669
            break;
×
3670
#ifndef NQUEST
3671
        case OptimizationMode::quest:
3672
            if (fd->quest.quest_type == QuestType::card_survival)
3673
            {
3674
                for (const auto & status: p[0]->assaults.m_indirect)
3675
                { fd->quest_counter += (fd->quest.quest_key == status->m_card->m_id); }
3676
                for (const auto & status: p[0]->structures.m_indirect)
3677
                { fd->quest_counter += (fd->quest.quest_key == status->m_card->m_id); }
3678
                for (const auto & card: p[0]->deck->shuffled_cards)
3679
                { fd->quest_counter += (fd->quest.quest_key == card->m_id); }
3680
            }
3681
            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);
3682
            _DEBUG_MSG(1, "Quest: %u / %u = %u%%.\n", fd->quest_counter, fd->quest.quest_value, quest_score);
3683
            break;
3684
#endif
3685
        default:
3686
            break;
3687
    }
3688
    // you lose
3689
    if(!is_alive(&fd->players[0]->commander))
1,357,213✔
3690
    {
3691
        _DEBUG_MSG(1, "You lose.\n");
167,054✔
3692
        switch (fd->optimization_mode)
167,054✔
3693
        {
3694
            case OptimizationMode::raid: return {0, 0, 1, (points_score_type)raid_damage,1};
×
3695
            case OptimizationMode::brawl: return {0, 0, 1, (points_score_type) 5,1};
×
3696
            case OptimizationMode::brawl_defense:
×
3697
                                          {
×
3698
                                              unsigned enemy_brawl_score = evaluate_brawl_score(fd, 1);
×
3699
                                              unsigned max_score = max_possible_score[(size_t)OptimizationMode::brawl_defense];
×
3700
                                              if(enemy_brawl_score> max_score)
×
3701
                                                std::cerr << "WARNING: enemy_brawl_score > max_possible_brawl_score" << std::endl;
×
3702
                                              return {0, 0, 1, (points_score_type)safe_minus(max_score , enemy_brawl_score),1};
×
3703
                                          }
3704
            case OptimizationMode::war: return {0,0,1, (points_score_type) 20,1};
×
3705
            case OptimizationMode::war_defense:
×
3706
                                        {
×
3707
                                            unsigned enemy_war_score = evaluate_war_score(fd, 1);
×
3708
                                            unsigned max_score = max_possible_score[(size_t)OptimizationMode::war_defense];
×
3709
                                            if(enemy_war_score> max_score)
×
3710
                                                std::cerr << "WARNING: enemy_war_score > max_possible_war_score" << std::endl;
×
3711
                                            return {0, 0, 1, (points_score_type)safe_minus(max_score , enemy_war_score),1};
×
3712
                                        }
3713
#ifndef NQUEST
3714
            case OptimizationMode::quest: return {0, 0, 1, (points_score_type)(fd->quest.must_win ? 0 : quest_score),1};
3715
#endif
3716
            default: return {0, 0, 1, 0,1};
167,054✔
3717
        }
3718
    }
3719
    // you win
3720
    if(!is_alive(&fd->players[1]->commander))
1,190,159✔
3721
    {
3722
        _DEBUG_MSG(1, "You win.\n");
1,169,191✔
3723
        switch (fd->optimization_mode)
1,169,191✔
3724
        {
3725
            case OptimizationMode::brawl:
×
3726
                {
×
3727
                    unsigned brawl_score = evaluate_brawl_score(fd, 0);
×
3728
                    return {1, 0, 0, (points_score_type)brawl_score,1};
×
3729
                }
3730
            case OptimizationMode::brawl_defense:
×
3731
                {
×
3732
                    unsigned max_score = max_possible_score[(size_t)OptimizationMode::brawl_defense];
×
3733
                    unsigned min_score = min_possible_score[(size_t)OptimizationMode::brawl_defense];
×
3734
                    return {1, 0, 0, (points_score_type)(max_score - min_score),1};
×
3735
                }
3736
            case OptimizationMode::campaign:
×
3737
                {
×
3738
                    unsigned total_dominions_destroyed = (p[0]->deck->alpha_dominion != nullptr) - p[0]->structures.count(is_it_dominion);
×
3739
                    unsigned campaign_score = 100 - 10 * (p[0]->total_nonsummon_cards_destroyed - total_dominions_destroyed);
×
3740
                    return {1, 0, 0, (points_score_type)campaign_score,1};
×
3741
                }
3742
            case OptimizationMode::war:
×
3743
                {
×
3744
                    unsigned war_score = evaluate_war_score(fd, 0);
×
3745
                    return {1,0,0, (points_score_type) war_score,1};
×
3746
                }
3747
            case OptimizationMode::war_defense:
×
3748
                {
×
3749
                    unsigned max_score = max_possible_score[(size_t)OptimizationMode::war_defense];
×
3750
                    unsigned min_score = min_possible_score[(size_t)OptimizationMode::war_defense];
×
3751
                    return {1, 0, 0, (points_score_type)(max_score - min_score),1};
×
3752
                }
3753
#ifndef NQUEST
3754
            case OptimizationMode::quest: return {1, 0, 0, (points_score_type)(fd->quest.win_score + quest_score),1};
3755
#endif
3756
            default:
1,169,191✔
3757
                                          return {1, 0, 0, 100,1};
1,169,191✔
3758
        }
3759
    }
3760
    if (fd->turn > turn_limit)
20,968✔
3761
    {
3762
        _DEBUG_MSG(1, "Stall after %u turns.\n", turn_limit);
20,968✔
3763
        switch (fd->optimization_mode)
20,968✔
3764
        {
3765
            case OptimizationMode::defense: return {0, 1, 0, 100,1};
×
3766
            case OptimizationMode::raid: return {0, 1, 0, (points_score_type)raid_damage,1};
×
3767
            case OptimizationMode::brawl: return {0, 1, 0, 5,1};
×
3768
            case OptimizationMode::brawl_defense:
×
3769
                                          {
×
3770
                                              unsigned max_score = max_possible_score[(size_t)OptimizationMode::brawl_defense];
×
3771
                                              unsigned min_score = min_possible_score[(size_t)OptimizationMode::brawl_defense];
×
3772
                                              return {1, 0, 0, (points_score_type)(max_score - min_score),1};
×
3773
                                          }
3774
            case OptimizationMode::war: return {0,1,0, (points_score_type) 20,1};
×
3775
            case OptimizationMode::war_defense:
×
3776
                                        {
×
3777
                                            unsigned max_score = max_possible_score[(size_t)OptimizationMode::war_defense];
×
3778
                                            unsigned min_score = min_possible_score[(size_t)OptimizationMode::war_defense];
×
3779
                                            return {1, 0, 0, (points_score_type)(max_score - min_score),1};
×
3780
                                        }
3781
#ifndef NQUEST
3782
            case OptimizationMode::quest: return {0, 1, 0, (points_score_type)(fd->quest.must_win ? 0 : quest_score),1};
3783
#endif
3784
            default: return {0, 1, 0, 0,1};
20,968✔
3785
        }
3786
    }
3787

3788
    // Huh? How did we get here?
3789
    assert(false);
×
3790
    return {0, 0, 0, 0,1};
3791
}
3792

3793
//------------------------------------------------------------------------------
3794
//turns_both sets the number of turns to sim before exiting before winner exists.
3795
Results<uint64_t> play(Field* fd,bool skip_init, bool skip_preplay,unsigned turns_both)
1,357,213✔
3796
{
3797
    if(!skip_init){ //>>> start skip init
1,357,213✔
3798
        fd->players[0]->commander.m_player = 0;
1,357,213✔
3799
        fd->players[1]->commander.m_player = 1;
1,357,213✔
3800
        fd->tapi = fd->gamemode == surge ? 1 : 0;
1,357,213✔
3801
        fd->tipi = opponent(fd->tapi);
1,357,213✔
3802
        fd->tap = fd->players[fd->tapi];
1,357,213✔
3803
        fd->tip = fd->players[fd->tipi];
1,357,213✔
3804
        fd->end = false;
1,357,213✔
3805

3806
        // Play dominion & fortresses
3807
        for (unsigned _(0), ai(fd->tapi); _ < 2; ++_)
4,071,639✔
3808
        {
3809
            if (fd->players[ai]->deck->alpha_dominion)
2,714,426✔
3810
            { PlayCard(fd->players[ai]->deck->alpha_dominion, fd, ai, &fd->players[ai]->commander).op<CardType::structure>(); }
1,473,062✔
3811
            for (const Card* played_card: fd->players[ai]->deck->shuffled_forts)
5,428,852✔
3812
            {
3813

3814
                switch (played_card->m_type)
×
3815
                {
3816
                    case CardType::assault:
×
3817
                        PlayCard(played_card, fd, ai, &fd->players[ai]->commander).op<CardType::assault>();
×
3818
                        break;
×
3819
                    case CardType::structure:
×
3820
                        PlayCard(played_card, fd, ai, &fd->players[ai]->commander).op<CardType::structure>();
×
3821
                        break;
×
3822
                    case CardType::commander:
×
3823
                    case CardType::num_cardtypes:
×
3824
                        _DEBUG_MSG(0, "Unknown card type: #%u %s: %u\n",
×
3825
                                played_card->m_id, card_description(fd->cards, played_card).c_str(), played_card->m_type);
×
3826
                        assert(false);
×
3827
                        break;
3828
                }
3829
            }
3830
            resolve_skill(fd);
2,714,426✔
3831
            std::swap(fd->tapi, fd->tipi);
2,714,426✔
3832
            std::swap(fd->tap, fd->tip);
2,714,426✔
3833
            ai = opponent(ai);
2,714,426✔
3834
        }
3835
    }//>>> end skip init
3836
    unsigned both_turn_limit = fd->turn+2*turns_both;
1,357,213✔
3837
    while(__builtin_expect(fd->turn <= turn_limit && !fd->end && (turns_both==0 || fd->turn < both_turn_limit), true))
18,422,837✔
3838
    {
3839
        if(!skip_preplay){ //>>> start skip init
18,401,869✔
3840

3841
            fd->current_phase = Field::playcard_phase;
18,401,869✔
3842
            // Initialize stuff, remove dead cards
3843
            _DEBUG_MSG(1, "------------------------------------------------------------------------\n"
18,401,869✔
3844
                    "TURN %u begins for %s\n", fd->turn, status_description(&fd->tap->commander).c_str());
18,401,869✔
3845

3846
            // reduce timers & perform triggered skills (like Summon)
3847
            fd->prepare_action();
18,401,869✔
3848
            turn_start_phase(fd); // summon may postpone skills to be resolved
18,401,869✔
3849
            resolve_skill(fd); // resolve postponed skills recursively
18,401,869✔
3850
            fd->finalize_action();
18,401,869✔
3851

3852
            //bool bge_megamorphosis = fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis];
3853

3854
        }//>>> end skip init
3855
        else { skip_preplay = false;}
3856
        // Play a card
3857
        const Card* played_card(fd->tap->deck->next(fd));
18,401,869✔
3858
        if (played_card)
18,401,869✔
3859
        {
3860

3861
            // Begin 'Play Card' phase action
3862
            fd->prepare_action();
16,522,320✔
3863

3864
            // Play selected card
3865
            //CardStatus* played_status = nullptr;
3866
            switch (played_card->m_type)
16,522,320✔
3867
            {
3868
                case CardType::assault:
15,736,090✔
3869
                    PlayCard(played_card, fd, fd->tapi, &fd->tap->commander).op<CardType::assault>();
15,736,090✔
3870
                    break;
15,736,090✔
3871
                case CardType::structure:
786,230✔
3872
                    PlayCard(played_card, fd, fd->tapi, &fd->tap->commander).op<CardType::structure>();
786,230✔
3873
                    break;
786,230✔
3874
                case CardType::commander:
×
3875
                case CardType::num_cardtypes:
×
3876
                    _DEBUG_MSG(0, "Unknown card type: #%u %s: %u\n",
×
3877
                            played_card->m_id, card_description(fd->cards, played_card).c_str(), played_card->m_type);
×
3878
                    assert(false);
×
3879
                    break;
3880
            }
3881
            resolve_skill(fd); // resolve postponed skills recursively
16,522,320✔
3882
            //status_description(played_status)
3883
            //_DEBUG_MSG(3,"Card played: %s", status_description(played_status).c_str());
3884
            // End 'Play Card' phase action
3885
            fd->finalize_action();
16,522,320✔
3886

3887

3888

3889
        }
3890
        if (__builtin_expect(fd->end, false)) { break; }
18,401,869✔
3891

3892
        //-------------------------------------------------
3893
        // Phase: (Later-) Enhance, Inhibit, Sabotage, Disease
3894
        //-------------------------------------------------
3895
        //Skill: Enhance
3896
        //Perform later enhance for commander
3897
        if(!fd->fixes[Fix::enhance_early]) {
18,401,869✔
3898
        check_and_perform_later_enhance(fd,&fd->tap->commander);
×
3899
        auto& structures(fd->tap->structures);
×
3900
        for(unsigned index(0); index < structures.size(); ++index)
×
3901
        {
3902
            CardStatus * status = &structures[index];
×
3903
            //enhance everything else after card was played
3904
            check_and_perform_later_enhance(fd,status);
×
3905
        }
3906
        }
3907
        //Perform Inhibit, Sabotage, Disease
3908
        auto& assaults(fd->tap->assaults);
18,401,869✔
3909
        for(unsigned index(0); index < assaults.size(); ++index)
70,510,571✔
3910
        {
3911
            CardStatus * att_status = &assaults[index];
52,108,702✔
3912
            if(att_status->m_index >= fd->tip->assaults.size())continue; //skip no enemy
52,108,702✔
3913
            auto def_status = &fd->tip->assaults[att_status->m_index];
31,614,507✔
3914
            if(!is_alive(def_status))continue; //skip dead
31,614,507✔
3915

3916
            check_and_perform_inhibit(fd,att_status,def_status);
31,614,298✔
3917
            check_and_perform_sabotage(fd,att_status,def_status);
31,614,298✔
3918
            check_and_perform_disease(fd,att_status,def_status);
31,614,298✔
3919
        }
3920
        //-------------------------------------------------
3921

3922
        // Evaluate Passive BGE Heroism skills
3923
        if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::heroism], false))
18,401,869✔
3924
        {
3925
            for (CardStatus * dst: fd->tap->assaults.m_indirect)
242,163✔
3926
            {
3927
                unsigned bge_value = (dst->skill(Skill::valor) + dst->skill(Skill::bravery)+ 1) / 2;
150,151✔
3928
                if (bge_value <= 0)
150,151✔
3929
                { continue; }
132,193✔
3930
                SkillSpec ss_protect{Skill::protect, bge_value, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
18,048✔
3931
                if (dst->m_inhibited > 0)
18,048✔
3932
                {
3933
                    _DEBUG_MSG(1, "Heroism: %s on %s but it is inhibited\n",
90✔
3934
                            skill_short_description(fd->cards, ss_protect).c_str(), status_description(dst).c_str());
90✔
3935
                    -- dst->m_inhibited;
90✔
3936

3937
                    // Passive BGE: Divert
3938
                    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::divert], false))
90✔
3939
                    {
3940
                        SkillSpec diverted_ss = ss_protect;
×
3941
                        diverted_ss.y = allfactions;
×
3942
                        diverted_ss.n = 1;
×
3943
                        diverted_ss.all = false;
×
3944
                        // for (unsigned i = 0; i < num_inhibited; ++ i)
3945
                        {
×
3946
                            select_targets<Skill::protect>(fd, &fd->tip->commander, diverted_ss);
×
3947
                            std::vector<CardStatus*> selection_array = fd->selection_array;
×
3948
                            for (CardStatus * dst: selection_array)
×
3949
                            {
3950
                                if (dst->m_inhibited > 0)
×
3951
                                {
3952
                                    _DEBUG_MSG(1, "Heroism: %s (Diverted) on %s but it is inhibited\n",
×
3953
                                            skill_short_description(fd->cards, diverted_ss).c_str(), status_description(dst).c_str());
×
3954
                                    -- dst->m_inhibited;
×
3955
                                    continue;
×
3956
                                }
×
3957
                                _DEBUG_MSG(1, "Heroism: %s (Diverted) on %s\n",
×
3958
                                        skill_short_description(fd->cards, diverted_ss).c_str(), status_description(dst).c_str());
×
3959
                                perform_skill<Skill::protect>(fd, &fd->tap->commander, dst, diverted_ss);  // XXX: the caster
×
3960
                            }
3961
                        }
×
3962
                    }
3963
                    continue;
90✔
3964
                }
90✔
3965
#ifndef NQUEST
3966
                bool has_counted_quest = false;
3967
#endif
3968
                check_and_perform_skill<Skill::protect>(fd, &fd->tap->commander, dst, ss_protect, false
17,958✔
3969
#ifndef NQUEST
3970
                        , has_counted_quest
3971
#endif
3972
                        );
3973
            }
3974
        }
3975

3976
        // Evaluate activation BGE skills
3977
        fd->current_phase = Field::bge_phase;
18,401,869✔
3978
        for (const auto & bg_skill: fd->bg_skills[fd->tapi])
18,494,508✔
3979
        {
3980
            fd->prepare_action();
92,639✔
3981
            _DEBUG_MSG(2, "Evaluating BG skill %s\n", skill_description(fd->cards, bg_skill).c_str());
92,639✔
3982
            fd->skill_queue.emplace_back(&fd->tap->commander, bg_skill);
92,639✔
3983
            resolve_skill(fd);
92,639✔
3984
            fd->finalize_action();
92,639✔
3985
        }
3986
        if (__builtin_expect(fd->end, false)) { break; }
18,401,869✔
3987

3988
        // Evaluate commander
3989
        fd->current_phase = Field::commander_phase;
18,401,869✔
3990
        evaluate_skills<CardType::commander>(fd, &fd->tap->commander, fd->tap->commander.m_card->m_skills);
18,401,869✔
3991
        if (__builtin_expect(fd->end, false)) { break; }
18,401,869✔
3992

3993
        // Evaluate structures
3994
        fd->current_phase = Field::structures_phase;
18,401,869✔
3995
        for (fd->current_ci = 0; !fd->end && (fd->current_ci < fd->tap->structures.size()); ++fd->current_ci)
43,309,006✔
3996
        {
3997
            CardStatus* current_status(&fd->tap->structures[fd->current_ci]);
24,907,137✔
3998
            if (!is_active(current_status))
24,907,137✔
3999
            {
4000
                _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str());
8,358,413✔
4001
            }
4002
            else
4003
            {
4004
                evaluate_skills<CardType::structure>(fd, current_status, current_status->m_card->m_skills);
16,548,724✔
4005
            }
4006
        }
4007

4008
        // Evaluate assaults
4009
        fd->current_phase = Field::assaults_phase;
18,401,869✔
4010
        fd->bloodlust_value = 0;
18,401,869✔
4011
        for (fd->current_ci = 0; !fd->end && (fd->current_ci < fd->tap->assaults.size()); ++fd->current_ci)
65,251,146✔
4012
        {
4013
            CardStatus* current_status(&fd->tap->assaults[fd->current_ci]);
48,185,522✔
4014
            bool attacked = false;
48,185,522✔
4015
            if (!is_active(current_status))
48,185,522✔
4016
            {
4017
                _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str());
25,130,689✔
4018
                // Passive BGE: HaltedOrders
4019
                /*
4020
                unsigned inhibit_value;
4021
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::haltedorders], false)
4022
                        && (current_status->m_delay > 0) // still frozen
4023
                        && (fd->current_ci < fd->tip->assaults.size()) // across slot isn't empty
4024
                        && is_alive(&fd->tip->assaults[fd->current_ci]) // across assault is alive
4025
                        && ((inhibit_value = current_status->skill(Skill::inhibit))
4026
                            > fd->tip->assaults[fd->current_ci].m_inhibited)) // inhibit/re-inhibit(if higher)
4027
                        {
4028
                            CardStatus* across_status(&fd->tip->assaults[fd->current_ci]);
4029
                            _DEBUG_MSG(1, "Halted Orders: %s inhibits %s by %u\n",
4030
                                    status_description(current_status).c_str(),
4031
                                    status_description(across_status).c_str(), inhibit_value);
4032
                            across_status->m_inhibited = inhibit_value;
4033
                        }
4034
                        */
4035
            }
4036
            else
4037
            {
4038
                if (current_status->m_protected_stasis)
23,054,833✔
4039
                {
4040
                    _DEBUG_MSG(1, "%s loses Stasis protection (activated)\n",
2,121,184✔
4041
                            status_description(current_status).c_str());
4042
                }
4043
                current_status->m_protected_stasis = 0;
23,054,833✔
4044
                fd->assault_bloodlusted = false;
23,054,833✔
4045
                current_status->m_step = CardStep::attacking;
23,054,833✔
4046
                evaluate_skills<CardType::assault>(fd, current_status, current_status->m_card->m_skills, &attacked);
23,054,833✔
4047
                if (__builtin_expect(fd->end, false)) { break; }
23,054,833✔
4048
                if (__builtin_expect(!is_alive(current_status), false)) { continue; }
21,718,588✔
4049
            }
4050

4051
            current_status->m_step = CardStep::attacked;
46,099,838✔
4052
        }
4053
        fd->current_phase = Field::end_phase;
18,401,869✔
4054
        turn_end_phase(fd);
18,401,869✔
4055
        if (__builtin_expect(fd->end, false)) { break; }
18,401,869✔
4056
        _DEBUG_MSG(1, "TURN %u ends for %s\n", fd->turn, status_description(&fd->tap->commander).c_str());
17,065,624✔
4057
        std::swap(fd->tapi, fd->tipi);
17,065,624✔
4058
        std::swap(fd->tap, fd->tip);
17,065,624✔
4059
        ++fd->turn;
17,065,624✔
4060
    }
4061

4062
    return evaluate_sim_result(fd,turns_both!= 0);
1,357,213✔
4063
}
4064

4065
//------------------------------------------------------------------------------
4066
void fill_skill_table()
81✔
4067
{
4068
    memset(skill_table, 0, sizeof skill_table);
81✔
4069
    skill_table[Skill::mortar] = perform_targetted_hostile_fast<Skill::mortar>;
81✔
4070
    skill_table[Skill::enfeeble] = perform_targetted_hostile_fast<Skill::enfeeble>;
81✔
4071
    skill_table[Skill::enhance] = perform_targetted_allied_fast<Skill::enhance>;
81✔
4072
    skill_table[Skill::evolve] = perform_targetted_allied_fast<Skill::evolve>;
81✔
4073
    skill_table[Skill::heal] = perform_targetted_allied_fast<Skill::heal>;
81✔
4074
    skill_table[Skill::jam] = perform_targetted_hostile_fast<Skill::jam>;
81✔
4075
    skill_table[Skill::mend] = perform_targetted_allied_fast<Skill::mend>;
81✔
4076
    skill_table[Skill::fortify] = perform_targetted_allied_fast<Skill::fortify>;
81✔
4077
    skill_table[Skill::overload] = perform_targetted_allied_fast<Skill::overload>;
81✔
4078
    skill_table[Skill::protect] = perform_targetted_allied_fast<Skill::protect>;
81✔
4079
    skill_table[Skill::rally] = perform_targetted_allied_fast<Skill::rally>;
81✔
4080
    skill_table[Skill::enrage] = perform_targetted_allied_fast<Skill::enrage>;
81✔
4081
    skill_table[Skill::entrap] = perform_targetted_allied_fast<Skill::entrap>;
81✔
4082
    skill_table[Skill::rush] = perform_targetted_allied_fast_rush;
81✔
4083
    skill_table[Skill::siege] = perform_targetted_hostile_fast<Skill::siege>;
81✔
4084
    skill_table[Skill::strike] = perform_targetted_hostile_fast<Skill::strike>;
81✔
4085
    skill_table[Skill::sunder] = perform_targetted_hostile_fast<Skill::sunder>;
81✔
4086
    skill_table[Skill::weaken] = perform_targetted_hostile_fast<Skill::weaken>;
81✔
4087
    skill_table[Skill::mimic] = perform_targetted_hostile_fast<Skill::mimic>;
81✔
4088
}
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