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

APN-Pucky / tyrant_optimize / 20466751125

23 Dec 2025 04:57PM UTC coverage: 70.389% (-0.07%) from 70.463%
20466751125

push

github

dsuchka
Fix skill names (fixes dd7e0c1 - Fix Poison skill class)

4778 of 6788 relevant lines covered (70.39%)

9463573.13 hits per line

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

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

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

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

16
template<enum CardType::CardType def_cardtype>
17
void perform_bge_devour(Field* fd, CardStatus* att_status, CardStatus* def_status, unsigned att_dmg);
18
template<enum CardType::CardType def_cardtype>
19
void perform_bge_heroism(Field* fd, CardStatus* att_status, CardStatus* def_status, unsigned att_dmg);
20
void perform_poison(Field* fd, CardStatus* att_status, CardStatus* def_status);
21
void perform_corrosive(Field* fd, CardStatus* att_status, CardStatus* def_status);
22
void perform_mark(Field* fd, CardStatus* att_status, CardStatus* def_status);
23
void perform_berserk(Field* fd, CardStatus* att_status, CardStatus* def_status);
24
template<enum CardType::CardType def_cardtype>
25
void perform_counter(Field* fd, CardStatus* att_status, CardStatus* def_status);
26
void perform_hunt(Field* fd, CardStatus* att_status, CardStatus* def_status);
27
void perform_subdue(Field* fd, CardStatus* att_status, CardStatus* def_status);
28
bool check_and_perform_valor(Field* fd, CardStatus* src);
29
bool check_and_perform_bravery(Field* fd, CardStatus* src);
30
bool check_and_perform_early_enhance(Field* fd, CardStatus* src);
31
bool check_and_perform_later_enhance(Field* fd, CardStatus* src);
32
CardStatus* check_and_perform_summon(Field* fd, CardStatus* src);
33
//------------------------------------------------------------------------------
34
inline unsigned remove_absorption(Field* fd, CardStatus* status, unsigned dmg);
35
inline unsigned remove_absorption(CardStatus* status, unsigned dmg);
36
inline unsigned remove_disease(CardStatus* status, unsigned heal);
37
//------------------------------------------------------------------------------
38
inline bool has_attacked(CardStatus* c) { return (c->m_step == CardStep::attacked); }
16,669,708✔
39
inline bool is_alive(CardStatus* c) { return (c->m_hp > 0); }
16,210,773✔
40
inline bool can_act(CardStatus* c) { return is_alive(c) && !c->m_jammed; }
425,528,053✔
41
inline bool is_active(CardStatus* c) { return can_act(c) && (c->m_delay == 0); }
353,159,689✔
42
inline bool is_active_next_turn(CardStatus* c) { return can_act(c) && (c->m_delay <= 1); }
50,888,074✔
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)));}
254,460✔
44
// Can be healed / repaired
45
inline bool can_be_healed(CardStatus* c) { return is_alive(c) && (c->m_hp < c->max_hp()); }
36,121✔
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,138,480✔
60
{
61
    return status->description();
21,942,540✔
62
}
63
//------------------------------------------------------------------------------
64
template <typename CardsIter, typename Functor>
65
inline unsigned Field::make_selection_array(CardsIter first, CardsIter last, Functor f)
121,695,958✔
66
{
67
    this->selection_array.clear();
121,695,958✔
68
    for(auto c = first; c != last; ++c)
496,392,504✔
69
    {
70
        if (f(*c))
746,768,931✔
71
        {
72
            this->selection_array.push_back(*c);
192,086,950✔
73
        }
74
    }
75
    return(this->selection_array.size());
121,695,958✔
76
}
77
inline CardStatus * Field::left_assault(const CardStatus * status)
1,065,021✔
78
{
79
    return left_assault(status, 1);
4,014,327✔
80
}
81
inline CardStatus * Field::left_assault(const CardStatus * status, const unsigned n)
1,065,021✔
82
{
83
    auto & assaults = this->players[status->m_player]->assaults;
1,065,021✔
84
    if (status->m_index >= n)
1,065,021✔
85
    {
86
        auto left_status = &assaults[status->m_index - n];
815,831✔
87
        if (is_alive(left_status))
815,831✔
88
        {
89
            return left_status;
90
        }
91
    }
92
    return nullptr;
93
}
94
inline CardStatus * Field::right_assault(const CardStatus * status)
1,065,021✔
95
{
96
    return right_assault(status, 1);
3,982,151✔
97
}
98
inline CardStatus * Field::right_assault(const CardStatus * status, const unsigned n)
1,065,021✔
99
{
100
    auto & assaults = this->players[status->m_player]->assaults;
1,065,021✔
101
    if ((status->m_index + n) < assaults.size())
1,065,021✔
102
    {
103
        auto right_status = &assaults[status->m_index + n];
822,109✔
104
        if (is_alive(right_status))
822,109✔
105
        {
106
            return right_status;
107
        }
108
    }
109
    return nullptr;
110
}
111
inline void Field::print_selection_array()
×
112
{
113
#ifndef NDEBUG
114
    for(auto c: this->selection_array)
×
115
    {
116
        _DEBUG_MSG(2, "+ %s\n", status_description(c).c_str());
×
117
    }
118
#endif
119
}
×
120

121
//------------------------------------------------------------------------------
122
inline void Field::prepare_action()
102,072,380✔
123
{
124
    damaged_units_to_times.clear();
204,144,760✔
125
}
126

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

148
//------------------------------------------------------------------------------
149
inline unsigned CardStatus::skill_base_value(Skill::Skill skill_id) const
1,261,288,405✔
150
{
151
    return m_card->m_skill_value[skill_id + m_primary_skill_offset[skill_id]]
1,261,288,405✔
152
        + (skill_id == Skill::berserk ? m_enraged : 0)
×
153
        + (skill_id == Skill::counter ? m_entrapped : 0)
34,772,306✔
154
        ;
155
}
156
//------------------------------------------------------------------------------
157
inline unsigned CardStatus::skill(Skill::Skill skill_id) const
801,431,976✔
158
{
159
    return (is_activation_skill_with_x(skill_id)
801,431,976✔
160
            ? safe_minus(skill_base_value(skill_id), m_sabotaged)
161
            : skill_base_value(skill_id))
803,525,011✔
162
        + enhanced(skill_id);
803,525,011✔
163
}
164
//------------------------------------------------------------------------------
165
inline bool CardStatus::has_skill(Skill::Skill skill_id) const
457,930,495✔
166
{
167
    return skill_base_value(skill_id);
52,971✔
168
}
169
//------------------------------------------------------------------------------
170
inline unsigned CardStatus::enhanced(Skill::Skill skill_id) const
909,763,781✔
171
{
172
    return m_enhanced_value[skill_id + m_primary_skill_offset[skill_id]];
165,159✔
173
}
174
//------------------------------------------------------------------------------
175
inline unsigned CardStatus::protected_value() const
54,260,226✔
176
{
177
    return m_protected + m_protected_stasis;
54,260,226✔
178
}
179
//------------------------------------------------------------------------------
180
/**
181
 * @brief Maximum health.
182
 * This takes subduing into account by reducing the permanent health buffs by subdue.
183
 * 
184
 * @return unsigned maximum health.
185
 */
186
inline unsigned CardStatus::max_hp() const
101,126,691✔
187
{
188
    return (m_card->m_health + safe_minus(m_perm_health_buff, m_subdued));
97,417,234✔
189
}
190
/** @brief Increase of current health.
191
 *  This takes disease into account and removes as needed.
192
 *
193
 *  @param [in] value increase of health.
194
 *  @return applied increase of health.
195
 */
196
inline unsigned CardStatus::add_hp(unsigned value)
22,329,121✔
197
{
198
    value = remove_disease(this,value);
22,329,121✔
199
    return (m_hp = std::min(m_hp + value, max_hp()));
39,752,913✔
200
}
201
/** @brief Permanent increase of maximum health.
202
 *  This takes disease into account and removes as needed.
203
 *  The increase of the maximum health entails an increase of current health by the same amount.
204
 *
205
 *  @param [in] value increase of maximum health.
206
 *  @return applied increase of maximum health.
207
 */ 
