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

APN-Pucky / tyrant_optimize / 15799373702

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

push

github

web-flow
ci: lcov ignore (#84)

* ci: lcov ignore

* latest ubutnut

* ci: lcov more ignore

* try less than latest

* cmake toolchain for windows

* ubuntu in ubuntu

* Revert "cmake toolchain for windows"

This reverts commit 881abfcd5.

* install git

* fetch depth

* add sudo

* non interactive debian

* set time zone

* ok

* 2

* add_pip

* no source but sh

4755 of 6763 relevant lines covered (70.31%)

9581795.24 hits per line

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

87.02
/xml.cpp
1
#include "xml.h"
2

3
#include <cstring>
4
#include <fstream>
5
#include <iostream>
6
#include <map>
7
#include <stdexcept>
8
#include <algorithm>
9
#include <boost/algorithm/string.hpp>
10
#include "rapidxml.hpp"
11
#include "card.h"
12
#include "cards.h"
13
#include "deck.h"
14
#include "tyrant.h"
15
#include "tyrant_optimize.h"
16
//---------------------- $20 cards.xml parsing ---------------------------------
17
// Sets: 1 enclave; 2 nexus; 3 blight; 4 purity; 5 homeworld;
18
// 6 phobos; 7 phobos aftermath; 8 awakening
19
// 1000 standard; 5000 rewards; 5001 promotional; 9000 exclusive
20
// mission only and test cards have no set
21
using namespace rapidxml;
22

23
Skill::Skill skill_name_to_id(const std::string & name)
7,289,605✔
24
{
25
    static std::map<std::string, int> skill_map;
7,289,605✔
26
    if (skill_map.empty())
7,289,605✔
27
    {
28
        for (unsigned i(0); i < Skill::num_skills; ++i)
57✔
29
        {
30
            std::string skill_id = boost::to_lower_copy(skill_names[i]);
56✔
31
            skill_map[skill_id] = i;
56✔
32
        }
56✔
33
        skill_map["armored"] = skill_map["armor"];  // Special case for Armor: id and name differ
1✔
34
        skill_map["besiege"] = skill_map["mortar"]; // Special case for Mortar: id and name differ
1✔
35
    }
36
    auto x = skill_map.find(boost::to_lower_copy(name));
7,289,605✔
37
    if (x == skill_map.end())
7,289,605✔
38
    {
39
        return Skill::no_skill;
40
    }
41
    else
42
    {
43
        return (Skill::Skill)x->second;
7,289,573✔
44
    }
45
}
46

47
Faction skill_faction(xml_node<>* skill)
7,114,068✔
48
{
49
    xml_attribute<>* y(skill->first_attribute("y"));
7,114,068✔
50
    if (y)
7,114,068✔
51
    {
52
        return static_cast<Faction>(atoi(y->value()));
1,748,448✔
53
    }
54
    return allfactions;
55
}
56

57
Skill::Trigger skill_trigger(xml_node<>* skill)
7,113,288✔
58
{
59
    xml_attribute<>* trigger(skill->first_attribute("trigger"));
7,113,288✔
60
    if (trigger)
7,113,288✔
61
    {
62
        for (unsigned t(Skill::Trigger::activate); t < Skill::num_triggers; ++t)
956,670✔
63
        {
64
            if (skill_trigger_names[t] == boost::to_lower_copy(std::string(trigger->value())))
2,870,010✔
65
            {
66
                return static_cast<Skill::Trigger>(t);
339,924✔
67
            }
68
        }
69
        std::cerr << "WARNING: unknown skill trigger: " << trigger->value() << std::endl;
×
70
    }
71
    return Skill::Trigger::activate;
72
}
73

74
unsigned node_value(xml_node<>* skill, const char* attribute, unsigned default_value = 0)
36,397,296✔
75
{
76
    xml_attribute<>* value_node(skill->first_attribute(attribute));
36,397,296✔
77
    return value_node ? atoi(value_node->value()) : default_value;
45,911,424✔
78
}
79

80
double node_value_float(xml_node<>* skill, const char* attribute, double default_value = 0)
780✔
81
{
82
    xml_attribute<>* value_node(skill->first_attribute(attribute));
780✔
83
    return value_node ? atof(value_node->value()) : default_value;
1,404✔
84
}
85

86
Skill::Skill skill_target_skill(xml_node<>* skill, const char* attribute)
14,228,136✔
87
{
88
    Skill::Skill s(Skill::no_skill);
14,228,136✔
89
    xml_attribute<>* x(skill->first_attribute(attribute));
14,228,136✔
90
    if (x)
14,228,136✔
91
    {
92
       s = skill_name_to_id(x->value());
351,000✔
93
    }
94
    return(s);
14,228,136✔
95
}
96

97
//------------------------------------------------------------------------------
98
void load_decks_xml(Decks& decks, const Cards& all_cards, const std::string & mission_filename, const std::string & raid_filename, bool do_warn_on_missing=true)
78✔
99
{
100
    try
78✔
101
    {
102
        read_missions(decks, all_cards, mission_filename, do_warn_on_missing);
78✔
103
    }
104
    catch (const rapidxml::parse_error& e)
×
105
    {
106
        std::cerr << "\nFailed to parse file [" << mission_filename << "]. Skip it.\n";
×
107
    }
×
108
    try
78✔
109
    {
110
        read_raids(decks, all_cards, raid_filename, do_warn_on_missing);
78✔
111
    }
112
    catch(const rapidxml::parse_error& e)
×
113
    {
114
        std::cerr << "\nFailed to parse file [" << raid_filename << "]. Skip it.\n";
×
115
    }
×
116
}
78✔
117

118
//------------------------------------------------------------------------------
119
bool parse_file(const std::string & filename, std::vector<char>& buffer, xml_document<>& doc, bool do_warn_on_missing=true)
1,950✔
120
{
121
    std::ifstream cards_stream(filename, std::ios::binary);
1,950✔
122
    if (!cards_stream.good())
1,950✔
123
    {
124
        if (do_warn_on_missing)
78✔
125
        {
126
            std::cerr << "WARNING: The file '" << filename << "' does not exist. Proceeding without reading from this file.\n";
×
127
        }
128
        buffer.resize(1);
78✔
129
        buffer[0] = 0;
78✔
130
        doc.parse<0>(&buffer[0]);
78✔
131
        return false;
132
    }
133
    // Get the size of the file
134
    cards_stream.seekg(0,std::ios::end);
1,872✔
135
    std::streampos length = cards_stream.tellg();
1,872✔
136
    cards_stream.seekg(0,std::ios::beg);
1,872✔
137
    buffer.resize(length + std::streampos(1));
1,872✔
138
    cards_stream.read(&buffer[0],length);
1,872✔
139
    // zero-terminate
140
    buffer[length] = '\0';
1,872✔
141
    try
1,872✔
142
    {
143
        doc.parse<0>(&buffer[0]);
1,872✔
144
        return true;
145
    }
146
    catch(rapidxml::parse_error& e)
×
147
    {
148
        std::cerr << "Parse error exception.\n";
×
149
        std::cout << e.what();
×
150
        throw(e);
×
151
    }
×
152
}
1,950✔
153
//------------------------------------------------------------------------------
154
void parse_card_node(Cards& all_cards, Card* card, xml_node<>* card_node)
3,903,588✔
155
{
156
    double eps = 1e-4;
3,903,588✔
157
    xml_node<>* id_node(card_node->first_node("id"));
3,903,588✔
158
    xml_node<>* card_id_node = card_node->first_node("card_id");
3,903,588✔
159
    assert(id_node || card_id_node);
3,903,588✔
160
    xml_node<>* name_node(card_node->first_node("name"));
3,903,588✔
161
    xml_node<>* attack_node(card_node->first_node("attack"));
3,903,588✔
162
    xml_node<>* health_node(card_node->first_node("health"));
3,903,588✔
163
    xml_node<>* cost_node(card_node->first_node("cost"));
3,903,588✔
164
    xml_node<>* rarity_node(card_node->first_node("rarity"));
3,903,588✔
165
    xml_node<>* type_node(card_node->first_node("type"));
3,903,588✔
166
    xml_node<>* fortress_type_node(card_node->first_node("fortress_type"));
3,903,588✔
167
    xml_node<>* set_node(card_node->first_node("set"));
3,903,588✔
168
    int set(set_node ? atoi(set_node->value()) : card->m_set);
4,547,400✔
169
    xml_node<>* level_node(card_node->first_node("level"));
3,903,588✔
170
    xml_node<>* fusion_level_node(card_node->first_node("fusion_level"));
3,903,588✔
171
    if (id_node) { card->m_base_id = card->m_id = atoi(id_node->value()); }
4,548,804✔
172
    else if (card_id_node) { card->m_id = atoi(card_id_node->value()); }
6,516,744✔
173
    if (name_node) { card->m_name = name_node->value(); }
4,551,534✔
174
    if (level_node) { card->m_level = atoi(level_node->value()); }
7,161,960✔
175
    if (fusion_level_node) { card->m_fusion_level = atoi(fusion_level_node->value()); }
4,277,130✔
176
    if (attack_node) { card->m_attack = atoi(attack_node->value()); 
4,812,054✔
177
            if(abs(1-atk_scale)>eps)
908,778✔
178
                card->m_attack = ceil(card->m_attack/(atk_scale));
×
179
    }
180
    if (health_node) { card->m_health = atoi(health_node->value()); 
5,477,940✔
181
            if(abs(1-hp_scale)>eps)
1,574,352✔
182
                card->m_health = ceil(card->m_health/(hp_scale));
×
183

184
    }
185
    if (cost_node) { card->m_delay = atoi(cost_node->value()); }
4,525,716✔
186
    if (id_node)
3,903,588✔
187
    {
188
        // [0 .. 999]
189
        if (card->m_id < 1000)
645,216✔
190
        {
191
            card->m_type = CardType::assault;
15,600✔
192
        }
193

194
        // [1000 .. 1999]
195
        else if (card->m_id < 2000)
629,616✔
196
        {
197
            card->m_type = CardType::commander;
13,728✔
198
        }
199

200
        // [2000 .. 2999]
201
        else if (card->m_id < 3000)
615,888✔
202
        {
203
            card->m_type = CardType::structure;
10,530✔
204

205
            // fortress
206
            if ((card->m_id >= 2700) && (card->m_id < 2997))
10,530✔
207
            {
208
                if (fortress_type_node) {
1,014✔
209
                    unsigned fort_type_value = atoi(fortress_type_node->value());
1,872✔
210
                    switch (fort_type_value) {
936✔
211
                    case 1:
390✔
212
                        card->m_category = CardCategory::fortress_defense;
390✔
213
                        break;
390✔
214
                    case 2:
546✔
215
                        card->m_category = CardCategory::fortress_siege;
546✔
216
                        break;
546✔
217
                    default:
×
218
                        std::cerr << "WARNING: parsing card [" << card->m_id << "]: unsupported fortress_type=" << fort_type_value << std::endl;
×
219
                    }
220
                }
221
                else if ((card->m_id < 2748) || (card->m_id >= 2754)) // except Sky Fortress
78✔
222
                {
223
                    std::cerr << "WARNING: parsing card [" << card->m_id << "]: expected fortress_type node" << std::endl;
×
224
                }
225
            }
226
        }
227

228
        // [3000 ... 7999]
229
        else if (card->m_id < 8000)
605,358✔
230
        {
231
            card->m_type = CardType::assault;
53,664✔
232
        }
233

234
        // [8000 .. 9999]
235
        else if (card->m_id < 10000)
551,694✔
236
        {
237
            card->m_type = CardType::structure;
13,338✔
238
        }
239

240
        // [10000 .. 16999]
241
        else if (card->m_id < 17000)
538,356✔
242
        {
243
            card->m_type = CardType::assault;
87,750✔
244
        }
245

246
        // [17000 .. 24999]
247
        else if (card->m_id < 25000)
450,606✔
248
        {
249
            card->m_type = CardType::structure;
27,378✔
250
        }
251

252
        // [25000 .. 29999]
253
        else if (card->m_id < 30000)
423,228✔
254
        {
255
            card->m_type = CardType::commander;
19,188✔
256
        }
257

258
        // [30000 .. 50000]
259
        else if (card->m_id < 50001)
404,040✔
260
        {
261
            card->m_type = CardType::assault;
254,670✔
262
            if ((card->m_id == 43451) || (card->m_id == 43452))
254,670✔
263
            {
264
                card->m_category = CardCategory::dominion_material;
78✔
265
            }
266
        }
267

268
        // [50001 .. 55000]
269
        else if (card->m_id < 55001)
149,370✔
270
        {
271
            card->m_type = CardType::structure;
4,758✔
272
            card->m_category = CardCategory::dominion_alpha;
4,758✔
273
        }
274

275
        // [55001+]
276
        else
277
        {
278
            card->m_type = CardType::assault;
144,612✔
279
        }
280
    }
281
    if (rarity_node) { card->m_rarity = atoi(rarity_node->value()); }
4,548,804✔
282
    if (type_node) { card->m_faction = static_cast<Faction>(atoi(type_node->value())); }
4,550,364✔
283
    card->m_set = set;
3,903,588✔
284

285
    // fortresses
286
    if (set == 8000)
3,903,588✔
287
    {
288
        if (card->m_type != CardType::structure)
8,580✔
289
        {
290
            std::cerr << "WARNING: parsing card [" << card->m_id << "]: set 8000 supposes fortresses card that implies type Structure"
×
291
                << ", but card has type " << cardtype_names[card->m_type] << std::endl;
×
292
        }
293

294
        // assume all other fortresses as conquest towers
295
        if (card->m_category == CardCategory::normal)
8,580✔
296
        {
297
            card->m_category = CardCategory::fortress_conquest;
1,716✔
298
        }
299
    }
300

301
    if (card_node->first_node("skill"))
3,903,588✔
302
    { 
303
        // inherit no skill if there is skill node
304
        card->m_skills.clear();
2,421,822✔
305
        card->m_skills_on_play.clear();
2,421,822✔
306
            //APN
307
            card->m_skills_on_attacked.clear();
2,421,822✔
308
        card->m_skills_on_death.clear();
2,421,822✔
309
        memset(card->m_skill_value, 0, sizeof card->m_skill_value);
2,421,822✔
310
        memset(card->m_skill_trigger, 0, sizeof card->m_skill_trigger);
2,421,822✔
311
    }
312
    for (xml_node<>* skill_node = card_node->first_node("skill");
3,903,588✔
313
            skill_node;
11,016,876✔
314
            skill_node = skill_node->next_sibling("skill"))
7,113,288✔
315
    {
316
        Skill::Skill skill_id = skill_name_to_id(skill_node->first_attribute("id")->value());
14,226,576✔
317
        if (skill_id == Skill::no_skill) { continue; }
7,113,288✔
318
        auto trig = skill_trigger(skill_node);
7,113,288✔
319
        auto x = node_value(skill_node, "x", 0);
7,113,288✔
320
        auto y = skill_faction(skill_node);
7,113,288✔
321
        auto n = node_value(skill_node, "n", 0);
7,113,288✔
322
        auto c = node_value(skill_node, "c", skill_id==Skill::flurry?1:0);
13,947,960✔
323
        auto s = skill_target_skill(skill_node, "s");
7,113,288✔
324
        auto s2 = skill_target_skill(skill_node, "s2");
7,113,288✔
325
        bool all = node_value(skill_node, "all", 0);
7,113,288✔
326
        auto card_id = node_value(skill_node, "card_id", 0);
7,113,288✔
327
        //scaling
328
        if(abs(1-x_skill_scale[Skill::no_skill]*x_skill_scale[skill_id==Skill::enhance?s:skill_id])>eps)
14,108,016✔
329
                x = ceil(x/(x_skill_scale[Skill::no_skill]*x_skill_scale[skill_id==Skill::enhance?s:skill_id]));
×
330
        if(abs(1-n_skill_scale[Skill::no_skill]*n_skill_scale[skill_id])>eps)
7,113,288✔
331
                n = ceil(n/(n_skill_scale[Skill::no_skill]*n_skill_scale[skill_id]));
×
332
        if(abs(1-c_skill_scale[Skill::no_skill]*c_skill_scale[skill_id])>eps)
7,113,288✔
333
                c = ceil(c/(c_skill_scale[Skill::no_skill]*c_skill_scale[skill_id]));
×
334

335

336

337
        card->add_skill(trig, skill_id, x, y, n, c, s, s2, all, card_id);
7,113,288✔
338
    }
339
    all_cards.all_cards.push_back(card);
3,903,588✔
340
    Card* top_card = card;
3,903,588✔
341
    for (xml_node<>* upgrade_node = card_node->first_node("upgrade");
3,903,588✔
342
            upgrade_node;
7,161,960✔
343
            upgrade_node = upgrade_node->next_sibling("upgrade"))
3,258,372✔
344
    {
345
        Card* pre_upgraded_card = top_card;
3,258,372✔
346
        top_card = new Card(*top_card);
3,258,372✔
347
        parse_card_node(all_cards, top_card, upgrade_node);
3,258,372✔
348
        if (top_card->m_type == CardType::commander)
3,258,372✔
349
        {
350
            // Commanders cost twice
351
            top_card->m_recipe_cost = 2 * upgrade_cost[pre_upgraded_card->m_level];
157,794✔
352
        }
353
        else
354
        {
355
            top_card->m_recipe_cost = upgrade_cost[pre_upgraded_card->m_level];
3,100,578✔
356
        }
357
        top_card->m_recipe_cards.clear();
3,258,372✔
358
        top_card->m_recipe_cards[pre_upgraded_card] = 1;
3,258,372✔
359
        pre_upgraded_card->m_used_for_cards[top_card] = 1;
3,258,372✔
360
    }
361
    card->m_top_level_card = top_card;
3,903,588✔
362
}
3,903,588✔
363

364
bool load_cards_xml(Cards & all_cards, const std::string & filename, bool do_warn_on_missing)
1,560✔
365
{
366
    std::vector<char> buffer;
1,560✔
367
    xml_document<> doc;
1,560✔
368
    if (!parse_file(filename, buffer, doc, do_warn_on_missing)) { return false; }
1,560✔
369
    xml_node<>* root = doc.first_node();
1,482✔
370
    if (!root) { return false; }
1,482✔
371

372
    for (xml_node<>* card_node = root->first_node("unit");
1,482✔
373
        card_node;
646,698✔
374
        card_node = card_node->next_sibling("unit"))
645,216✔
375
    {
376
        auto card = new Card();
645,216✔
377
        parse_card_node(all_cards, card, card_node);
645,216✔
378
    }
379

380
    return true;
381
}
1,560✔
382

383
void load_skills_set_xml(Cards & all_cards, const std::string & filename, bool do_warn_on_missing)
78✔
384
{
385
    std::vector<char> buffer;
78✔
386
    xml_document<> doc;
78✔
387
    parse_file(filename, buffer, doc, do_warn_on_missing);
78✔
388
    xml_node<>* root = doc.first_node();
78✔
389
    if (!root) { return; }
78✔
390

391
    for (xml_node<>* set_node = root->first_node("cardSet");
78✔
392
            set_node;
1,638✔
393
            set_node = set_node->next_sibling("cardSet"))
1,560✔
394
    {
395
        xml_node<>* id_node(set_node->first_node("id"));
1,560✔
396
        xml_node<>* visible_node = set_node->first_node("visible");
1,560✔
397
        if (id_node && visible_node && atoi(visible_node->value()))
3,120✔
398
        {
399
            all_cards.visible_cardset.insert(atoi(id_node->value()));
2,340✔
400
        }
401
    }
402
}
78✔
403

404
void load_levels_xml(Cards& all_cards, const std::string& filename, bool do_warn_on_missing)
78✔
405
{
406
    std::vector<char> buffer;
78✔
407
    xml_document<> doc;
78✔
408
    parse_file(filename, buffer, doc, do_warn_on_missing);
78✔
409
    xml_node<>* root = doc.first_node();
78✔
410
    if (!root) { return; }
78✔
411

412
    for (xml_node<>* dfl_node = root->first_node("dominion_fusion_level");
78✔
413
            dfl_node;
78✔
414
            dfl_node = dfl_node->next_sibling("dominion_fusion_level"))
×
415
    {
416
        xml_node<>* fl_node = dfl_node->first_node("fusion_level");
×
417
        if (!fl_node)
×
418
        {
419
            std::cerr << "WARNING: levels.xml/dominion_fusion_level: no fusion_level" << std::endl;
×
420
            continue;
×
421
        }
422
        unsigned fusion_lvl = atoi(fl_node->value());
×
423
        if (fusion_lvl > 2)
×
424
        {
425
            std::cerr << "WARNING: levels.xml/dominion_fusion_level: unsupported fusion level: " << fusion_lvl << std::endl;
×
426
            continue;
×
427
        }
428
        for (xml_node<>* lvl_node = dfl_node->first_node("level");
×
429
                lvl_node;
×
430
                lvl_node = lvl_node->next_sibling("level"))
×
431
        {
432
            xml_node<>* id_node(lvl_node->first_node("id"));
×
433
            unsigned card_lvl = id_node ? atoi(id_node->value()) : 0;
×
434
            if (!(card_lvl >= 1 && card_lvl <= 6))
×
435
            {
436
                std::cerr << "WARNING: levels.xml/dominion_fusion_level: unsupported card level: " << card_lvl << std::endl;
×
437
                continue;
×
438
            }
439

440
            // dominion cost in shards (dominion material)
441
            // each level have cost for next level (using lvl+1), first level cost can be found in fusion recipes
442
            for (xml_node<>* cost_node = lvl_node->first_node("card_cost");
×
443
                    cost_node && (card_lvl < 6);
×
444
                    cost_node = cost_node->next_sibling("card_cost"))
×
445
            {
446
                const Card* cost_card = all_cards.by_id(atoi(cost_node->first_attribute("card_id")->value()));
×
447
                if (cost_card->m_category != CardCategory::dominion_material)
×
448
                {
449
                    std::cerr << "WARNING: levels.xml/dominion_fusion_level: card_id=" << cost_card->m_id
×
450
                        << " (" << cost_card->m_name << ") is not a dominion material card (new shard type?)" << std::endl;
×
451
                }
452
                else
453
                {
454
                    dominion_cost[fusion_lvl][card_lvl+1][cost_card] += atoi(cost_node->first_attribute("number")->value());
×
455
                }
456
            }
457

458
            // dominion refund in shards (dominion material)
459
            for (xml_node<>* ref_node = lvl_node->first_node("card_refund");
×
460
                    ref_node;
×
461
                    ref_node = ref_node->next_sibling("card_refund"))
×
462
            {
463
                const Card* refund_card = all_cards.by_id(atoi(ref_node->first_attribute("card_id")->value()));
×
464
                if (refund_card->m_category == CardCategory::dominion_material)
×
465
                {
466
                    dominion_refund[fusion_lvl][card_lvl][refund_card] += atoi(ref_node->first_attribute("number")->value());
×
467
                }
468
            }
469
        }
470
    }
471
}
78✔
472
//------------------------------------------------------------------------------
473
Deck* read_deck(Decks& decks, const Cards& all_cards, xml_node<>* node, DeckType::DeckType decktype, unsigned id, std::string base_deck_name)
144,222✔
474
{
475
    xml_node<>* commander_node(node->first_node("commander"));
144,222✔
476
    const Card* card = all_cards.by_id(atoi(commander_node->value()));
288,444✔
477
    const Card* commander_card{card};
143,910✔
478
    xml_node<>* commander_max_level_node(node->first_node("commander_max_level"));
143,910✔
479
    unsigned commander_max_level = commander_max_level_node ? atoi(commander_max_level_node->value()) : commander_card->m_top_level_card->m_level;
143,988✔
480
    unsigned upgrade_opportunities = commander_max_level - card->m_level;
143,910✔
481
    std::vector<const Card*> always_cards;
143,910✔
482
    std::vector<std::tuple<unsigned, unsigned, std::vector<const Card*>>> some_forts;
143,910✔
483
    std::vector<std::tuple<unsigned, unsigned, std::vector<const Card*>>> some_cards;
143,910✔
484
    xml_node<>* levels_node(node->first_node("levels"));
143,910✔
485
    xml_node<>* effects_node(node->first_node("effects"));
143,910✔
486
    xml_node<>* deck_node(node->first_node("deck"));
143,910✔
487
    unsigned max_level = levels_node ? atoi(levels_node->value()) : 10;
188,292✔
488

489
    // Effectes (skill based BGEs; assuming that X is a floating point number (multiplier))
490
    std::vector<SkillSpecXMult> effects;
143,910✔
491
    if (effects_node)
143,910✔
492
    {
493
        for (xml_node<>* skill_node = effects_node->first_node("skill");
156✔
494
                skill_node;
936✔
495
                skill_node = skill_node->next_sibling("skill"))
780✔
496
        {
497
            auto skill_name = skill_node->first_attribute("id")->value();
780✔
498
            Skill::Skill skill_id = skill_name_to_id(skill_name);
780✔
499
            if (skill_id == Skill::no_skill) { throw std::runtime_error("unknown skill id:" + tuo::to_string(skill_name)); }
780✔
500
            auto x = node_value_float(skill_node, "x", 0.0);
780✔
501
            auto y = skill_faction(skill_node);
780✔
502
            auto n = node_value(skill_node, "n", 0);
780✔
503
            auto c = node_value(skill_node, "c", 0);
780✔
504
            auto s = skill_target_skill(skill_node, "s");
780✔
505
            auto s2 = skill_target_skill(skill_node, "s2");
780✔
506
            bool all(node_value(skill_node, "all", 0));
780✔
507
            effects.push_back({skill_id, x, y, n, c, s, s2, all});
780✔
508
        }
509
    }
510

511
    // Fixed fortresses (<fortress_card id="xxx"/>)
512
    std::vector<const Card*> fortress_cards;
143,910✔
513
    for (xml_node<>* fortress_card_node = node->first_node("fortress_card");
143,910✔
514
            fortress_card_node;
147,576✔
515
            fortress_card_node = fortress_card_node->next_sibling("fortress_card"))
3,666✔
516
    {
517
        const Card* card = all_cards.by_id(atoi(fortress_card_node->first_attribute("id")->value()));
7,332✔
518
        fortress_cards.push_back(card);
3,666✔
519
        upgrade_opportunities += card->m_top_level_card->m_level - card->m_level;
3,666✔
520
    }
521

522
    // Variable fortresses (<fortress_pool amount="x" replicates="y"> ... </fortress_pool>)
523
    for (xml_node<>* fortress_pool_node = node->first_node("fortress_pool");
143,910✔
524
            fortress_pool_node;
144,456✔
525
            fortress_pool_node = fortress_pool_node->next_sibling("fortress_pool"))
546✔
526
    {
527
        unsigned num_cards_from_pool(atoi(fortress_pool_node->first_attribute("amount")->value()));
1,092✔
528
        unsigned pool_replicates(fortress_pool_node->first_attribute("replicates")
546✔
529
            ? atoi(fortress_pool_node->first_attribute("replicates")->value())
546✔
530
            : 1);
546✔
531
        std::vector<const Card*> cards_from_pool;
546✔
532
        unsigned upgrade_points = 0;
546✔
533
        for (xml_node<>* card_node = fortress_pool_node->first_node("card");
546✔
534
                card_node;
2,418✔
535
                card_node = card_node->next_sibling("card"))
1,872✔
536
        {
537
            card = all_cards.by_id(atoi(card_node->value()));
3,744✔
538
            unsigned card_replicates(card_node->first_attribute("replicates") ? atoi(card_node->first_attribute("replicates")->value()) : 1);
3,432✔
539
            while (card_replicates --)
3,900✔
540
            {
541
                cards_from_pool.push_back(card);
2,028✔
542
                upgrade_points += card->m_top_level_card->m_level - card->m_level;
2,028✔
543
            }
544
        }
545
        some_forts.push_back(std::make_tuple(num_cards_from_pool, pool_replicates, cards_from_pool));
546✔
546
        upgrade_opportunities += upgrade_points * num_cards_from_pool * pool_replicates / cards_from_pool.size();
546✔
547
    }
546✔
548

549
    // Fixed cards (<always_include> ... </always_include>)
550
    xml_node<>* always_node{deck_node->first_node("always_include")};
143,910✔
551
    for (xml_node<>* card_node = (always_node ? always_node : deck_node)->first_node("card");
244,140✔
552
            card_node;
1,560,702✔
553
            card_node = card_node->next_sibling("card"))
1,416,792✔
554
    {
555
        card = all_cards.by_id(atoi(card_node->value()));
2,833,584✔
556
        unsigned replicates(card_node->first_attribute("replicates") ? atoi(card_node->first_attribute("replicates")->value()) : 1);
1,421,082✔
557
        while (replicates --)
2,842,554✔
558
        {
559
            always_cards.push_back(card);
1,425,762✔
560
            upgrade_opportunities += card->m_top_level_card->m_level - card->m_level;
1,425,762✔
561
        }
562
    }
563

564
    // Variable cards (<card_pool amount="x" replicates="y"> ... </card_pool>)
565
    for (xml_node<>* pool_node = deck_node->first_node("card_pool");
143,910✔
566
            pool_node;
146,328✔
567
            pool_node = pool_node->next_sibling("card_pool"))
2,418✔
568
    {
569
        unsigned num_cards_from_pool(atoi(pool_node->first_attribute("amount")->value()));
4,836✔
570
        unsigned pool_replicates(pool_node->first_attribute("replicates") ? atoi(pool_node->first_attribute("replicates")->value()) : 1);
2,886✔
571
        std::vector<const Card*> cards_from_pool;
2,418✔
572
        unsigned upgrade_points = 0;
2,418✔
573
        for (xml_node<>* card_node = pool_node->first_node("card");
2,418✔
574
                card_node;
14,664✔
575
                card_node = card_node->next_sibling("card"))
12,246✔
576
        {
577
            card = all_cards.by_id(atoi(card_node->value()));
24,492✔
578
            unsigned card_replicates(card_node->first_attribute("replicates") ? atoi(card_node->first_attribute("replicates")->value()) : 1);
17,004✔
579
            while (card_replicates --)
29,562✔
580
            {
581
                cards_from_pool.push_back(card);
17,316✔
582
                upgrade_points += card->m_top_level_card->m_level - card->m_level;
17,316✔
583
            }
584
        }
585
        some_cards.push_back(std::make_tuple(num_cards_from_pool, pool_replicates, cards_from_pool));
2,418✔
586
        upgrade_opportunities += upgrade_points * num_cards_from_pool * pool_replicates / cards_from_pool.size();
2,418✔
587
    }
2,418✔
588

589
    // Mission requirement
590
    xml_node<>* mission_req_node(node->first_node(decktype == DeckType::mission ? "req" : "mission_req"));
188,292✔
591
    unsigned mission_req(mission_req_node ? atoi(mission_req_node->value()) : 0);
243,360✔
592

593
    for (unsigned level = 1; level < max_level; ++ level)
1,131,546✔
594
    {
595
        std::string deck_name = base_deck_name + "-" + tuo::to_string(level);
1,975,272✔
596
        unsigned upgrade_points = ceil(upgrade_opportunities * (level - 1) / (double)(max_level - 1));
987,636✔
597
        decks.decks.push_back(Deck{all_cards, decktype, id, deck_name, upgrade_points, upgrade_opportunities});
2,962,908✔
598
        Deck* deck = &decks.decks.back();
987,636✔
599
        deck->set(commander_card, commander_max_level, always_cards, some_forts, some_cards, mission_req);
987,636✔
600
        deck->fortress_cards = fortress_cards;
987,636✔
601
        deck->effects = effects;
987,636✔
602
        deck->level = level;
987,636✔
603
        decks.add_deck(deck, deck_name);
987,636✔
604
        decks.add_deck(deck, decktype_names[decktype] + " #" + tuo::to_string(id) + "-" + tuo::to_string(level));
2,962,908✔
605
    }
987,636✔
606

607
    decks.decks.push_back(Deck{all_cards, decktype, id, base_deck_name});
431,730✔
608
    Deck* deck = &decks.decks.back();
143,910✔
609
    deck->set(commander_card, commander_max_level, always_cards, some_forts, some_cards, mission_req);
143,910✔
610
    deck->fortress_cards = fortress_cards;
143,910✔
611
    deck->effects = effects;
143,910✔
612
    deck->level = max_level;
143,910✔
613

614
    // upgrade cards for full-level missions/raids
615
    if (max_level > 1)
143,910✔
616
    {
617
        while (deck->commander->m_level < commander_max_level)
344,448✔
618
        { deck->commander = deck->commander->upgraded(); }
481,572✔
619
        for (auto && card: deck->fortress_cards)
107,328✔
620
        { card = card->m_top_level_card; }
3,666✔
621
        for (auto && card: deck->cards)
1,129,440✔
622
        { card = card->m_top_level_card; }
1,025,778✔
623
        for (auto && pool: deck->variable_forts)
104,208✔
624
        {
625
            for (auto && card: std::get<2>(pool))
2,574✔
626
            { card = card->m_top_level_card; }
2,028✔
627
        }
628
        for (auto && pool: deck->variable_cards)
105,144✔
629
        {
630
            for (auto && card: std::get<2>(pool))
15,678✔
631
            { card = card->m_top_level_card; }
14,196✔
632
        }
633
    }
634

635
    decks.add_deck(deck, base_deck_name);
143,910✔
636
    decks.add_deck(deck, base_deck_name + "-" + tuo::to_string(max_level));
287,820✔
637
    decks.add_deck(deck, decktype_names[decktype] + " #" + tuo::to_string(id));
287,820✔
638
    decks.add_deck(deck, decktype_names[decktype] + " #" + tuo::to_string(id) + "-" + tuo::to_string(max_level));
431,730✔
639
    decks.by_type_id[{decktype, id}] = deck;
143,910✔
640
    return deck;
143,910✔
641
}
143,910✔
642
//------------------------------------------------------------------------------
643
void read_missions(Decks& decks, const Cards& all_cards, const std::string & filename, bool do_warn_on_missing=true)
78✔
644
{
645
    std::vector<char> buffer;
78✔
646
    xml_document<> doc;
78✔
647
    parse_file(filename.c_str(), buffer, doc, do_warn_on_missing);
156✔
648
    xml_node<>* root = doc.first_node();
78✔
649

650
    if (!root)
78✔
651
    {
652
        return;
×
653
    }
654

655
    for (xml_node<>* mission_node = root->first_node("mission");
78✔
656
        mission_node;
99,840✔
657
        mission_node = mission_node->next_sibling("mission"))
99,762✔
658
    {
659
        std::vector<unsigned> card_ids;
99,762✔
660
        xml_node<>* id_node(mission_node->first_node("id"));
99,762✔
661
        assert(id_node);
99,762✔
662
        unsigned id(id_node ? atoi(id_node->value()) : 0);
199,524✔
663
        xml_node<>* name_node(mission_node->first_node("name"));
99,762✔
664
        std::string deck_name{name_node->value()};
199,524✔
665
        try
99,762✔
666
        {
667
            read_deck(decks, all_cards, mission_node, DeckType::mission, id, deck_name);
299,286✔
668
        }
669
        catch (const std::runtime_error& e)
234✔
670
        {
671
            std::cerr << "WARNING: Failed to parse mission [" << deck_name << "] in file " << filename << ": [" << e.what() << "]. Skip the mission.\n";
234✔
672
            continue;
234✔
673
        }
234✔
674
    }
99,762✔
675
}
78✔
676
//------------------------------------------------------------------------------
677
void read_raids(Decks& decks, const Cards& all_cards, const std::string & filename, bool do_warn_on_missing=true)
78✔
678
{
679
    std::vector<char> buffer;
78✔
680
    xml_document<> doc;
78✔
681
    parse_file(filename.c_str(), buffer, doc, do_warn_on_missing);
156✔
682
    xml_node<>* root = doc.first_node();
78✔
683

684
    if (!root)
78✔
685
    {
686
        return;
×
687
    }
688

689
    for (xml_node<>* raid_node = root->first_node("raid");
78✔
690
        raid_node;
3,744✔
691
        raid_node = raid_node->next_sibling("raid"))
3,666✔
692
    {
693
        xml_node<>* id_node(raid_node->first_node("id"));
3,666✔
694
        assert(id_node);
3,666✔
695
        unsigned id(id_node ? atoi(id_node->value()) : 0);
7,332✔
696
        xml_node<>* name_node(raid_node->first_node("name"));
3,666✔
697
        std::string deck_name{name_node->value()};
7,332✔
698
        try
3,666✔
699
        {
700
            read_deck(decks, all_cards, raid_node, DeckType::raid, id, deck_name);
10,998✔
701
        }
702
        catch (const std::runtime_error& e)
78✔
703
        {
704
            std::cerr << "WARNING: Failed to parse raid [" << deck_name << "] in file " << filename << ": [" << e.what() << "]. Skip the raid.\n";
78✔
705
            continue;
78✔
706
        }
78✔
707
    }
3,666✔
708

709
    for (xml_node<>* campaign_node = root->first_node("campaign");
78✔
710
        campaign_node;
20,748✔
711
        campaign_node = campaign_node->next_sibling("campaign"))
20,670✔
712
    {
713
        xml_node<>* id_node(campaign_node->first_node("id"));
20,670✔
714
        unsigned id(id_node ? atoi(id_node->value()) : 0);
39,702✔
715
        for (auto && name_node = campaign_node->first_node("name");
20,670✔
716
            name_node;
61,464✔
717
            name_node = name_node->next_sibling("name"))
40,794✔
718
        {
719
            try
40,794✔
720
            {
721
                read_deck(decks, all_cards, campaign_node, DeckType::campaign, id, name_node->value());
122,382✔
722
            }
723
            catch (const std::runtime_error& e)
×
724
            {
725
                std::cerr << "WARNING: Failed to parse campaign [" << name_node->value() << "] in file " << filename << ": [" << e.what() << "]. Skip the campaign.\n";
×
726
                continue;
×
727
            }
×
728
        }
729
    }
730
}
78✔
731

732
//------------------------------------------------------------------------------
733
void load_recipes_xml(Cards& all_cards, const std::string & filename, bool do_warn_on_missing=true)
78✔
734
{
735
    std::vector<char> buffer;
78✔
736
    xml_document<> doc;
78✔
737
    parse_file(filename, buffer, doc, do_warn_on_missing);
78✔
738
    xml_node<>* root = doc.first_node();
78✔
739

740
    if (!root)
78✔
741
    {
742
        return;
×
743
    }
744

745
    for (xml_node<>* recipe_node = root->first_node("fusion_recipe");
78✔
746
        recipe_node;
390,000✔
747
        recipe_node = recipe_node->next_sibling("fusion_recipe"))
389,922✔
748
    {
749
        xml_node<>* card_id_node(recipe_node->first_node("card_id"));
389,922✔
750
        if (!card_id_node) { continue; }
404,430✔
751
        unsigned card_id(atoi(card_id_node->value()));
779,844✔
752
        Card* card = all_cards.cards_by_id[card_id];
389,922✔
753
        if (!card) {
389,922✔
754
            std::cerr << "Could not find card by id " << card_id << std::endl;
14,508✔
755
            continue;
14,508✔
756
        }
757

758
        for (xml_node<>* resource_node = recipe_node->first_node("resource");
375,414✔
759
                resource_node;
789,672✔
760
                resource_node = resource_node->next_sibling("resource"))
414,258✔
761
        {
762
            unsigned card_id(node_value(resource_node, "card_id"));
414,258✔
763
            unsigned number(node_value(resource_node, "number"));
414,258✔
764
            if (card_id == 0 || number == 0) { continue; }
414,258✔
765
            if (!card->is_low_level_card())
414,258✔
766
            {
767
                card->m_base_id = card->m_id;
78✔
768
                card->m_recipe_cards.clear();
78✔
769
            }
770
            Card* material_card = all_cards.cards_by_id[card_id];
414,258✔
771
            card->m_recipe_cards[material_card] += number;
414,258✔
772
            material_card->m_used_for_cards[card] += number;
414,258✔
773
        }
774
    }
775
}
78✔
776

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