208
inline unsigned CardStatus::ext_hp(unsigned value)
13,567,957✔
209
{
210
    value = remove_disease(this,value);
13,567,957✔
211
    m_perm_health_buff += value;
13,567,957✔
212
    // we can safely call add_hp without worring about the second call to remove_disease because
213
    // the first call will have already removed the disease or the value will be 0
214
    return add_hp(value);
13,567,957✔
215
}
216
//------------------------------------------------------------------------------
217
inline void CardStatus::set(const Card* card)
24,548,256✔
218
{
219
    this->set(*card);
24,548,256✔
220
}
221
//------------------------------------------------------------------------------
222
inline void CardStatus::set(const Card& card)
24,548,256✔
223
{
224
    m_card = &card;
24,548,256✔
225
    m_index = 0;
24,548,256✔
226
    m_action_index=0;
24,548,256✔
227
    m_player = 0;
24,548,256✔
228
    m_delay = card.m_delay;
24,548,256✔
229
    m_hp = card.m_health;
24,548,256✔
230
    m_absorption = 0;
24,548,256✔
231
    m_step = CardStep::none;
24,548,256✔
232
    m_perm_health_buff = 0;
24,548,256✔
233
    m_perm_attack_buff = 0;
24,548,256✔
234
    m_temp_attack_buff = 0;
24,548,256✔
235
    m_corroded_rate = 0;
24,548,256✔
236
    m_corroded_weakened = 0;
24,548,256✔
237
    m_subdued = 0;
24,548,256✔
238
    m_enfeebled = 0;
24,548,256✔
239
    m_evaded = 0;
24,548,256✔
240
    m_inhibited = 0;
24,548,256✔
241
    m_sabotaged = 0;
24,548,256✔
242
    m_jammed = false;
24,548,256✔
243
    m_overloaded = false;
24,548,256✔
244
    m_paybacked = 0;
24,548,256✔
245
    m_tributed = 0;
24,548,256✔
246
    m_poisoned = 0;
24,548,256✔
247
    m_protected = 0;
24,548,256✔
248
    m_protected_stasis = 0;
24,548,256✔
249
    m_enraged = 0;
24,548,256✔
250
    m_entrapped = 0;
24,548,256✔
251
    m_marked = 0;
24,548,256✔
252
    m_diseased = 0;
24,548,256✔
253
    m_rush_attempted = false;
24,548,256✔
254
    m_sundered = false;
24,548,256✔
255
    //APN
256
    m_summoned = false;
24,548,256✔
257

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

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

309
}
310
//------------------------------------------------------------------------------
311
const Card* card_by_id_safe(const Cards& cards, const unsigned card_id)
×
312
{
313
    const auto cardIter = cards.cards_by_id.find(card_id);
×
314
    if (cardIter == cards.cards_by_id.end()) assert(false);//"UnknownCard.id[" + to_string(card_id) + "]"); }
×
315
    return cardIter->second;
×
316
}
317
std::string card_name_by_id_safe(const Cards& cards, const unsigned card_id)
108,375✔
318
{
319
    const auto cardIter = cards.cards_by_id.find(card_id);
108,375✔
320
    if (cardIter == cards.cards_by_id.end()) { return "UnknownCard.id[" + tuo::to_string(card_id) + "]"; }
108,375✔
321
    return cardIter->second->m_name;
216,750✔
322
}
323
//------------------------------------------------------------------------------
324
std::string card_description(const Cards& cards, const Card* c)
2,513,343✔
325
{
326
    std::string desc;
2,513,343✔
327
    desc = c->m_name;
2,513,343✔
328
    switch(c->m_type)
2,513,343✔
329
    {
330
        case CardType::assault:
2,076,437✔
331
            desc += ": " + tuo::to_string(c->m_attack) + "/" + tuo::to_string(c->m_health) + "/" + tuo::to_string(c->m_delay);
10,382,185✔
332
            break;
2,076,437✔
333
        case CardType::structure:
436,342✔
334
            desc += ": " + tuo::to_string(c->m_health) + "/" + tuo::to_string(c->m_delay);
1,745,368✔
335
            break;
436,342✔
336
        case CardType::commander:
564✔
337
            desc += ": hp:" + tuo::to_string(c->m_health);
1,692✔
338
            break;
564✔
339
        case CardType::num_cardtypes:
×
340
            assert(false);
×
341
            break;
342
    }
343
    if (c->m_rarity >= 4) { desc += " " + rarity_names[c->m_rarity]; }
4,218,564✔
344
    if (c->m_faction != allfactions) { desc += " " + faction_names[c->m_faction]; }
5,026,686✔
345
    for (auto& skill: c->m_skills_on_play) { desc += ", " + skill_description(cards, skill, Skill::Trigger::play); }
2,646,201✔
346
    for (auto& skill: c->m_skills) { desc += ", " + skill_description(cards, skill, Skill::Trigger::activate); }
16,624,629✔
347
    //APN
348
    for (auto& skill: c->m_skills_on_attacked) { desc += ", " + skill_description(cards, skill, Skill::Trigger::attacked); }
3,077,129✔
349
    for (auto& skill: c->m_skills_on_death) { desc += ", " + skill_description(cards, skill, Skill::Trigger::death); }
2,688,279✔
350
    return desc;
2,513,343✔
351
}
×
352
//------------------------------------------------------------------------------
353
std::string CardStatus::description() const
55,138,480✔
354
{
355
    std::string desc = "P" + tuo::to_string(m_player) + " ";
165,415,440✔
356
    switch(m_card->m_type)
55,138,480✔
357
    {
358
        case CardType::commander: desc += "Commander "; break;
17,020,754✔
359
        case CardType::assault: desc += "Assault " + tuo::to_string(m_index) + " "; break;
126,948,124✔
360
        case CardType::structure: desc += "Structure " + tuo::to_string(m_index) + " "; break;
25,522,780✔
361
        case CardType::num_cardtypes: assert(false); break;
×
362
    }
363
    desc += "[" + m_card->m_name;
110,276,960✔
364
    switch (m_card->m_type)
55,138,480✔
365
    {
366
        case CardType::assault:
31,737,031✔
367
            desc += " att:[[" + tuo::to_string(m_card->m_attack) + "(base)";
126,948,124✔
368
            if (m_perm_attack_buff)
31,737,031✔
369
            {
370
                desc += "+[" + tuo::to_string(m_perm_attack_buff) + "(perm)";
28,841,688✔
371
                if (m_subdued) { desc += "-" + tuo::to_string(m_subdued) + "(subd)"; }
8,462,127✔
372
                desc += "]";
7,210,422✔
373
            }
374
            if (m_corroded_weakened) { desc += "-" + tuo::to_string(m_corroded_weakened) + "(corr)"; }
33,145,438✔
375
            desc += "]";
31,737,031✔
376
            if (m_temp_attack_buff) { desc += (m_temp_attack_buff > 0 ? "+" : "") + tuo::to_string(m_temp_attack_buff) + "(temp)"; }
48,782,776✔
377
            desc += "]=" + tuo::to_string(attack_power());
95,211,093✔
378
        case CardType::structure:
55,138,480✔
379
        case CardType::commander:
55,138,480✔
380
            desc += " hp:" + tuo::to_string(m_hp);
165,415,440✔
381
            if(m_absorption)desc += " absorb:" + tuo::to_string(m_absorption);
60,830,970✔
382
            break;
383
        case CardType::num_cardtypes:
×
384
            assert(false);
×
385
            break;
386
    }
387
    if (m_delay) { desc += " cd:" + tuo::to_string(m_delay); }
79,760,650✔
388
    // Status w/o value
389
    if (m_jammed) { desc += ", jammed"; }
55,138,480✔
390
    if (m_overloaded) { desc += ", overloaded"; }
55,138,480✔
391
    if (m_sundered) { desc += ", sundered"; }
55,138,480✔
392
    if (m_summoned) { desc+= ", summoned"; }
55,138,480✔
393
    // Status w/ value
394
    if (m_corroded_weakened || m_corroded_rate) { desc += ", corroded " + tuo::to_string(m_corroded_weakened) + " (rate: " + tuo::to_string(m_corroded_rate) + ")"; }
57,150,820✔
395
    if (m_subdued) { desc += ", subdued " + tuo::to_string(m_subdued); }
57,270,862✔
396
    if (m_enfeebled) { desc += ", enfeebled " + tuo::to_string(m_enfeebled); }
60,410,918✔
397
    if (m_inhibited) { desc += ", inhibited " + tuo::to_string(m_inhibited); }
58,140,004✔
398
    if (m_sabotaged) { desc += ", sabotaged " + tuo::to_string(m_sabotaged); }
58,321,498✔
399
    if (m_poisoned) { desc += ", poisoned " + tuo::to_string(m_poisoned); }
56,923,616✔
400
    if (m_protected) { desc += ", protected " + tuo::to_string(m_protected); }
76,154,314✔
401
    if (m_protected_stasis) { desc += ", stasis " + tuo::to_string(m_protected_stasis); }
59,943,590✔
402
    if (m_enraged) { desc += ", enraged " + tuo::to_string(m_enraged); }
61,301,090✔
403
    if (m_entrapped) { desc += ", entrapped " + tuo::to_string(m_entrapped); }
74,667,402✔
404
    if (m_marked) { desc += ", marked " + tuo::to_string(m_marked); }
55,440,104✔
405
    if (m_diseased) { desc += ", diseased " + tuo::to_string(m_diseased); }
56,217,738✔
406
    //    if(m_step != CardStep::none) { desc += ", Step " + to_string(static_cast<int>(m_step)); }
407
    //APN
408
    const Skill::Trigger s_triggers[] = { Skill::Trigger::play, Skill::Trigger::activate, Skill::Trigger::death , Skill::Trigger::attacked};
55,138,480✔
409
    for (const Skill::Trigger& trig: s_triggers)
275,692,400✔
410
    {
411
        std::vector<SkillSpec> card_skills(
220,553,920✔
412
                (trig == Skill::Trigger::play) ? m_card->m_skills_on_play :
220,553,920✔
413
                (trig == Skill::Trigger::activate) ? m_card->m_skills :
165,415,440✔
414
                //APN
415
                (trig == Skill::Trigger::attacked) ? m_card->m_skills_on_attacked :
110,276,960✔
416
                (trig == Skill::Trigger::death) ? m_card->m_skills_on_death :
55,138,480✔
417
                std::vector<SkillSpec>());
220,553,920✔
418

419
        // emulate Berserk/Counter by status Enraged/Entrapped unless such skills exist (only for normal skill triggering)
420
        if (trig == Skill::Trigger::activate)
220,553,920✔
421
        {
422
            if (m_enraged && !std::count_if(card_skills.begin(), card_skills.end(), [](const SkillSpec ss) { return (ss.id == Skill::berserk); }))
58,219,785✔
423
            {
424
                SkillSpec ss{Skill::berserk, m_enraged, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
3,041,561✔
425
                card_skills.emplace_back(ss);
3,041,561✔
426
            }
427
            if (m_entrapped && !std::count_if(card_skills.begin(), card_skills.end(), [](const SkillSpec ss) { return (ss.id == Skill::counter); }))
64,902,941✔
428
            {
429
                SkillSpec ss{Skill::counter, m_entrapped, allfactions, 0, 0, Skill::no_skill, Skill::no_skill, false, 0,};
9,559,450✔
430
                card_skills.emplace_back(ss);
9,559,450✔
431
            }
432
        }
433
        for (const auto& ss : card_skills)
397,837,697✔
434
        {
435
            std::string skill_desc;
177,283,777✔
436
            if (m_evolved_skill_offset[ss.id]) { skill_desc += "->" + skill_names[ss.id + m_evolved_skill_offset[ss.id]]; }
177,675,244✔
437
            if (m_enhanced_value[ss.id]) { skill_desc += " +" + tuo::to_string(m_enhanced_value[ss.id]); }
180,161,661✔
438
            if (!skill_desc.empty())
177,283,777✔
439
            {
440
                desc += ", " + (
3,660,818✔
441
                        (trig == Skill::Trigger::play) ? "(On Play)" :
3,660,818✔
442
                        (trig == Skill::Trigger::attacked) ? "(On Attacked)" :
1,830,409✔
443
                        (trig == Skill::Trigger::death) ? "(On Death)" :
1,830,409✔
444
                        std::string("")) + skill_names[ss.id] + skill_desc;
9,152,045✔
445
            }
446
        }
177,283,777✔
447
    }
220,553,920✔
448
    return desc + "]";
55,138,480✔
449
}
55,138,480✔
450
//------------------------------------------------------------------------------
451
void Hand::reset(std::mt19937& re)
2,673,052✔
452
{
453
    assaults.reset();
2,673,052✔
454
    structures.reset();
2,673,052✔
455
    deck->shuffle(re);
2,673,052✔
456
    commander.set(deck->shuffled_commander);
2,673,052✔
457
    total_cards_destroyed = 0;
2,673,052✔
458
    total_nonsummon_cards_destroyed = 0;
2,673,052✔
459
    if (commander.skill(Skill::stasis))
2,673,052✔
460
    {
461
        stasis_faction_bitmap |= (1u << commander.m_card->m_faction);
×
462
    }
463
}
2,673,052✔
464

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

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

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

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

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

501
//------------------------------------------------------------------------------
502
inline void resolve_scavenge(Storage<CardStatus>& store)
38,516,608✔
503
{
504
    for(auto status : store.m_indirect)
148,088,443✔
505
    {
506
        if(!is_alive(status))continue;
109,571,835✔
507
        unsigned scavenge_value = status->skill(Skill::scavenge);
95,882,906✔
508
        if(!scavenge_value)continue;
95,882,906✔
509

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

547
        if ((status->m_card->m_type == CardType::assault) && (!skip_all_except_summon))
9,629,152✔
548
        {
549
            // Skill: Avenge
550
            const unsigned host_idx = status->m_index;
8,638,879✔
551
            unsigned from_idx, till_idx;
8,638,879✔
552
            //scan all assaults for Avenge
553
            from_idx = 0;
8,638,879✔
554
            till_idx = assaults.size() - 1;
8,638,879✔
555
            for (; from_idx <= till_idx; ++ from_idx)
34,321,007✔
556
            {
557
                if (from_idx == host_idx) { continue; }
25,682,128✔
558
                CardStatus* adj_status = &assaults[from_idx];
17,043,249✔
559
                if (!is_alive(adj_status)) { continue; }
17,043,249✔
560
                unsigned avenge_value = adj_status->skill(Skill::avenge);
14,886,467✔
561
                if (!avenge_value) { continue; }
14,886,467✔
562

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

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

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

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

656
//------------------------------------------------------------------------------
657
void(*skill_table[Skill::num_skills])(Field*, CardStatus* src, const SkillSpec&);
658
void resolve_skill(Field* fd)
179,616,756✔
659
{
660
    while (!fd->skill_queue.empty())
290,744,363✔
661
    {
662
        auto skill_instance(fd->skill_queue.front());
111,127,607✔
663
        auto& status(std::get<0>(skill_instance));
111,127,607✔
664
        const auto& ss(std::get<1>(skill_instance));
111,127,607✔
665
        fd->skill_queue.pop_front();
111,127,607✔
666
        if (__builtin_expect(status->m_card->m_skill_trigger[ss.id] == Skill::Trigger::activate, true))
111,127,607✔
667
        {
668
            if (!is_alive(status))
106,849,442✔
669
            {
670
                _DEBUG_MSG(2, "%s failed to %s because it is dead.\n",
3,962✔
671
                        status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
3,962✔
672
                continue;
3,089,288✔
673
            }
3,962✔
674
            if (status->m_jammed)
106,845,480✔
675
            {
676
                _DEBUG_MSG(2, "%s failed to %s because it is Jammed.\n",
1,322✔
677
                        status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
1,322✔
678
                continue;
1,322✔
679
            }
1,322✔
680
        }
681

682
        // is summon? (non-activation skill)
683
        if (ss.id == Skill::summon)
111,122,323✔
684
        {
685
            check_and_perform_summon(fd, status);
3,004,486✔
686
            continue;
3,004,486✔
687
        }
688
        _DEBUG_ASSERT(is_activation_skill(ss.id) || ss.id == Skill::enhance); // enhance is no trigger, but  queues the skill
108,210,373✔
689

690
        SkillSpec modified_s = ss;
108,117,837✔
691

692
        // apply evolve
693
        signed evolved_offset = status->m_evolved_skill_offset[modified_s.id];
108,117,837✔
694
        if (evolved_offset != 0)
108,117,837✔
695
        { modified_s = apply_evolve(modified_s, evolved_offset); }
258,428✔
696

697
        // apply sabotage (only for X-based activation skills)
698
        unsigned sabotaged_value = status->m_sabotaged;
108,117,837✔
699
        if ((sabotaged_value > 0) && is_activation_skill_with_x(modified_s.id))
108,117,837✔
700
        { modified_s = apply_sabotage(modified_s, sabotaged_value); }
236,788✔
701

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

707
        // perform skill (if it is still applicable)
708
        if (is_activation_skill_with_x(modified_s.id) && !modified_s.x)
108,117,837✔
709
        {
710
            _DEBUG_MSG(2, "%s failed to %s because its X value is zeroed (sabotaged).\n",
79,518✔
711
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
79,518✔
712
            continue;
79,518✔
713
        }
79,518✔
714
        else { skill_table[modified_s.id](fd, status, modified_s); }
108,038,319✔
715
    }
716
}
179,616,756✔
717

718
void apply_corrosion(CardStatus * status)
21,242,180✔
719
{
720
      if (status->m_corroded_rate)
21,242,180✔
721
      {
722
        unsigned v = std::min(status->m_corroded_rate, status->attack_power());
101,113✔
723
        unsigned corrosion = std::min(v, status->m_card->m_attack
202,226✔
724
                + status->m_perm_attack_buff - status->m_corroded_weakened);
101,113✔
725
        _DEBUG_MSG(1, "%s loses Attack by %u (+corrosion %u).\n", status_description(status).c_str(), v, corrosion);
101,113✔
726
        status->m_corroded_weakened += corrosion;
101,113✔
727
      }
728
}
21,242,180✔
729
void remove_corrosion(CardStatus * status)
2,801,306✔
730
{
731
       if (status->m_corroded_rate)
2,801,306✔
732
       {
733
           _DEBUG_MSG(1, "%s loses Status corroded.\n", status_description(status).c_str());
4,051✔
734
           status->m_corroded_rate = 0;
4,051✔
735
           status->m_corroded_weakened = 0;
4,051✔
736
       }
737
}
2,801,306✔
738
//------------------------------------------------------------------------------
739
bool attack_phase(Field* fd);
740

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

748
    template <enum CardType::CardType type>
749
void evaluate_skills(Field* fd, CardStatus* status, const std::vector<SkillSpec>& skills, bool* attacked=nullptr)
60,336,616✔
750
{
751
    _DEBUG_ASSERT(status);
60,336,616✔
752
    unsigned num_actions(1);
753
    for (unsigned action_index(0); action_index < num_actions; ++ action_index)
125,078,847✔
754
    {
755
        status->m_action_index = action_index;
66,058,037✔
756
        fd->prepare_action();
66,058,037✔
757
        _DEBUG_ASSERT(fd->skill_queue.size() == 0);
66,058,037✔
758
        for (auto & ss: skills)
254,925,419✔
759
        {
760
            if (!is_activation_skill(ss.id)) { continue; }
270,122,227✔
761
            if (status->m_skill_cd[ss.id] > 0) { continue; }
107,612,537✔
762
            _DEBUG_MSG(2, "Evaluating %s skill %s\n",
106,611,270✔
763
                    status_description(status).c_str(), skill_description(fd->cards, ss).c_str());
764
            fd->skill_queue.emplace_back(status, ss);
106,611,270✔
765
            resolve_skill(fd);
106,611,270✔
766
        }
767
        if (type == CardType::assault)
768
        {
769
            // Attack
770
            if (can_act(status))
25,359,292✔
771
            {
772
                if (attack_phase(fd))
25,170,103✔
773
                {
774
                    *attacked = true;
22,557,986✔
775
                    if (__builtin_expect(fd->end, false)) { break; }
22,557,986✔
776
                    //Apply corrosion
777
                    apply_corrosion(status);
21,242,180✔
778
                }
779
                else
780
                {
781
                  // Remove Corrosion
782
                  remove_corrosion(status);
2,612,117✔
783
                }
784
            }
785
            else
786
            {
787
                _DEBUG_MSG(2, "%s cannot take attack.\n", status_description(status).c_str());
189,189✔
788
                // Remove Corrosion
789
                remove_corrosion(status);
189,189✔
790
            }
791
        }
792
        fd->finalize_action();
64,742,231✔
793
        // Flurry
794
        if (can_act(status) && status->has_skill(Skill::flurry) && (status->m_skill_cd[Skill::flurry] == 0))
131,078,225✔
795
        {
796
#ifndef NQUEST
797
            if (status->m_player == 0)
798
            {
799
                fd->inc_counter(QuestType::skill_use, Skill::flurry);
800
            }
801
#endif
802
            _DEBUG_MSG(1, "%s activates Flurry x %d\n",
2,462,049✔
803
                    status_description(status).c_str(), status->skill_base_value(Skill::flurry));
804
            num_actions += status->skill_base_value(Skill::flurry);
2,330,923✔
805
            for (const auto & ss : skills)
9,320,471✔
806
            {
807
                Skill::Skill evolved_skill_id = static_cast<Skill::Skill>(ss.id + status->m_evolved_skill_offset[ss.id]);
6,989,548✔
808
                if (evolved_skill_id == Skill::flurry)
6,989,548✔
809
                {
810
                    status->m_skill_cd[ss.id] = ss.c;
2,330,923✔
811
                }
812
            }
813
        }
814
    }
815
}
60,336,616✔
816

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

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

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

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

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

863
            //refresh/init absorb
864
            if(status->has_skill(Skill::absorb))
21,875,204✔
865
            {
866
                status->m_absorption = status->skill_base_value(Skill::absorb);
1,261,292✔
867
            }
868

869

870
            // 1. Evaluate skill Allegiance & count assaults with same faction (structures will be counted later)
871
            // 2. Passive BGE Cold Sleep
872
            for (CardStatus* status_i : fd->players[status->m_player]->assaults.m_indirect)
85,816,094✔
873
            {
874
                if (status_i == status || !is_alive(status_i)) { continue; } // except itself
63,940,890✔
875
                //std::cout << status_description(status_i).c_str();
876
                _DEBUG_ASSERT(is_alive(status_i));
46,444,756✔
877
                if (bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction))
46,444,756✔
878
                {
879
                    ++ same_faction_cards_count;
20,087,478✔
880
                    unsigned allegiance_value = status_i->skill(Skill::allegiance);
20,087,478✔
881
                    if (__builtin_expect(allegiance_value, false) && !status->m_summoned)
20,087,478✔
882
                    {
883
                        _DEBUG_MSG(1, "%s activates Allegiance %u\n", status_description(status_i).c_str(), allegiance_value);
1,399,408✔
884
                        if (! status_i->m_sundered)
1,311,156✔
885
                        { status_i->m_perm_attack_buff += allegiance_value; }
1,262,940✔
886
                        status_i->ext_hp(allegiance_value);
1,311,156✔
887
                    }
888
                }
889
                if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::coldsleep], false)
46,444,756✔
890
                        && status_i->m_protected_stasis && can_be_healed(status_i))
110,385,646✔
891
                {
892
                    unsigned bge_value = (status_i->m_protected_stasis + 1) / 2;
2,196✔
893
                    _DEBUG_MSG(1, "Cold Sleep: %s heals itself for %u\n", status_description(status_i).c_str(), bge_value);
6,588✔
894
                    status_i->add_hp(bge_value);
2,196✔
895
                }
896
            }
897

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

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

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

937
            // summarize stasis when:
938
            //  1. Passive BGE Megamorphosis is enabled
939
            //  2. current faction is marked for it
940
            if ((card->m_delay > 0) && (card->m_type == CardType::assault)
20,497,080✔
941
                    && __builtin_expect(bge_megamorphosis || (fd->players[status->m_player]->stasis_faction_bitmap & played_faction_mask), false))
38,047,459✔
942
            {
943
                unsigned stacked_stasis = (bge_megamorphosis || (fd->players[status->m_player]->commander.m_card->m_faction == card->m_faction))
8,055,490✔
944
                    ? fd->players[status->m_player]->commander.skill(Skill::stasis)
15,104,323✔
945
                    : 0u;
946
#ifndef NDEBUG
947
                if (stacked_stasis > 0)
7,048,833✔
948
                {
949
                    _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
×
950
                            faction_names[card->m_faction].c_str(), stacked_stasis,
951
                            status_description(&fd->players[status->m_player]->commander).c_str(), stacked_stasis);
952
                }
953
#endif
954
                for (CardStatus * status_i : fd->players[status->m_player]->structures.m_indirect)
15,203,625✔
955
                {
956
                    if ((bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)) && is_alive(status_i))
7,105,067✔
957
                    {
958
                        stacked_stasis += status_i->skill(Skill::stasis);
1,876,268✔
959
#ifndef NDEBUG
960
                        if (status_i->skill(Skill::stasis) > 0)
1,876,268✔
961
                        {
962
                            _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
7,105,067✔
963
                                    faction_names[card->m_faction].c_str(), status_i->skill(Skill::stasis),
964
                                    status_description(status_i).c_str(), stacked_stasis);
965
                        }
966
#endif
967
                    }
968
                }
969
                for (CardStatus * status_i : fd->players[status->m_player]->assaults.m_indirect)
32,127,617✔
970
                {
971
                    if ((bge_megamorphosis || (status_i->m_card->m_faction == card->m_faction)) && is_alive(status_i))
24,029,059✔
972
                    {
973
                        stacked_stasis += status_i->skill(Skill::stasis);
18,954,887✔
974
#ifndef NDEBUG
975
                        if (status_i->skill(Skill::stasis) > 0)
18,954,887✔
976
                        {
977
                            _DEBUG_MSG(2, "+ Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
8,159,163✔
978
                                    faction_names[card->m_faction].c_str(), status_i->skill(Skill::stasis),
979
                                    status_description(status_i).c_str(), stacked_stasis);
980
                        }
981
#endif
982
                        if (__builtin_expect(fd->bg_effects[status->m_player][PassiveBGE::temporalbacklash] && status_i->skill(Skill::counter), false))
18,954,887✔
983
                        {
984
                            stacked_stasis += (status_i->skill(Skill::counter) + 1) / 2;
3,067✔
985
#ifndef NDEBUG
986
                            _DEBUG_MSG(2, "Temporal Backlash: + Stasis [%s]: stacks +%u stasis protection from %s (total stacked: %u)\n",
24,032,126✔
987
                                    faction_names[card->m_faction].c_str(), (status_i->skill(Skill::counter) + 1) / 2,
988
                                    status_description(status_i).c_str(), stacked_stasis);
989
#endif
990
                        }
991
                    }
992
                }
993
                status->m_protected_stasis = stacked_stasis;
8,098,558✔
994
#ifndef NDEBUG
995
                if (stacked_stasis > 0)
8,098,558✔
996
                {
997
                    _DEBUG_MSG(1, "%s gains %u stasis protection\n",
6,720,159✔
998
                            status_description(status).c_str(), stacked_stasis);
999
                }
1000
#endif
1001
                // no more stasis for current faction: do unmark (if no Passive BGE Megamorphosis)
1002
                if (__builtin_expect(!bge_megamorphosis, true) && __builtin_expect(!stacked_stasis, false))
8,098,558✔
1003
                {
1004
                    fd->players[status->m_player]->stasis_faction_bitmap &= ~played_faction_mask;
1,612,866✔
1005
                    _DEBUG_MSG(1, "- Stasis [%s]: no more units with stasis from %s\n",
1,622,268✔
1006
                            faction_names[card->m_faction].c_str(),status_description(&fd->players[status->m_player]->commander).c_str());
1007
                }
1008

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

1022

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

1039
            return status;
21,875,204✔
1040
        }
1041

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

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

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

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

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

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

1103
    template<>
1104
inline bool skill_check<Skill::heal>(Field* fd, CardStatus* c, CardStatus* ref)
73,874,718✔
1105
{
1106
    return can_be_healed(c);
38,545✔
1107
}
1108

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

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

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

1127
    template<>
1128
inline bool skill_check<Skill::jam>(Field* fd, CardStatus* c, CardStatus* ref)
12,140,725✔
1129
{
1130
    if(__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::ironwill], false) && c->has_skill(Skill::armor))return false;
12,140,725✔
1131
    // active player performs Jam
1132
    if (fd->tapi == ref->m_player)
12,138,494✔
1133
    { return is_active_next_turn(c); }
12,110,234✔
1134

1135

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

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

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

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

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

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

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

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

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

1188
    template<>
1189
inline bool skill_check<Skill::disease>(Field* fd, CardStatus* c, CardStatus* ref)
131,609✔
1190
{
1191
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
131,609✔
1192
}
1193
    template<>
1194
inline bool skill_check<Skill::inhibit>(Field* fd, CardStatus* c, CardStatus* ref)
2,896,421✔
1195
{
1196
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
2,896,421✔
1197
}
1198
    template<>
1199
inline bool skill_check<Skill::sabotage>(Field* fd, CardStatus* c, CardStatus* ref)
515,678✔
1200
{
1201
    return is_alive(c) && (ref->m_card->m_type == CardType::assault);
515,678✔
1202
}
1203
inline unsigned remove_disease(CardStatus* status, unsigned heal)
35,897,078✔
1204
{
1205
    unsigned remaining_heal(heal);
35,897,078✔
1206
    if(__builtin_expect(status->m_diseased == 0,true))
35,897,078✔
1207
    {
1208
        //skip
1209
    }
1210
    else if (heal > status->m_diseased)
55,831✔
1211
    {
1212
        _DEBUG_MSG(1, "%s disease-blocked %u heal\n", status_description(status).c_str(), status->m_diseased);
14,681✔
1213
        remaining_heal = heal - status->m_diseased;
14,681✔
1214
        status->m_diseased = 0;
14,681✔
1215
    }
1216
    else
1217
    {
1218
        _DEBUG_MSG(1, "%s disease-blocked %u heal\n", status_description(status).c_str(), heal);
41,150✔
1219
        status->m_diseased -= heal;
41,150✔
1220
        remaining_heal = 0;
41,150✔
1221
    }
1222
    return remaining_heal;
35,897,078✔
1223
}
1224

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

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

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

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

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

1316
void cooldown_skills(CardStatus * status)
49,483,288✔
1317
{
1318
    for (const auto & ss : status->m_card->m_skills)
191,295,391✔
1319
    {
1320
        if (status->m_skill_cd[ss.id] > 0)
141,812,103✔
1321
        {
1322
            _DEBUG_MSG(2, "%s reduces timer (%u) of skill %s\n",
3,486,359✔
1323
                    status_description(status).c_str(), status->m_skill_cd[ss.id], skill_names[ss.id].c_str());
3,486,359✔
1324
            -- status->m_skill_cd[ss.id];
3,486,359✔
1325
        }
1326
    }
1327
}
49,483,288✔
1328
/**
1329
 * Handle:
1330
 * Absorb, (Activation)Summon, Bravery, (Initial)Valor, Inhibit, Sabotage, Disease, Enhance, (Cooldown/Every X) Reductions
1331
 * 
1332
 * Does not handle these skills for newly summoned units ( i.e. valor, barrier)
1333
 **/
1334
void turn_start_phase_update(Field*fd, CardStatus * status)
59,826,363✔
1335
{
1336
            //apply Absorb + Triggered\{Valor} Enhances
1337
            check_and_perform_early_enhance(fd,status);
119,652,726✔
1338
            if(fd->fixes[Fix::enhance_early])check_and_perform_later_enhance(fd,status);
59,826,363✔
1339
            //refresh absorb
1340
            if(status->has_skill(Skill::absorb))
59,826,363✔
1341
            {
1342
                status->m_absorption = status->skill_base_value(Skill::absorb);
4,059,348✔
1343
            }
1344
            if(__builtin_expect(fd->fixes[Fix::barrier_each_turn],true) && status->has_skill(Skill::barrier)){
59,826,363✔
1345
                _DEBUG_MSG(1,"%s gets barrier protection %u per turn\n",status_description(status).c_str(),status->skill(Skill::barrier));
1,499,527✔
1346
                status->m_protected += status->skill(Skill::barrier);
1,499,527✔
1347
            }
1348
            check_and_perform_bravery(fd,status);
59,826,363✔
1349
            if (status->m_delay > 0)
59,826,363✔
1350
            {
1351
                _DEBUG_MSG(1, "%s reduces its timer\n", status_description(status).c_str());
29,229,084✔
1352
                --status->m_delay;
29,229,084✔
1353
                if (status->m_delay == 0)
29,229,084✔
1354
                {
1355
                    check_and_perform_valor(fd, status);
12,767,919✔
1356
                    if (status->m_card->m_skill_trigger[Skill::summon] == Skill::Trigger::activate)
12,767,919✔
1357
                    {
1358
                        check_and_perform_summon(fd, status);
10,446,131✔
1359
                    }
1360
                }
1361
            }
1362
            else
1363
            {
1364
                cooldown_skills(status);
30,597,279✔
1365
            }
1366
}
59,826,363✔
1367

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

1376
    //Perform early enhance for commander
1377
    check_and_perform_early_enhance(fd,&fd->tap->commander);
37,772,018✔
1378
    if(fd->fixes[Fix::enhance_early])check_and_perform_later_enhance(fd,&fd->tap->commander);
18,886,009✔
1379

1380
    // Active player's structure cards:
1381
    // update index
1382
    // reduce delay; reduce skill cooldown
1383
    {
18,886,009✔
1384
        auto& structures(fd->tap->structures);
18,886,009✔
1385
        for(unsigned index(0); index < structures.size(); ++index)
41,167,448✔
1386
        {
1387
            CardStatus * status = &structures[index];
22,281,439✔
1388
            status->m_index = index;
22,281,439✔
1389
            turn_start_phase_update(fd,status);
22,281,439✔
1390
        }
1391
    }
1392
    // Active player's assault cards:
1393
    // update index
1394
    // reduce delay; reduce skill cooldown
1395
    {
1396
        for(unsigned index(0); index < end; ++index)
56,430,933✔
1397
        {
1398
            CardStatus * status = &assaults[index];
37,544,924✔
1399
            status->m_index = index;
37,544,924✔
1400
            turn_start_phase_update(fd,status);
37,544,924✔
1401
        }
1402
    }
1403
    // Defending player's structure cards:
1404
    // update index
1405
    {
18,886,009✔
1406
        auto& structures(fd->tip->structures);
18,886,009✔
1407
        for(unsigned index(0), end(structures.size()); index < end; ++index)
41,996,215✔
1408
        {
1409
            CardStatus& status(structures[index]);
23,110,206✔
1410
            status.m_index = index;
23,110,206✔
1411
        }
1412
    }
1413
    // Defending player's assault cards:
1414
    // update index
1415
    {
18,886,009✔
1416
        auto& assaults(fd->tip->assaults);
18,886,009✔
1417
        for(unsigned index(0), end(assaults.size()); index < end; ++index)
64,953,650✔
1418
        {
1419
            CardStatus& status(assaults[index]);
46,067,641✔
1420
            status.m_index = index;
46,067,641✔
1421
        }
1422
    }
1423
}
18,886,009✔
1424

1425
void turn_end_phase(Field* fd)
18,886,009✔
1426
{
1427
    // Inactive player's assault cards:
1428
    {
18,886,009✔
1429
        auto& assaults(fd->tip->assaults);
18,886,009✔
1430
        for(unsigned index(0), end(assaults.size()); index < end; ++ index)
65,040,339✔
1431
        {
1432
            CardStatus& status(assaults[index]);
46,154,330✔
1433
            if (status.m_hp <= 0)
46,154,330✔
1434
            {
1435
                continue;
7,794,917✔
1436
            }
1437
            status.m_enfeebled = 0;
38,359,413✔
1438
            status.m_protected = 0;
38,359,413✔
1439
            std::memset(status.m_primary_skill_offset, 0, sizeof status.m_primary_skill_offset);
38,359,413✔
1440
            std::memset(status.m_evolved_skill_offset, 0, sizeof status.m_evolved_skill_offset);
38,359,413✔
1441
            std::memset(status.m_enhanced_value, 0, sizeof status.m_enhanced_value);
38,359,413✔
1442
            status.m_evaded = 0;  // so far only useful in Inactive turn
38,359,413✔
1443
            status.m_paybacked = 0;  // ditto
38,359,413✔
1444
            status.m_entrapped = 0;
38,359,413✔
1445
        }
1446
    }
1447
    // Inactive player's structure cards:
1448
    {
18,886,009✔
1449
        auto& structures(fd->tip->structures);
18,886,009✔
1450
        for(unsigned index(0), end(structures.size()); index < end; ++ index)
41,999,757✔
1451
        {
1452
            CardStatus& status(structures[index]);
23,113,748✔
1453
            if (status.m_hp <= 0)
23,113,748✔
1454
            {
1455
                continue;
989,887✔
1456
            }
1457
            // reset the structure's (barrier) protect
1458
            status.m_protected = 0;
22,123,861✔
1459
            status.m_evaded = 0;  // so far only useful in Inactive turn
22,123,861✔
1460
        }
1461
    }
1462

1463
    // Active player's assault cards:
1464
    {
18,886,009✔
1465
        auto& assaults(fd->tap->assaults);
18,886,009✔
1466
        for(unsigned index(0), end(assaults.size()); index < end; ++ index)
73,757,708✔
1467
        {
1468
            CardStatus& status(assaults[index]);
54,871,699✔
1469
            if (status.m_hp <= 0)
54,871,699✔
1470
            {
1471
                continue;
823,709✔
1472
            }
1473
            unsigned refresh_value = status.skill(Skill::refresh) + (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::crackdown],false)?(status.skill(Skill::subdue)+1)/2:0); //BGE: crackdown refresh+=subdue/2
54,047,990✔
1474

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

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

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

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

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

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

1567

1568
inline bool alive_assault(Storage<CardStatus>& assaults, unsigned index)
22,557,986✔
1569
{
1570
    return(assaults.size() > index && is_alive(&assaults[index]));
16,210,773✔
1571
}
1572

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

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

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

1634

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

1642
            if ( __builtin_expect(fd->fixes[Fix::subdue_before_attack],true)) {
22,507,698✔
1643
                perform_subdue(fd, att_status, def_status);
22,507,698✔
1644
                if(att_status->attack_power() == 0) // MK - 05/01/2023 6:58 AM: "Previously, when an attack was made, it checked if it had 0 attack and canceled. Now, after that check, it applies Subdue and checks again if it has 0 attack, and then cancels the rest of the attack.Previously, when an attack was made, it checked if it had 0 attack and canceled. Now, after that check, it applies Subdue and checks again if it has 0 attack, and then cancels the rest of the attack."
22,507,698✔
1645
                { 
1646
                    return 0; 
1647
                }
1648
            }
1649
            
1650
            // APN Bug fix for subdue not reducing attack damage
1651
            // https://github.com/APN-Pucky/tyrant_optimize/issues/79
1652
            unsigned pre_modifier_dmg = att_status->attack_power();
22,503,708✔
1653

1654
            modify_attack_damage<def_cardtype>(pre_modifier_dmg);
22,503,708✔
1655

1656
            if(att_dmg) {
22,503,708✔
1657
                // Deal the attack damage
1658
                attack_damage<def_cardtype>();
18,712,258✔
1659

1660
                // Game Over -> return
1661
                if (__builtin_expect(fd->end, false)) { return att_dmg; }
18,712,258✔
1662
                damage_dependant_pre_oa<def_cardtype>();
10,926,901✔
1663
            }
1664
            // on attacked does also trigger if the attack damage is blocked to zero
1665
            on_attacked<def_cardtype>();
21,187,902✔
1666

1667
            // only if alive attacker
1668
            if (is_alive(att_status) && (__builtin_expect(fd->fixes[Fix::counter_without_damage],true) || att_dmg) )
21,187,902✔
1669
            {
1670
                perform_counter<def_cardtype>(fd, att_status, def_status);    
21,175,896✔
1671
            }
1672
            if (is_alive(att_status) && (__builtin_expect(fd->fixes[Fix::corrosive_protect_armor],true) ||att_dmg )) // MK - 05/01/2023 6:58 AM: "Corrosive will apply to an attacker even if the attack did 0 damage, just like new Counter"
21,187,902✔
1673
            {
1674
                perform_corrosive(fd, att_status, def_status);
20,464,097✔
1675
            }
1676
            if (is_alive(att_status) && att_dmg)
21,187,902✔
1677
            {
1678
                // Bug fix? 2023-04-03 leech after counter but before drain/berserk
1679
                // Skill: Leech
1680
                do_leech<def_cardtype>();
10,770,728✔
1681
            }
1682

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

1687
            if (is_alive(att_status) && att_dmg) {
21,187,902✔
1688
                perform_berserk(fd, att_status, def_status);
16,735,140✔
1689
            }
1690
            if (is_alive(att_status) )  {
21,187,902✔
1691
                perform_poison(fd, att_status, def_status);
20,464,097✔
1692
            }
1693

1694
            if (is_alive(att_status) && att_dmg) {
21,187,902✔
1695
                perform_mark(fd, att_status, def_status);
16,735,140✔
1696

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

1700
                if ( __builtin_expect(!fd->fixes[Fix::subdue_before_attack],false)) {
16,735,140✔
1701
                    perform_subdue(fd, att_status, def_status);
×
1702
                }
1703
            }
1704
            // perform hunt
1705
            perform_hunt(fd, att_status, def_status);
21,187,902✔
1706

1707
            return att_dmg;
21,187,902✔
1708
        }
1709

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

1732
            // Enhance damage (if additional damage isn't prevented)
1733
            if (! att_status->m_sundered)
22,503,708✔
1734
            {
1735
                // Skill: Mark
1736
                unsigned marked_value = def_status->m_marked;
21,228,660✔
1737
                if(marked_value)
21,228,660✔
1738
                {
1739
#ifndef NDEBUG
1740
                    if (debug_print > 0) { desc += "+" + tuo::to_string(marked_value) + "(mark)"; }
47,155✔
1741
#endif
1742
                    att_dmg += marked_value;
11,956✔
1743
                }
1744
                // Skill: Legion
1745
                unsigned legion_base = att_status->skill(Skill::legion);
21,228,660✔
1746
                if (__builtin_expect(legion_base, false))
21,228,660✔
1747
                {
1748
                    unsigned itr_idx, till_idx;
1749
                    bool bge_megamorphosis = fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis] && !fd->fixes[Fix::legion_under_mega];
3,082,817✔
1750
                    //scan all assaults for Global Legion
1751
                    itr_idx = 0;
3,082,817✔
1752
                    till_idx = att_assaults.size() - 1;
3,082,817✔
1753
                    for (; itr_idx <= till_idx; ++ itr_idx)
20,354,410✔
1754
                    {
1755
                        if(itr_idx == att_status->m_index)continue; //legion doesn't count itself, unlike coalition
17,271,593✔
1756
                        legion_value += is_alive(&att_assaults[itr_idx])
28,354,124✔
1757
                          && (bge_megamorphosis || (att_assaults[itr_idx].m_card->m_faction == att_status->m_card->m_faction));
14,188,776✔
1758
                    }
1759
                    if (legion_value)
3,082,817✔
1760
                    {
1761
                        legion_value *= legion_base;
2,917,086✔
1762
#ifndef NDEBUG
1763
                        if (debug_print > 0) { desc += "+" + tuo::to_string(legion_value) + "(legion)"; }
3,181,581✔
1764
#endif
1765
                        att_dmg += legion_value;
2,917,086✔
1766
                    }
1767
                }
1768

1769
                // Skill: Coalition
1770
                unsigned coalition_base = att_status->skill(Skill::coalition);
21,228,660✔
1771
                if (__builtin_expect(coalition_base, false))
21,228,660✔
1772
                {
1773
                    uint8_t factions_bitmap = 0;
1,186,164✔
1774
                    for (CardStatus * status : att_assaults.m_indirect)
6,299,990✔
1775
                    {
1776
                        if (! is_alive(status)) { continue; }
5,113,826✔
1777
                        factions_bitmap |= (1 << (status->m_card->m_faction));
5,107,581✔
1778
                    }
1779
                    _DEBUG_ASSERT(factions_bitmap);
1,186,164✔
1780
                    unsigned uniq_factions = byte_bits_count(factions_bitmap);
1,186,164✔
1781
                    coalition_value = coalition_base * uniq_factions;
1,186,164✔
1782
#ifndef NDEBUG
1783
                    if (debug_print > 0) { desc += "+" + tuo::to_string(coalition_value) + "(coalition/x" + tuo::to_string(uniq_factions) + ")"; }
1,722,828✔
1784
#endif
1785
                    att_dmg += coalition_value;
1,186,164✔
1786
                }
1787

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

1798
                // Skill: Venom
1799
                unsigned venom_value = att_status->skill(Skill::venom);
21,228,660✔
1800
                if (venom_value > 0 && def_status->m_poisoned > 0)
21,228,660✔
1801
                {
1802
#ifndef NDEBUG
1803
                    if (debug_print > 0) { desc += "+" + tuo::to_string(venom_value) + "(venom)"; }
76,274✔
1804
#endif
1805
                    att_dmg += venom_value;
25,205✔
1806
                }
1807

1808
                // Passive BGE: Bloodlust
1809
                if (fd->bloodlust_value > 0)
21,228,660✔
1810
                {
1811
#ifndef NDEBUG
1812
                    if (debug_print > 0) { desc += "+" + tuo::to_string(fd->bloodlust_value) + "(bloodlust)"; }
207,172✔
1813
#endif
1814
                    att_dmg += fd->bloodlust_value;
51,793✔
1815
                }
1816

1817
                // State: Enfeebled
1818
                if (def_status->m_enfeebled > 0)
21,228,660✔
1819
                {
1820
#ifndef NDEBUG
1821
                    if (debug_print > 0) { desc += "+" + tuo::to_string(def_status->m_enfeebled) + "(enfeebled)"; }
2,889,594✔
1822
#endif
1823
                    att_dmg += def_status->m_enfeebled;
1,971,543✔
1824
                }
1825
            }
1826
            // prevent damage
1827
#ifndef NDEBUG
1828
            std::string reduced_desc;
22,503,708✔
1829
#endif
1830
            unsigned reduced_dmg(0); // damage reduced by armor, protect and in turn again canceled by pierce, etc.
22,503,708✔
1831
            unsigned armor_value = 0;
22,503,708✔
1832
            // Armor
1833
            if (def_status->m_card->m_type == CardType::assault) {
22,503,708✔
1834
                // Passive BGE: Fortification (adj step -> 1 (1 left, host, 1 right)
1835
                unsigned adj_size = (unsigned)(fd->bg_effects[fd->tapi][PassiveBGE::fortification]);
14,718,351✔
1836
                unsigned host_idx = def_status->m_index;
14,718,351✔
1837
                unsigned from_idx = safe_minus(host_idx, adj_size);
14,718,351✔
1838
                unsigned till_idx = std::min(host_idx + adj_size, safe_minus(def_assaults.size(), 1));
27,571,778✔
1839
                for (; from_idx <= till_idx; ++ from_idx)
29,464,053✔
1840
                {
1841
                    CardStatus* adj_status = &def_assaults[from_idx];
14,745,702✔
1842
                    if (!is_alive(adj_status)) { continue; }
14,745,702✔
1843
                    armor_value = std::max(armor_value, adj_status->skill(Skill::armor));
18,855,785✔
1844
                }
1845
            }
1846
            if (armor_value > 0)
22,503,708✔
1847
            {
1848
#ifndef NDEBUG
1849
                if(debug_print > 0) { reduced_desc += tuo::to_string(armor_value) + "(armor)"; }
4,446,020✔
1850
#endif
1851
                reduced_dmg += armor_value;
1852
            }
1853
            if (def_status->protected_value() > 0)
22,503,708✔
1854
            {
1855
#ifndef NDEBUG
1856
                if(debug_print > 0) { reduced_desc += (reduced_desc.empty() ? "" : "+") + tuo::to_string(def_status->protected_value()) + "(protected)"; }
11,800,109✔
1857
#endif
1858
                reduced_dmg += def_status->protected_value();
9,276,530✔
1859
            }
1860
            unsigned pierce_value = att_status->skill(Skill::pierce);
22,503,708✔
1861
            if (reduced_dmg > 0 && pierce_value > 0)
22,503,708✔
1862
            {
1863
#ifndef NDEBUG
1864
                if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(pierce_value) + "(pierce)"; }
478,702✔
1865
#endif
1866
                reduced_dmg = safe_minus(reduced_dmg, pierce_value);
22,503,708✔
1867
            }
1868
            unsigned rupture_value = att_status->skill(Skill::rupture);
22,503,708✔
1869
            if (reduced_dmg > 0 && rupture_value > 0)
22,503,708✔
1870
            {
1871
#ifndef NDEBUG
1872
                if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(rupture_value) + "(rupture)"; }
×
1873
#endif
1874
                reduced_dmg = safe_minus(reduced_dmg, rupture_value);
22,503,708✔
1875
            }
1876
            if(__builtin_expect(fd->fixes[Fix::corrosive_protect_armor],true)) // MK - 05/01/2023 6:58 AM: "Corrosive status reduces protection from attacks (equivalent to giving the attacker Pierce X). Note that the value is equal to initial Corrosive application, not attack removed."
22,503,708✔
1877
            {
1878
                unsigned corrosive_value = def_status->m_corroded_rate;
22,503,708✔
1879
                if (reduced_dmg > 0 && corrosive_value > 0)
22,503,708✔
1880
                {
1881
#ifndef NDEBUG
1882
                    if (debug_print > 0) { reduced_desc += "-" + tuo::to_string(corrosive_value) + "(corrosive)"; }
31,861✔
1883
#endif
1884
                    reduced_dmg = safe_minus(reduced_dmg, corrosive_value);
22,503,708✔
1885
                }
1886
            }
1887
            att_dmg = safe_minus(att_dmg, reduced_dmg);
22,503,708✔
1888
#ifndef NDEBUG
1889
            if (debug_print > 0)
22,503,708✔
1890
            {
1891
                if(!reduced_desc.empty()) { desc += "-[" + reduced_desc + "]"; }
4,706,975✔
1892
                if(!desc.empty()) { desc += "=" + tuo::to_string(att_dmg); }
5,294,601✔
1893
                else { assert(att_dmg == pre_modifier_dmg); }
1,658,730✔
1894
                _DEBUG_MSG(1, "%s attacks %s for %u%s damage\n",
5,741,374✔
1895
                        status_description(att_status).c_str(),
1896
                        status_description(def_status).c_str(), pre_modifier_dmg, desc.c_str());
1897
            }
1898
#endif
1899
            // Passive BGE: Brigade
1900
            if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::brigade] && legion_value, false)
22,503,708✔
1901
                    && can_be_healed(att_status))
×
1902
            {
1903
                _DEBUG_MSG(1, "Brigade: %s heals itself for %u\n",
×
1904
                        status_description(att_status).c_str(), legion_value);
1905
                att_status->add_hp(legion_value);
×
1906
            }
1907

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

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

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

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

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

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

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

1950

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

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

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

1998

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

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

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

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

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

2067
    return true;
2068
}
2069

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

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

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

2094
    template<>
2095
inline bool skill_predicate<Skill::enhance>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
30,754,918✔
2096
{
2097
    if (!is_alive(dst)) return false;
30,754,918✔
2098
    if (!dst->has_skill(s.s)) return false;
61,509,836✔
2099
    if (is_active(dst)) return true;
6,692,070✔
2100
    if (is_defensive_skill(s.s)) return true;
3,507,827✔
2101
    if (s.s == Skill::hunt || s.s == Skill::mark) return true; // Combat-Modifier exceptions (game bug?)
496,850✔
2102
    if (is_instant_debuff_skill(s.s)) return true; // Enhance Sabotage, Inhibit, Disease also without dst being active
496,358✔
2103
    if (is_triggered_skill(s.s) && s.s != Skill::valor) return true;// Enhance Allegiance, Stasis, Bravery ( + not in TU: Flurry, Summon; No enhance on inactive dst: Valor)
496,258✔
2104

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

2113
    template<>
2114
inline bool skill_predicate<Skill::evolve>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
3,085,542✔
2115
{
2116
    if (!is_alive(dst)) return false;
3,085,542✔
2117
    if (!dst->has_skill(s.s)) return false;
6,165,510✔
2118
    if (dst->has_skill(s.s2)) return false;
1,869,266✔
2119
    if (is_active(dst)) return true;
934,633✔
2120
    if (is_defensive_skill(s.s2)) return true;
547,978✔
2121

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

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

2136
    //include on activate/attacked/death
2137
    //for(const auto & a  : {dst->m_card->m_skills,dst->m_card->m_skills_on_play,dst->m_card->m_skills_on_death,dst->m_card->m_skills_on_attacked})
2138
    //
2139
    std::vector<std::vector<SkillSpec>> all;
464,736✔
2140
    all.emplace_back(dst->m_card->m_skills);
464,736✔
2141
    all.emplace_back(dst->m_card->m_skills_on_attacked);
464,736✔
2142

2143
    for(std::vector<SkillSpec> & a  : all)
748,993✔
2144
    {
2145
    // scan all enemy skills until first activation
2146
    for (const SkillSpec & ss: a)
1,430,333✔
2147
    {
2148
        // get skill
2149
        Skill::Skill skill_id = static_cast<Skill::Skill>(ss.id);
1,146,076✔
2150

2151
        // skip non-activation skills and Mimic (Mimic can't be mimicked)
2152
        if (!is_activation_skill(skill_id) || (skill_id == Skill::mimic))
1,478,485✔
2153
        { continue; }
813,667✔
2154

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

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

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

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

2175
    // check skills
2176
    bool inhibited_searched = false;
301,197✔
2177
    for (const auto& ss: dst->m_card->m_skills)
832,768✔
2178
    {
2179
        // skip cooldown skills
2180
        if (dst->m_skill_cd[ss.id] > 0)
769,649✔
2181
        { continue; }
7,974✔
2182

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

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

2190
        // unit with an activation helpful skill is valid target only when there are inhibited units
2191
        // TODO check mend/fortify valid overload target?!?
2192
        if ((evolved_skill_id != Skill::mend && evolved_skill_id != Skill::fortify)
525,920✔
2193
                && is_activation_helpful_skill(evolved_skill_id)
531,571✔
2194
                && __builtin_expect(!inhibited_searched, true))
686,910✔
2195
        {
2196
            for (const auto & c: fd->players[dst->m_player]->assaults.m_indirect)
862,054✔
2197
            {
2198
                if (is_alive(c) && c->m_inhibited)
704,401✔
2199
                { return true; }
238,078✔
2200
            }
2201
            inhibited_searched = true;
2202
        }
2203
    }
2204
    return false;
2205
}
2206

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

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

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

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

2242
    // active player performs Weaken (normal case)
2243
    if (__builtin_expect((fd->tapi == src->m_player), true))
34,369,064✔
2244
    { return is_active_next_turn(dst); }
34,254,815✔
2245

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

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

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

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

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

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

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

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

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

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

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

2314
    template<>
2315
inline void perform_skill<Skill::fortify>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
2,415,013✔
2316
{
2317
    dst->ext_hp(s.x);
1,758,314✔
2318
}
656,699✔
2319

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

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

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

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

2359
    template<>
2360
inline void perform_skill<Skill::rally>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
12,403,941✔
2361
{
2362
    dst->m_temp_attack_buff += s.x;
12,403,941✔
2363
}
227,067✔
2364

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

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

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

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

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

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

2419
    template<>
2420
inline void perform_skill<Skill::mimic>(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s)
213,968✔
2421
{
2422
    // collect all mimickable enemy skills
2423
    std::vector<const SkillSpec *> mimickable_skills;
213,968✔
2424
    mimickable_skills.reserve(dst->m_card->m_skills.size()+dst->m_card->m_skills_on_play.size()+dst->m_card->m_skills_on_death.size()+dst->m_card->m_skills_on_attacked.size());
213,968✔
2425
    _DEBUG_MSG(2, " * Mimickable skills of %s\n", status_description(dst).c_str());
213,968✔
2426
    //include on activate/attacked
2427
    std::vector<std::vector<SkillSpec>> all;
213,968✔
2428
    all.emplace_back(dst->m_card->m_skills);
213,968✔
2429
    all.emplace_back(dst->m_card->m_skills_on_attacked);
213,968✔
2430
    for(std::vector<SkillSpec> & a : all)
641,904✔
2431
    {
2432
    for (const SkillSpec & ss: a)
1,068,656✔
2433
    {
2434
        // get skill
2435
        Skill::Skill skill_id = static_cast<Skill::Skill>(ss.id);
640,720✔
2436

2437
        // skip non-activation skills and Mimic (Mimic can't be mimicked)
2438
        if (!is_activation_skill(skill_id) || (skill_id == Skill::mimic))
924,821✔
2439
        { continue; }
356,619✔
2440

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

2445
        mimickable_skills.emplace_back(&ss);
282,901✔
2446
        _DEBUG_MSG(2, "  + %s\n", skill_description(fd->cards, ss).c_str());
640,720✔
2447
    }
2448
    }
2449

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

2467
    template<unsigned skill_id>
2468
inline unsigned select_fast(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
121,695,958✔
2469
{
2470
    if ((s.y == allfactions)
121,695,958✔
2471
            || fd->bg_effects[fd->tapi][PassiveBGE::metamorphosis]
19,892,279✔
2472
            || fd->bg_effects[fd->tapi][PassiveBGE::megamorphosis])
141,431,711✔
2473
    {
2474
        auto pred = [fd, src, s](CardStatus* c) {
395,023,273✔
2475
            return(skill_predicate<skill_id>(fd, src, c, s));
344,339,593✔
2476
        };
2477
        return fd->make_selection_array(cards.begin(), cards.end(), pred);
102,113,872✔
2478
    }
2479
    else
2480
    {
2481
        auto pred = [fd, src, s](CardStatus* c) {
101,369,231✔
2482
            return ((c->m_card->m_faction == s.y || c->m_card->m_faction == progenitor) && skill_predicate<skill_id>(fd, src, c, s));
95,552,795✔
2483
        };
2484
        return fd->make_selection_array(cards.begin(), cards.end(), pred);
19,582,086✔
2485
    }
2486
}
2487

2488
    template<>
2489
inline unsigned select_fast<Skill::mend>(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
109,589✔
2490
{
2491
    fd->selection_array.clear();
109,589✔
2492
    bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
109,589✔
2493
    auto& assaults = fd->players[src->m_player]->assaults;
109,589✔
2494
    unsigned adj_size = 1 + (unsigned)(critical_reach);
109,589✔
2495
    unsigned host_idx = src->m_index;
109,589✔
2496
    unsigned from_idx = safe_minus(host_idx, adj_size);
109,589✔
2497
    unsigned till_idx = std::min(host_idx + adj_size, safe_minus(assaults.size(), 1));
213,732✔
2498
    for (; from_idx <= till_idx; ++ from_idx)
376,167✔
2499
    {
2500
        if (from_idx == host_idx) { continue; }
268,165✔
2501
        CardStatus* adj_status = &assaults[from_idx];
156,989✔
2502
        if (!is_alive(adj_status)) { continue; }
156,989✔
2503
        if (skill_predicate<Skill::mend>(fd, src, adj_status, s))
310,804✔
2504
        {
2505
            fd->selection_array.push_back(adj_status);
4,276✔
2506
        }
2507
    }
2508
    return fd->selection_array.size();
109,589✔
2509
}
2510

2511
    template<>
2512
inline unsigned select_fast<Skill::fortify>(Field* fd, CardStatus* src, const std::vector<CardStatus*>& cards, const SkillSpec& s)
1,195,334✔
2513
{
2514
    fd->selection_array.clear();
1,195,334✔
2515
    bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
1,195,334✔
2516
    auto& assaults = fd->players[src->m_player]->assaults;
1,195,334✔
2517
    unsigned adj_size = 1 + (unsigned)(critical_reach);
1,195,334✔
2518
    unsigned host_idx = src->m_index;
1,195,334✔
2519
    unsigned from_idx = safe_minus(host_idx, adj_size);
1,195,334✔
2520
    unsigned till_idx = std::min(host_idx + adj_size, safe_minus(assaults.size(), 1));
2,348,398✔
2521
    for (; from_idx <= till_idx; ++ from_idx)
4,234,741✔
2522
    {
2523
        if (from_idx == host_idx) { continue; }
3,054,404✔
2524
        CardStatus* adj_status = &assaults[from_idx];
1,844,073✔
2525
        if (!is_alive(adj_status)) { continue; }
1,844,073✔
2526
        if (skill_predicate<Skill::fortify>(fd, src, adj_status, s))
1,829,076✔
2527
        {
2528
            fd->selection_array.push_back(adj_status);
1,829,076✔
2529
        }
2530
    }
2531
    return fd->selection_array.size();
1,195,334✔
2532
}
2533
inline std::vector<CardStatus*>& skill_targets_hostile_assault(Field* fd, CardStatus* src)
42,173,025✔
2534
{
2535
    return(fd->players[opponent(src->m_player)]->assaults.m_indirect);
42,173,025✔
2536
}
2537

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2602
template<> std::vector<CardStatus*>& skill_targets<Skill::strike>(Field* fd, CardStatus* src)
14,592,024✔
2603
{ return(skill_targets_hostile_assault(fd, src)); }
1,106,181✔
2604

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

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

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

2614
    template<Skill::Skill skill_id>
2615
inline bool check_and_perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s, bool is_evadable
135,490,281✔
2616
#ifndef NQUEST
2617
        , bool & has_counted_quest
2618
#endif
2619
        )
2620
{
2621
    if (__builtin_expect(skill_check<skill_id>(fd, dst, src), true))
135,490,281✔
2622
    {
2623
#ifndef NQUEST
2624
        if (src->m_player == 0 && ! has_counted_quest)
2625
        {
2626
            fd->inc_counter(QuestType::skill_use, skill_id, dst->m_card->m_id);
2627
            has_counted_quest = true;
2628
        }
2629
#endif
2630
        if (is_evadable && (dst->m_evaded < dst->skill(Skill::evade)))
135,490,281✔
2631
        {
2632
            ++ dst->m_evaded;
16,427,114✔
2633
            _DEBUG_MSG(1, "%s %s on %s but it evades\n",
18,385,184✔
2634
                    status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2635
                    status_description(dst).c_str());
2636
            return(false);
16,427,114✔
2637
        }
2638
        _DEBUG_MSG(1, "%s %s on %s\n",
144,907,313✔
2639
                status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2640
                status_description(dst).c_str());
2641
        perform_skill<skill_id>(fd, src, dst, s);
119,063,167✔
2642
        if (s.c > 0)
119,063,167✔
2643
        {
2644
            src->m_skill_cd[skill_id] = s.c;
1,568,641✔
2645
        }
2646
        // Skill: Tribute
2647
        if (skill_check<Skill::tribute>(fd, dst, src)
140,004,179✔
2648
                // only activation helpful skills can be tributed (* except Evolve, Enhance, and Rush)
2649
                && is_activation_helpful_skill(s.id) && (s.id != Skill::evolve) && (s.id != Skill::enhance) && (s.id != Skill::rush)
20,941,012✔
2650
                && (dst->m_tributed < dst->skill(Skill::tribute))
8,075,165✔
2651
                && skill_check<skill_id>(fd, src, src))
106,350,058✔
2652
        {
2653
            ++ dst->m_tributed;
968,285✔
2654
            _DEBUG_MSG(1, "%s tributes %s back to %s\n",
968,285✔
2655
                    status_description(dst).c_str(), skill_short_description(fd->cards, s).c_str(),
2656
                    status_description(src).c_str());
2657
            perform_skill<skill_id>(fd, src, src, s);
968,285✔
2658
        }
2659
        return(true);
119,063,167✔
2660
    }
2661
    _DEBUG_MSG(1, "(CANCELLED) %s %s on %s\n",
×
2662
            status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
2663
            status_description(dst).c_str());
2664
    return(false);
2665
}
2666
template<enum CardType::CardType def_cardtype>
2667
void perform_bge_devour(Field* fd, CardStatus* att_status, CardStatus* def_status)
16,735,140✔
2668
{
2669
// Passive BGE: Devour
2670
                unsigned leech_value;
2671
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::devour], false)
10,770,728✔
2672
                        && ((leech_value = att_status->skill(Skill::leech) + att_status->skill(Skill::refresh)) > 0)
47,300✔
2673
                        && (def_cardtype == CardType::assault))
10,770,728✔
2674
                {
2675
                    unsigned bge_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::devour] > 0)
1,814✔
2676
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::devour]
907✔
2677
                        : 4;
2678
                    unsigned bge_value = (leech_value - 1) / bge_denominator + 1;
907✔
2679
                    if (! att_status->m_sundered)
907✔
2680
                    {
2681
                        _DEBUG_MSG(1, "Devour: %s gains %u attack\n",
2,694✔
2682
                                status_description(att_status).c_str(), bge_value);
2683
                        att_status->m_perm_attack_buff += bge_value;
898✔
2684
                    }
2685
                    _DEBUG_MSG(1, "Devour: %s extends max hp / heals itself for %u\n",
2,721✔
2686
                            status_description(att_status).c_str(), bge_value);
2687
                    att_status->ext_hp(bge_value);
907✔
2688
                }
2689
}
10,770,728✔
2690
template<enum CardType::CardType def_cardtype>
2691
void perform_bge_heroism(Field* fd, CardStatus* att_status, CardStatus* def_status)
16,735,140✔
2692
{
2693
// Passive BGE: Heroism
2694
                unsigned valor_value;
2695
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::heroism], false)
16,735,140✔
2696
                        && ((valor_value = att_status->skill(Skill::valor) + att_status->skill(Skill::bravery)) > 0)
23,218✔
2697
                        && !att_status->m_sundered
2,538✔
2698
                        && (def_cardtype == CardType::assault) && (def_status->m_hp <= 0))
10,773,252✔
2699
                {
2700
                    _DEBUG_MSG(1, "Heroism: %s gain %u attack\n",
2,190✔
2701
                            status_description(att_status).c_str(), valor_value);
2702
                    att_status->m_perm_attack_buff += valor_value;
730✔
2703
                }
2704
                }
10,770,728✔
2705
/**
2706
 * @brief Perform Mark skill, increases Mark-counter.
2707
 * 
2708
 * @param fd     Field
2709
 * @param att_status Attacker
2710
 * @param def_status Defender
2711
 */
2712
void perform_mark(Field* fd, CardStatus* att_status, CardStatus* def_status)
16,735,140✔
2713
{
2714
    // Bug fix? 2023-04-03 mark should come after berserk
2715
    // Increase Mark-counter
2716
    unsigned mark_base = att_status->skill(Skill::mark);
16,735,140✔
2717
    if(mark_base && skill_check<Skill::mark>(fd,att_status,def_status)) {
16,735,140✔
2718
        _DEBUG_MSG(1, "%s marks %s for %u\n",
35,445✔
2719
                status_description(att_status).c_str(), status_description(def_status).c_str(), mark_base);
35,445✔
2720
        def_status->m_marked += mark_base;
35,445✔
2721
    }
2722
}
16,735,140✔
2723
/**
2724
 * @brief Perform Berserk skill and Passive BGE: EnduringRage
2725
 * 
2726
 * Increases attack by berserk value if not sundered.
2727
 * 
2728
 * @param fd     Field
2729
 * @param att_status Attacker
2730
 * @param def_status Defender
2731
 */
2732
void perform_berserk(Field* fd, CardStatus* att_status, CardStatus* def_status)
16,735,140✔
2733
{
2734
            // Skill: Berserk
2735
            unsigned berserk_value = att_status->skill(Skill::berserk);
16,735,140✔
2736
            if ( !att_status->m_sundered && (berserk_value > 0))
16,735,140✔
2737
            {
2738
                // perform_skill_berserk
2739
                att_status->m_perm_attack_buff += berserk_value;
6,614,997✔
2740
#ifndef NQUEST
2741
                if (att_status->m_player == 0)
2742
                {
2743
                    fd->inc_counter(QuestType::skill_use, Skill::berserk);
2744
                }
2745
#endif
2746

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

2761

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

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

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

2821
                // Passive BGE: Counterflux
2822
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::counterflux], false)
2,089,968✔
2823
                        && (def_cardtype == CardType::assault) && is_alive(def_status))
1,240,094✔
2824
                {
2825
                    unsigned flux_denominator = (fd->bg_effects[fd->tapi][PassiveBGE::counterflux] > 0)
8,212✔
2826
                        ? (unsigned)fd->bg_effects[fd->tapi][PassiveBGE::counterflux]
4,106✔
2827
                        : 4;
2828
                    unsigned flux_value = (def_status->skill(Skill::counter) - 1) / flux_denominator + 1;
4,106✔
2829
                    _DEBUG_MSG(1, "Counterflux: %s heals itself and berserks for %u\n",
12,318✔
2830
                            status_description(def_status).c_str(), flux_value);
2831
                    def_status->add_hp(flux_value);
4,106✔
2832
                    if (! def_status->m_sundered)
4,106✔
2833
                    { def_status->m_perm_attack_buff += flux_value; }
3,688✔
2834
                }
2835
            }
2836
}
21,175,896✔
2837
bool check_and_perform_enhance(Field* fd, CardStatus* src, bool early)
157,424,744✔
2838
{
2839
      if(!is_active(src))return false; // active
157,424,744✔
2840
      if(!src->has_skill(Skill::enhance))return false; // enhance Skill
97,525,494✔
2841
      for(auto ss : src->m_card->m_skills)
109,013,472✔
2842
      {
2843
          if(ss.id != Skill::enhance)continue;
81,759,954✔
2844
          if(early ^ (ss.s == Skill::allegiance || ss.s == Skill::absorb ||ss.s == Skill::stasis || ss.s == Skill::bravery))continue; //only specified skills are 'early'
27,708,874✔
2845
          skill_table[ss.id](fd,src,ss);
13,626,759✔
2846
      }
2847
      return true;
27,253,518✔
2848
}
2849
bool check_and_perform_early_enhance(Field* fd, CardStatus* src)
78,712,372✔
2850
{
2851
      return check_and_perform_enhance(fd,src,true);
78,712,372✔
2852
}
2853
bool check_and_perform_later_enhance(Field* fd, CardStatus* src)
78,712,372✔
2854
{
2855
      return check_and_perform_enhance(fd,src,false);
78,712,372✔
2856
}
2857
/**
2858
 * @brief Perform drain skill
2859
 * 
2860
 * @param fd Field
2861
 * @param att_status Attacker status
2862
 * @param def_status Defender status
2863
 * @param att_dmg damage dealt by attacker
2864
 */
2865
template<>
2866
void PerformAttack::perform_swipe_drain<CardType::assault>(Field* fd, CardStatus* att_status, CardStatus* def_status,unsigned att_dmg) {
14,718,351✔
2867
        unsigned swipe_value = att_status->skill(Skill::swipe);
14,718,351✔
2868
        unsigned drain_value = att_status->skill(Skill::drain);
14,718,351✔
2869
        if (swipe_value || drain_value)
14,718,351✔
2870
        {
2871
            Storage<CardStatus>& def_assaults(fd->tip->assaults);
1,402,831✔
2872
            bool critical_reach = fd->bg_effects[fd->tapi][PassiveBGE::criticalreach];
1,402,831✔
2873
            auto drain_total_dmg = att_dmg;
1,402,831✔
2874
            unsigned adj_size = 1 + (unsigned)(critical_reach);
1,402,831✔
2875
            unsigned host_idx = def_status->m_index;
1,402,831✔
2876
            unsigned from_idx = safe_minus(host_idx, adj_size);
1,402,831✔
2877
            unsigned till_idx = std::min(host_idx + adj_size, safe_minus(def_assaults.size(), 1));
2,383,877✔
2878
            for (; from_idx <= till_idx; ++ from_idx)
3,925,756✔
2879
            {
2880
                if (from_idx == host_idx) { continue; }
2,522,925✔
2881
                CardStatus* adj_status = &def_assaults[from_idx];
1,120,094✔
2882
                if (!is_alive(adj_status)) { continue; }
1,120,094✔
2883
                _DEBUG_ASSERT(adj_status->m_card->m_type == CardType::assault); //only assaults
900,341✔
2884
                //unsigned swipe_dmg = safe_minus(
2885
                //    swipe_value + drain_value + def_status->m_enfeebled,
2886
                //    def_status->protected_value());
2887
                unsigned remaining_dmg = remove_absorption(fd,adj_status,swipe_value + drain_value + adj_status->m_enfeebled);
900,341✔
2888
                remaining_dmg = safe_minus(remaining_dmg,adj_status->protected_value());
900,341✔
2889
                _DEBUG_MSG(1, "%s swipes %s for %u damage\n",
900,341✔
2890
                        status_description(att_status).c_str(),
2891
                        status_description(adj_status).c_str(), remaining_dmg);
900,341✔
2892

2893
                remove_hp(fd, adj_status, remaining_dmg);
900,341✔
2894
                drain_total_dmg += remaining_dmg;
900,341✔
2895
            }
2896
            if (drain_value && skill_check<Skill::drain>(fd, att_status, nullptr))
1,402,831✔
2897
            {
2898
                _DEBUG_MSG(1, "%s drains %u hp\n",
53,918✔
2899
                        status_description(att_status).c_str(), drain_total_dmg);
53,918✔
2900
                att_status->add_hp(drain_total_dmg);
53,918✔
2901
            }
2902
            prepend_on_death(fd);
1,402,831✔
2903
            resolve_skill(fd);
1,402,831✔
2904
        }
2905
}
14,718,351✔
2906
/**
2907
 * @brief Perform Hunt skill
2908
 * 
2909
 * @param fd Field 
2910
 * @param att_status Attacker status
2911
 * @param def_status Defender status
2912
 */
2913
void perform_hunt(Field* fd, CardStatus* att_status, CardStatus* def_status) {
21,187,902✔
2914
    unsigned hunt_value = att_status->skill(Skill::hunt);
21,187,902✔
2915
        if(hunt_value)
21,187,902✔
2916
        {
2917
            CardStatus* hunted_status{select_first_enemy_assault(fd)};
31,735✔
2918
            if (hunted_status != nullptr)
31,735✔
2919
            {
2920
                unsigned remaining_dmg = remove_absorption(fd,hunted_status,hunt_value + hunted_status->m_enfeebled);
21,960✔
2921
                remaining_dmg = safe_minus(remaining_dmg,hunted_status->protected_value());
21,960✔
2922
                _DEBUG_MSG(1, "%s hunts %s for %u damage\n",
21,960✔
2923
                        status_description(att_status).c_str(),
2924
                        status_description(hunted_status).c_str(), remaining_dmg);
21,960✔
2925

2926
                remove_hp(fd, hunted_status, remaining_dmg);
21,960✔
2927

2928
                prepend_on_death(fd);
21,960✔
2929
                resolve_skill(fd);
21,960✔
2930
            }
2931
        }
2932
}
21,187,902✔
2933
/**
2934
 * @brief Perform Subdue skill
2935
 * 
2936
 * @param fd Field
2937
 * @param att_status Attacker status
2938
 * @param def_status Defender status
2939
 */
2940
void perform_subdue(Field* fd, CardStatus* att_status, CardStatus* def_status)
22,507,698✔
2941
{
2942
                // Skill: Subdue
2943
                unsigned subdue_value = def_status->skill(Skill::subdue);
22,507,698✔
2944
                if (__builtin_expect(subdue_value, false))
22,507,698✔
2945
                {
2946
                    _DEBUG_MSG(1, "%s subdues %s by %u\n",
843,054✔
2947
                            status_description(def_status).c_str(),
2948
                            status_description(att_status).c_str(), subdue_value);
843,054✔
2949
                    att_status->m_subdued += subdue_value;
843,054✔
2950
                    //fix negative attack
2951
                    if(att_status->calc_attack_power()<0)
843,054✔
2952
                    {
2953
                        att_status->m_temp_attack_buff -= att_status->calc_attack_power();
2,655✔
2954
                    }
2955
                    if (att_status->m_hp > att_status->max_hp())
949,674✔
2956
                    {
2957
                        _DEBUG_MSG(1, "%s loses %u HP due to subdue (max hp: %u)\n",
143,726✔
2958
                                status_description(att_status).c_str(),
2959
                                (att_status->m_hp - att_status->max_hp()),
2960
                                att_status->max_hp());
143,726✔
2961
                        att_status->m_hp = att_status->max_hp();
246,759✔
2962
                    }
2963
                }
2964
}
22,507,698✔
2965
bool check_and_perform_valor(Field* fd, CardStatus* src)
14,146,043✔
2966
{
2967
    unsigned valor_value = src->skill(Skill::valor);
14,146,043✔
2968
    if (valor_value && !src->m_sundered && skill_check<Skill::valor>(fd, src, nullptr))
28,292,086✔
2969
    {
2970
        _DEBUG_ASSERT(src->m_card->m_type == CardType::assault); //only assaults
45,648✔
2971
        unsigned opponent_player = opponent(src->m_player);
45,648✔
2972
        const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ?
45,648✔
2973
            &fd->players[opponent_player]->assaults[src->m_index] :
20,299✔
2974
            nullptr;
45,648✔
2975
        if (dst == nullptr || dst->m_hp <= 0)
20,299✔
2976
        {
2977
            _DEBUG_MSG(1, "%s loses Valor (no blocker)\n", status_description(src).c_str());
25,349✔
2978
            return false;
25,349✔
2979
        }
2980
        else if (dst->attack_power() <= src->attack_power())
20,299✔
2981
        {
2982
            _DEBUG_MSG(1, "%s loses Valor (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str());
2,336✔
2983
            return false;
2,336✔
2984
        }
2985
#ifndef NQUEST
2986
        if (src->m_player == 0)
2987
        {
2988
            fd->inc_counter(QuestType::skill_use, Skill::valor);
2989
        }
2990
#endif
2991
        _DEBUG_MSG(1, "%s activates Valor %u\n", status_description(src).c_str(), valor_value);
17,963✔
2992
        src->m_perm_attack_buff += valor_value;
17,963✔
2993
        return true;
17,963✔
2994
    }
2995
    return false;
2996
}
2997

2998
bool check_and_perform_bravery(Field* fd, CardStatus* src)
61,204,487✔
2999
{
3000
    unsigned bravery_value = src->skill(Skill::bravery);
61,204,487✔
3001
    if (bravery_value && !src->m_sundered && skill_check<Skill::bravery>(fd, src, nullptr))
122,408,974✔
3002
    {
3003
        _DEBUG_ASSERT(src->m_card->m_type == CardType::assault); //only assaults
1,856,245✔
3004
        unsigned opponent_player = opponent(src->m_player);
1,856,245✔
3005
        const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ?
1,856,245✔
3006
            &fd->players[opponent_player]->assaults[src->m_index] :
1,042,967✔
3007
            nullptr;
1,856,245✔
3008
        if (dst == nullptr || dst->m_hp <= 0)
1,042,967✔
3009
        {
3010
            _DEBUG_MSG(1, "%s loses Bravery (no blocker)\n", status_description(src).c_str());
813,278✔
3011
            return false;
813,278✔
3012
        }
3013
        else if (dst->attack_power() <= src->attack_power())
1,042,967✔
3014
        {
3015
            _DEBUG_MSG(1, "%s loses Bravery (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str());
766,981✔
3016
            return false;
766,981✔
3017
        }
3018
#ifndef NQUEST
3019
        if (src->m_player == 0)
3020
        {
3021
            fd->inc_counter(QuestType::skill_use, Skill::bravery);
3022
        }
3023
#endif
3024
        _DEBUG_MSG(1, "%s activates Bravery %u\n", status_description(src).c_str(), bravery_value);
275,986✔
3025
        src->m_perm_attack_buff += bravery_value;
275,986✔
3026

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

3038
        return true;
275,986✔
3039
    }
3040
    return false;
3041
}
3042

3043
bool check_and_perform_inhibit(Field* fd, CardStatus* att_status,CardStatus* def_status)
33,536,090✔
3044
{
3045
      unsigned inhibit_value = att_status->skill(Skill::inhibit);
33,536,090✔
3046
      if (inhibit_value > def_status->m_inhibited && skill_check<Skill::inhibit>(fd, att_status, def_status))
36,432,511✔
3047
      {
3048
        _DEBUG_MSG(1, "%s inhibits %s by %u\n",
2,896,420✔
3049
                status_description(att_status).c_str(),
3050
                status_description(def_status).c_str(), inhibit_value);
2,896,420✔
3051
        def_status->m_inhibited = inhibit_value;
2,896,420✔
3052
        return true;
2,896,420✔
3053
      }
3054
      return false;
3055
}
3056
bool check_and_perform_sabotage(Field* fd, CardStatus* att_status, CardStatus* def_status)
33,536,090✔
3057
{
3058
    unsigned sabotage_value = att_status->skill(Skill::sabotage);
33,536,090✔
3059
    if (sabotage_value > def_status->m_sabotaged && skill_check<Skill::sabotage>(fd, att_status, def_status))
34,051,768✔
3060
    {
3061
        _DEBUG_MSG(1, "%s sabotages %s by %u\n",
515,678✔
3062
                status_description(att_status).c_str(),
3063
                status_description(def_status).c_str(), sabotage_value);
515,678✔
3064
        def_status->m_sabotaged = sabotage_value;
515,678✔
3065
        return true;
515,678✔
3066
    }
3067
    return false;
3068
}
3069
bool check_and_perform_disease(Field* fd, CardStatus* att_status,CardStatus* def_status)
33,536,090✔
3070
{
3071
    unsigned disease_base = att_status->skill(Skill::disease);
33,536,090✔
3072
    if(disease_base && skill_check<Skill::disease>(fd, att_status, def_status)) {
33,667,699✔
3073
        _DEBUG_MSG(1, "%s diseases %s for %u\n",
131,609✔
3074
        status_description(att_status).c_str(), status_description(def_status).c_str(), disease_base);
131,609✔
3075
        def_status->m_diseased += disease_base;
131,609✔
3076
        return true;
131,609✔
3077
    }
3078
    return false;
3079
}
3080

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

3108

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

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

3138
    // (false-loop)
3139
    unsigned n_selected = n_candidates;
122,770,729✔
3140
    do
121,894,700✔
3141
    {
3142
        // no candidates
3143
        if (n_candidates == 0)
119,540,438✔
3144
        { break; }
3145

3146
        // show candidates (debug)
3147
        _DEBUG_SELECTION("%s", skill_names[skill_id].c_str());
75,009,309✔
3148

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

3154
        // shuffle & trim
3155
        for (unsigned i = 0; i < n_targets; ++i)
40,401,000✔
3156
        {
3157
            std::swap(fd->selection_array[i], fd->selection_array[fd->rand(i, n_candidates - 1)]);
20,411,524✔
3158
        }
3159
        fd->selection_array.resize(n_targets);
19,989,476✔
3160
        if (n_targets > 1)
19,989,476✔
3161
        {
3162
            std::sort(fd->selection_array.begin(), fd->selection_array.end(),
295,963✔
3163
                    [](const CardStatus * a, const CardStatus * b) { return a->m_index < b->m_index; });
696,185✔
3164
        }
3165
        n_selected = n_targets;
3166

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

3169
    return n_selected;
121,894,700✔
3170
}
3171

3172
    template<Skill::Skill skill_id>
3173
void perform_targetted_allied_fast(Field* fd, CardStatus* src, const SkillSpec& s)
77,135,485✔
3174
{
3175
    select_targets<skill_id>(fd, src, s);
77,135,485✔
3176
    unsigned num_inhibited = 0;
77,135,485✔
3177
#ifndef NQUEST
3178
    bool has_counted_quest = false;
3179
#endif
3180
    bool src_overloaded = src->m_overloaded;
77,135,485✔
3181
    std::vector<CardStatus*> selection_array = fd->selection_array;
77,135,485✔
3182
    for (CardStatus * dst: selection_array)
161,435,502✔
3183
    {
3184
        if (dst->m_inhibited > 0 && !src_overloaded)
84,300,017✔
3185
        {
3186
            _DEBUG_MSG(1, "%s %s on %s but it is inhibited\n",
5,389,076✔
3187
                    status_description(src).c_str(), skill_short_description(fd->cards, s).c_str(),
3188
                    status_description(dst).c_str());
3189
            -- dst->m_inhibited;
4,929,002✔
3190
            ++ num_inhibited;
4,929,002✔
3191
            continue;
4,929,002✔
3192
        }
4,929,002✔
3193
        check_and_perform_skill<skill_id>(fd, src, dst, s, false
79,371,015✔
3194
#ifndef NQUEST
3195
                , has_counted_quest
3196
#endif
3197
                );
3198
    }
3199

3200
    // Passive BGE: Divert
3201
    if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::divert], false)
77,135,485✔
3202
            && (num_inhibited > 0))
77,135,485✔
3203
    {
3204
        SkillSpec diverted_ss = s;
5,806✔
3205
        diverted_ss.y = allfactions;
5,806✔
3206
        diverted_ss.n = 1;
5,806✔
3207
        diverted_ss.all = false;
5,806✔
3208
        for (unsigned i = 0; i < num_inhibited; ++ i)
11,647✔
3209
        {
3210
            select_targets<skill_id>(fd, &fd->tip->commander, diverted_ss);
5,841✔
3211
            std::vector<CardStatus*> selection_array = fd->selection_array;
5,841✔
3212
            for (CardStatus * dst: selection_array)
9,819✔
3213
            {
3214
                if (dst->m_inhibited > 0)
3,978✔
3215
                {
3216
                    _DEBUG_MSG(1, "%s %s (Diverted) on %s but it is inhibited\n",
705✔
3217
                            status_description(src).c_str(), skill_short_description(fd->cards, diverted_ss).c_str(),
3218
                            status_description(dst).c_str());
3219
                    -- dst->m_inhibited;
235✔
3220
                    continue;
235✔
3221
                }
235✔
3222
                _DEBUG_MSG(1, "%s %s (Diverted) on %s\n",
11,229✔
3223
                        status_description(src).c_str(), skill_short_description(fd->cards, diverted_ss).c_str(),
3224
                        status_description(dst).c_str());
3225
                perform_skill<skill_id>(fd, src, dst, diverted_ss);
3,782✔
3226
            }
3227
        }
3228
    }
3229
}
77,135,485✔
3230

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

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

3259
    // apply skill to each target(dst)
3260
    unsigned selection_array_len = fd->selection_array.size();
44,753,374✔
3261
    std::vector<CardStatus*> selection_array = fd->selection_array;
44,753,374✔
3262
    for (CardStatus * dst: selection_array)
100,855,498✔
3263
    {
3264
        // TurningTides
3265
        if (__builtin_expect(has_turningtides, false))
15,032,883✔
3266
        {
3267
            old_attack = dst->attack_power();
11,175✔
3268
        }
3269

3270
        // check & apply skill to target(dst)
3271
        if (check_and_perform_skill<skill_id>(fd, src, dst, s, ! (src->m_overloaded || (__builtin_expect(fd->fixes[Fix::dont_evade_mimic_selection],true) &&  skill_id == Skill::mimic))
56,316,092✔
3272
#ifndef NQUEST
3273
                    , has_counted_quest
3274
#endif
3275
                    ))
3276
        {
3277
            // TurningTides: get max attack decreasing
3278
            if (__builtin_expect(has_turningtides, false))
10,030,513✔
3279
            {
3280
                turningtides_value = std::max(turningtides_value, safe_minus(old_attack, dst->attack_power()));
25,788✔
3281
            }
3282

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

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

3301
    prepend_on_death(fd);  // skills
44,753,374✔
3302

3303
    // Payback/Revenge
3304
    for (CardStatus * pb_status: paybackers)
45,818,886✔
3305
    {
3306
        turningtides_value = 0;
1,065,512✔
3307

3308
        // apply Revenge
3309
        if (pb_status->skill(Skill::revenge))
1,065,512✔
3310
        {
3311
            unsigned revenged_count(0);
3312
            for (unsigned case_index(0); case_index < 3; ++ case_index)
4,260,084✔
3313
            {
3314
                CardStatus * target_status;
3315
#ifndef NDEBUG
3316
                const char * target_desc;
3317
#endif
3318
                switch (case_index)
3,195,063✔
3319
                {
3320
                    // revenge to left
3321
                    case 0:
1,065,021✔
3322
                        if (!(target_status = fd->left_assault(src))) { continue; }
277,933✔
3323
#ifndef NDEBUG
3324
                        target_desc = "left";
3325
#endif
3326
                        break;
3327

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

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

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

3349
                // skip illegal target
3350
                if (!skill_predicate<skill_id>(fd, target_status, target_status, s))
2,671,373✔
3351
                {
3352
                    continue;
165,002✔
3353
                }
3354

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

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

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

3381
                // revenged TurningTides: get max attack decreasing
3382
                if (__builtin_expect(has_turningtides, false))
105,365✔
3383
                {
3384
                    turningtides_value = std::max(turningtides_value, safe_minus(old_attack, target_status->attack_power()));
2,147✔
3385
                }
3386
            }
3387
            if (revenged_count)
1,065,021✔
3388
            {
3389
                // consume remaining payback/revenge
3390
                ++ pb_status->m_paybacked;
1,043,580✔
3391

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

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

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

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

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

3445
    prepend_on_death(fd,true);  // paybacked skills
44,753,374✔
3446
}
44,753,374✔
3447

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

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

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

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

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

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

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

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

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

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

3507

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

3511

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

3523

3524

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

3531

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

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

3579

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

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

3605

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

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

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

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

3764
        // Play dominion & fortresses
3765
        for (unsigned _(0), ai(fd->tapi); _ < 2; ++_)
4,009,578✔
3766
        {
3767
            if (fd->players[ai]->deck->alpha_dominion)
2,673,052✔
3768
            { PlayCard(fd->players[ai]->deck->alpha_dominion, fd, ai, &fd->players[ai]->commander).op<CardType::structure>(); }
1,444,435✔
3769
            for (const Card* played_card: fd->players[ai]->deck->shuffled_forts)
5,346,104✔
3770
            {
3771

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

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

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

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

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

3819
            // Begin 'Play Card' phase action
3820
            fd->prepare_action();
17,035,798✔
3821

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

3845

3846

3847
        }
3848
        if (__builtin_expect(fd->end, false)) { break; }
18,886,009✔
3849

3850
        //-------------------------------------------------
3851
        // Phase: (Later-) Enhance, Inhibit, Sabotage, Disease
3852
        //-------------------------------------------------
3853
        //Skill: Enhance
3854
        //Perform later enhance for commander
3855
        if(!fd->fixes[Fix::enhance_early]) {
18,886,009✔
3856
        check_and_perform_later_enhance(fd,&fd->tap->commander);
×
3857
        auto& structures(fd->tap->structures);
×
3858
        for(unsigned index(0); index < structures.size(); ++index)
×
3859
        {
3860
            CardStatus * status = &structures[index];
×
3861
            //enhance everything else after card was played
3862
            check_and_perform_later_enhance(fd,status);
×
3863
        }
3864
        }
3865
        //Perform Inhibit, Sabotage, Disease
3866
        auto& assaults(fd->tap->assaults);
18,886,009✔
3867
        for(unsigned index(0); index < assaults.size(); ++index)
73,753,728✔
3868
        {
3869
            CardStatus * att_status = &assaults[index];
54,867,719✔
3870
            if(att_status->m_index >= fd->tip->assaults.size())continue; //skip no enemy
54,867,719✔
3871
            auto def_status = &fd->tip->assaults[att_status->m_index];
33,536,279✔
3872
            if(!is_alive(def_status))continue; //skip dead
33,536,279✔
3873

3874
            check_and_perform_inhibit(fd,att_status,def_status);
33,536,090✔
3875
            check_and_perform_sabotage(fd,att_status,def_status);
33,536,090✔
3876
            check_and_perform_disease(fd,att_status,def_status);
33,536,090✔
3877
        }
3878
        //-------------------------------------------------
3879

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

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

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

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

3951
        // Evaluate structures
3952
        fd->current_phase = Field::structures_phase;
18,886,009✔
3953
        for (fd->current_ci = 0; !fd->end && (fd->current_ci < fd->tap->structures.size()); ++fd->current_ci)
44,180,690✔
3954
        {
3955
            CardStatus* current_status(&fd->tap->structures[fd->current_ci]);
25,294,681✔
3956
            if (!is_active(current_status))
25,294,681✔
3957
            {
3958
                _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str());
8,324,918✔
3959
            }
3960
            else
3961
            {
3962
                evaluate_skills<CardType::structure>(fd, current_status, current_status->m_card->m_skills);
16,969,763✔
3963
            }
3964
        }
3965

3966
        // Evaluate assaults
3967
        fd->current_phase = Field::assaults_phase;
18,886,009✔
3968
        fd->bloodlust_value = 0;
18,886,009✔
3969
        for (fd->current_ci = 0; !fd->end && (fd->current_ci < fd->tap->assaults.size()); ++fd->current_ci)
68,448,248✔
3970
        {
3971
            CardStatus* current_status(&fd->tap->assaults[fd->current_ci]);
50,878,045✔
3972
            bool attacked = false;
50,878,045✔
3973
            if (!is_active(current_status))
50,878,045✔
3974
            {
3975
                _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str());
26,397,201✔
3976
                // Passive BGE: HaltedOrders
3977
                /*
3978
                unsigned inhibit_value;
3979
                if (__builtin_expect(fd->bg_effects[fd->tapi][PassiveBGE::haltedorders], false)
3980
                        && (current_status->m_delay > 0) // still frozen
3981
                        && (fd->current_ci < fd->tip->assaults.size()) // across slot isn't empty
3982
                        && is_alive(&fd->tip->assaults[fd->current_ci]) // across assault is alive
3983
                        && ((inhibit_value = current_status->skill(Skill::inhibit))
3984
                            > fd->tip->assaults[fd->current_ci].m_inhibited)) // inhibit/re-inhibit(if higher)
3985
                        {
3986
                            CardStatus* across_status(&fd->tip->assaults[fd->current_ci]);
3987
                            _DEBUG_MSG(1, "Halted Orders: %s inhibits %s by %u\n",
3988
                                    status_description(current_status).c_str(),
3989
                                    status_description(across_status).c_str(), inhibit_value);
3990
                            across_status->m_inhibited = inhibit_value;
3991
                        }
3992
                        */
3993
            }
3994
            else
3995
            {
3996
                if (current_status->m_protected_stasis)
24,480,844✔
3997
                {
3998
                    _DEBUG_MSG(1, "%s loses Stasis protection (activated)\n",
2,406,890✔
3999
                            status_description(current_status).c_str());
4000
                }
4001
                current_status->m_protected_stasis = 0;
24,480,844✔
4002
                fd->assault_bloodlusted = false;
24,480,844✔
4003
                current_status->m_step = CardStep::attacking;
24,480,844✔
4004
                evaluate_skills<CardType::assault>(fd, current_status, current_status->m_card->m_skills, &attacked);
24,480,844✔
4005
                if (__builtin_expect(fd->end, false)) { break; }
24,480,844✔
4006
                if (__builtin_expect(!is_alive(current_status), false)) { continue; }
23,165,038✔
4007
            }
4008

4009
            current_status->m_step = CardStep::attacked;
48,825,100✔
4010
        }
4011
        fd->current_phase = Field::end_phase;
18,886,009✔
4012
        turn_end_phase(fd);
18,886,009✔
4013
        if (__builtin_expect(fd->end, false)) { break; }
18,886,009✔
4014
        _DEBUG_MSG(1, "TURN %u ends for %s\n", fd->turn, status_description(&fd->tap->commander).c_str());
17,570,203✔
4015
        std::swap(fd->tapi, fd->tipi);
17,570,203✔
4016
        std::swap(fd->tap, fd->tip);
17,570,203✔
4017
        ++fd->turn;
17,570,203✔
4018
    }
4019

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

4023
//------------------------------------------------------------------------------
4024
void fill_skill_table()
81✔
4025
{
4026
    memset(skill_table, 0, sizeof skill_table);
81✔
4027
    skill_table[Skill::mortar] = perform_targetted_hostile_fast<Skill::mortar>;
81✔
4028
    skill_table[Skill::enfeeble] = perform_targetted_hostile_fast<Skill::enfeeble>;
81✔
4029
    skill_table[Skill::enhance] = perform_targetted_allied_fast<Skill::enhance>;
81✔
4030
    skill_table[Skill::evolve] = perform_targetted_allied_fast<Skill::evolve>;
81✔
4031
    skill_table[Skill::heal] = perform_targetted_allied_fast<Skill::heal>;
81✔
4032
    skill_table[Skill::jam] = perform_targetted_hostile_fast<Skill::jam>;
81✔
4033
    skill_table[Skill::mend] = perform_targetted_allied_fast<Skill::mend>;
81✔
4034
    skill_table[Skill::fortify] = perform_targetted_allied_fast<Skill::fortify>;
81✔
4035
    skill_table[Skill::overload] = perform_targetted_allied_fast<Skill::overload>;
81✔
4036
    skill_table[Skill::protect] = perform_targetted_allied_fast<Skill::protect>;
81✔
4037
    skill_table[Skill::rally] = perform_targetted_allied_fast<Skill::rally>;
81✔
4038
    skill_table[Skill::enrage] = perform_targetted_allied_fast<Skill::enrage>;
81✔
4039
    skill_table[Skill::entrap] = perform_targetted_allied_fast<Skill::entrap>;
81✔
4040
    skill_table[Skill::rush] = perform_targetted_allied_fast_rush;
81✔
4041
    skill_table[Skill::siege] = perform_targetted_hostile_fast<Skill::siege>;
81✔
4042
    skill_table[Skill::strike] = perform_targetted_hostile_fast<Skill::strike>;
81✔
4043
    skill_table[Skill::sunder] = perform_targetted_hostile_fast<Skill::sunder>;
81✔
4044
    skill_table[Skill::weaken] = perform_targetted_hostile_fast<Skill::weaken>;
81✔
4045
    skill_table[Skill::mimic] = perform_targetted_hostile_fast<Skill::mimic>;
81✔
4046
}
81✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc