├── .gitignore ├── Makefile ├── Makefile-debug ├── Makefile.osx ├── README.md ├── card.h ├── cards.cpp ├── cards.h ├── data └── raids.xml ├── deck.cpp ├── deck.h ├── rapidxml.hpp ├── rapidxml_license.txt ├── read.cpp ├── read.h ├── sim.cpp ├── sim.h ├── tyrant.cpp ├── tyrant.h ├── tyrant_optimize.cpp ├── update_xml.sh ├── xml.cpp └── xml.h /.gitignore: -------------------------------------------------------------------------------- 1 | obj/** 2 | tu_optimize 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MAIN := tuo.exe 2 | SRCS := $(wildcard *.cpp) 3 | OBJS := $(patsubst %.cpp,obj/%.o,$(SRCS)) 4 | INCS := $(wildcard *.h) 5 | 6 | CPPFLAGS := -Wall -Werror -std=gnu++11 -O3 -DNDEBUG 7 | LDFLAGS := -lboost_system -lboost_thread -lboost_filesystem -lboost_regex 8 | 9 | all: $(MAIN) 10 | 11 | obj/%.o: %.cpp $(INCS) 12 | $(CXX) $(CPPFLAGS) -o $@ -c $< 13 | 14 | $(MAIN): $(OBJS) 15 | $(CXX) -o $@ $(OBJS) $(LDFLAGS) 16 | 17 | clean: 18 | del /q $(MAIN).exe obj\*.o 19 | -------------------------------------------------------------------------------- /Makefile-debug: -------------------------------------------------------------------------------- 1 | MAIN := tuodebug.exe 2 | SRCS := $(wildcard *.cpp) 3 | OBJS := $(patsubst %.cpp,obj-debug/%.o,$(SRCS)) 4 | INCS := $(wildcard *.h) 5 | 6 | CPPFLAGS := -Wall -Werror -std=gnu++11 -O3 7 | LDFLAGS := -lboost_system -lboost_thread -lboost_filesystem -lboost_regex 8 | 9 | all: $(MAIN) 10 | 11 | obj-debug/%.o: %.cpp $(INCS) 12 | $(CXX) $(CPPFLAGS) -o $@ -c $< 13 | 14 | $(MAIN): $(OBJS) 15 | $(CXX) -o $@ $(OBJS) $(LDFLAGS) 16 | 17 | clean: 18 | del /q $(MAIN).exe obj-debug\*.o 19 | -------------------------------------------------------------------------------- /Makefile.osx: -------------------------------------------------------------------------------- 1 | MAIN := tuo 2 | SRCS := $(wildcard *.cpp) 3 | OBJS := $(patsubst %.cpp,obj/%.o,$(SRCS)) 4 | INCS := $(wildcard *.h) 5 | 6 | CPPFLAGS := -Wall -Werror -std=c++11 -stdlib=libc++ -O3 -I/usr/local/include -Wno-deprecated-register -DNDEBUG 7 | LDFLAGS := lib/libboost_system-mt.a lib/libboost_thread-mt.a lib/libboost_filesystem-mt.a lib/libboost_regex-mt.a -L/usr/local/lib -Bstatic 8 | 9 | all: $(MAIN) 10 | 11 | obj/%.o: %.cpp ${INCS} 12 | $(CXX) $(CPPFLAGS) -o $@ -c $< 13 | 14 | $(MAIN): $(OBJS) 15 | $(CXX) -o $@ $(OBJS) $(LDFLAGS) 16 | 17 | clean: 18 | rm -f $(MAIN) obj/*.o 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tyrant_optimize 2 | =============== -------------------------------------------------------------------------------- /card.h: -------------------------------------------------------------------------------- 1 | #ifndef CARD_H_INCLUDED 2 | #define CARD_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include "tyrant.h" 8 | 9 | class Card 10 | { 11 | public: 12 | unsigned m_attack; 13 | unsigned m_base_id; // The id of the original card if a card is unique and alt/upgraded. The own id of the card otherwise. 14 | unsigned m_delay; 15 | Faction m_faction; 16 | unsigned m_health; 17 | unsigned m_id; 18 | unsigned m_level; 19 | unsigned m_fusion_level; 20 | std::string m_name; 21 | unsigned m_rarity; 22 | unsigned m_set; 23 | std::vector m_skills; 24 | unsigned m_skill_value[num_skills]; 25 | CardType::CardType m_type; 26 | const Card* m_top_level_card; // [TU] corresponding full-level card 27 | unsigned m_recipe_cost; 28 | std::map m_recipe_cards; 29 | std::map m_used_for_cards; 30 | 31 | public: 32 | Card() : 33 | m_attack(0), 34 | m_base_id(0), 35 | m_delay(0), 36 | m_faction(imperial), 37 | m_health(0), 38 | m_id(0), 39 | m_level(1), 40 | m_fusion_level(0), 41 | m_name(""), 42 | m_rarity(1), 43 | m_set(0), 44 | m_skills(), 45 | m_type(CardType::assault), 46 | m_top_level_card(this), 47 | m_recipe_cost(0), 48 | m_recipe_cards(), 49 | m_used_for_cards() 50 | { 51 | std::memset(m_skill_value, 0, sizeof m_skill_value); 52 | } 53 | 54 | void add_skill(Skill id, unsigned x, Faction y, unsigned n, unsigned c, Skill s, Skill s2, bool all); 55 | const Card* upgraded() const { return this == m_top_level_card ? this : m_used_for_cards.begin()->first; } 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /cards.cpp: -------------------------------------------------------------------------------- 1 | #include "cards.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "tyrant.h" 11 | #include "card.h" 12 | 13 | std::string simplify_name(const std::string& card_name) 14 | { 15 | std::string simple_name; 16 | for(auto c : card_name) 17 | { 18 | if(!strchr(";:,\"'! ", c)) 19 | { 20 | simple_name += ::tolower(c); 21 | } 22 | } 23 | return(simple_name); 24 | } 25 | 26 | std::list get_abbreviations(const std::string& name) 27 | { 28 | std::list abbr_list; 29 | boost::tokenizer> word_token{name, boost::char_delimiters_separator{false, " -", ""}}; 30 | std::string initial; 31 | auto token_iter = word_token.begin(); 32 | for(; token_iter != word_token.end(); ++token_iter) 33 | { 34 | abbr_list.push_back(simplify_name(std::string{token_iter->begin(), token_iter->end()})); 35 | initial += *token_iter->begin(); 36 | } 37 | abbr_list.push_back(simplify_name(initial)); 38 | return(abbr_list); 39 | } 40 | 41 | //------------------------------------------------------------------------------ 42 | Cards::~Cards() 43 | { 44 | for (Card* c: all_cards) { delete(c); } 45 | } 46 | 47 | const Card* Cards::by_id(unsigned id) const 48 | { 49 | const auto cardIter = cards_by_id.find(id); 50 | if(cardIter == cards_by_id.end()) 51 | { 52 | throw std::runtime_error("No card with id " + to_string(id)); 53 | } 54 | else 55 | { 56 | return(cardIter->second); 57 | } 58 | } 59 | //------------------------------------------------------------------------------ 60 | void Cards::organize() 61 | { 62 | cards_by_id.clear(); 63 | player_cards.clear(); 64 | cards_by_name.clear(); 65 | player_commanders.clear(); 66 | player_assaults.clear(); 67 | player_structures.clear(); 68 | // Round 1: set cards_by_id 69 | for(Card* card: all_cards) 70 | { 71 | cards_by_id[card->m_id] = card; 72 | } 73 | // Round 2: depend on cards_by_id / by_id(); update m_name, [TU] m_top_level_card etc.; set cards_by_name; 74 | for(Card* card: all_cards) 75 | { 76 | // Remove delimiters from card names 77 | size_t pos; 78 | while((pos = card->m_name.find_first_of(";:,")) != std::string::npos) 79 | { 80 | card->m_name.erase(pos, 1); 81 | } 82 | // set m_top_level_card for non base cards 83 | card->m_top_level_card = by_id(card->m_base_id)->m_top_level_card; 84 | // Cards available ("visible") to players have priority 85 | std::string base_name = card->m_name; 86 | if (card == card->m_top_level_card) 87 | { 88 | add_card(card, card->m_name + "-" + to_string(card->m_level)); 89 | } 90 | else 91 | { 92 | card->m_name += "-" + to_string(card->m_level); 93 | } 94 | add_card(card, card->m_name); 95 | } 96 | #if 0 // TODO refactor precedence 97 | // Round 3: depend on cards_by_name; set abbreviations 98 | for(Card* card: cards) 99 | { 100 | // generate abbreviations 101 | if(card->m_set > 0) 102 | { 103 | for(auto&& abbr_name : get_abbreviations(card->m_name)) 104 | { 105 | if(abbr_name.length() > 1 && cards_by_name.find(abbr_name) == cards_by_name.end()) 106 | { 107 | player_cards_abbr[abbr_name] = card->m_name; 108 | } 109 | } 110 | } 111 | } 112 | #endif 113 | } 114 | 115 | void Cards::add_card(Card * card, const std::string & name) 116 | { 117 | std::string simple_name{simplify_name(name)}; 118 | auto card_itr = cards_by_name.find(simple_name); 119 | signed old_visible = card_itr == cards_by_name.end() ? -1 : visible_cardset.count(card_itr->second->m_set); 120 | signed new_visible = visible_cardset.count(card->m_set); 121 | if (card_itr != cards_by_name.end()) 122 | { 123 | if (old_visible == new_visible) 124 | { 125 | ambiguous_names.insert(simple_name); 126 | } 127 | _DEBUG_MSG(2, "Duplicated card name \"%s\" [%u] set=%u (visible=%u) : [%u] set=%u (visible=%u)\n", name.c_str(), card_itr->second->m_id, card_itr->second->m_set, old_visible, card->m_id, card->m_set, new_visible); 128 | } 129 | else if (old_visible < new_visible) 130 | { 131 | ambiguous_names.erase(simple_name); 132 | cards_by_name[simple_name] = card; 133 | if (new_visible) 134 | { 135 | player_cards.push_back(card); 136 | switch(card->m_type) 137 | { 138 | case CardType::commander: { 139 | player_commanders.push_back(card); 140 | break; 141 | } 142 | case CardType::assault: { 143 | player_assaults.push_back(card); 144 | break; 145 | } 146 | case CardType::structure: { 147 | player_structures.push_back(card); 148 | break; 149 | } 150 | case CardType::num_cardtypes: { 151 | throw card->m_type; 152 | break; 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | // class Card 160 | void Card::add_skill(Skill id, unsigned x, Faction y, unsigned n, unsigned c, Skill s, Skill s2, bool all) 161 | { 162 | for(auto it = m_skills.begin(); it != m_skills.end(); ++ it) 163 | { 164 | if(it->id == id) 165 | { 166 | m_skills.erase(it); 167 | break; 168 | } 169 | } 170 | m_skills.push_back({id, x, y, n, c, s, s2, all}); 171 | m_skill_value[id] = x ? x : n ? n : 1; 172 | } 173 | 174 | -------------------------------------------------------------------------------- /cards.h: -------------------------------------------------------------------------------- 1 | #ifndef CARDS_H_INCLUDED 2 | #define CARDS_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class Card; 10 | 11 | class Cards 12 | { 13 | public: 14 | ~Cards(); 15 | 16 | std::vector all_cards; 17 | std::map cards_by_id; 18 | std::vector player_cards; 19 | std::map cards_by_name; 20 | std::vector player_commanders; 21 | std::vector player_assaults; 22 | std::vector player_structures; 23 | std::map player_cards_abbr; 24 | std::unordered_set visible_cardset; 25 | std::unordered_set ambiguous_names; 26 | const Card * by_id(unsigned id) const; 27 | void organize(); 28 | void add_card(Card * card, const std::string & name); 29 | }; 30 | 31 | std::string simplify_name(const std::string& card_name); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /deck.cpp: -------------------------------------------------------------------------------- 1 | #include "deck.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "card.h" 12 | #include "cards.h" 13 | #include "read.h" 14 | 15 | template 16 | void partial_shuffle(RandomAccessIterator first, RandomAccessIterator middle, 17 | RandomAccessIterator last, 18 | UniformRandomNumberGenerator&& g) 19 | { 20 | typedef typename std::iterator_traits::difference_type diff_t; 21 | typedef typename std::make_unsigned::type udiff_t; 22 | typedef typename std::uniform_int_distribution distr_t; 23 | typedef typename distr_t::param_type param_t; 24 | 25 | distr_t D; 26 | diff_t m = middle - first; 27 | diff_t n = last - first; 28 | for (diff_t i = 0; i < m; ++i) 29 | { 30 | std::swap(first[i], first[D(g, param_t(i, n-1))]); 31 | } 32 | } 33 | 34 | //------------------------------------------------------------------------------ 35 | const char* base64_chars = 36 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 37 | "abcdefghijklmnopqrstuvwxyz" 38 | "0123456789+/"; 39 | const char* wmt_b64_magic_chars = "-.~!*"; 40 | 41 | // Converts cards in `hash' to a deck. 42 | // Stores resulting card IDs in `ids'. 43 | void hash_to_ids_wmt_b64(const char* hash, std::vector& ids) 44 | { 45 | unsigned int last_id = 0; 46 | const char* pc = hash; 47 | 48 | while(*pc) 49 | { 50 | unsigned id_plus = 0; 51 | const char* pmagic = strchr(wmt_b64_magic_chars, *pc); 52 | if(pmagic) 53 | { 54 | ++ pc; 55 | id_plus = 4000 * (pmagic - wmt_b64_magic_chars + 1); 56 | } 57 | if(!*pc || !*(pc + 1)) 58 | { 59 | throw std::runtime_error("Invalid hash length"); 60 | } 61 | const char* p0 = strchr(base64_chars, *pc); 62 | const char* p1 = strchr(base64_chars, *(pc + 1)); 63 | if (!p0 || !p1) 64 | { 65 | throw std::runtime_error("Invalid hash character"); 66 | } 67 | pc += 2; 68 | size_t index0 = p0 - base64_chars; 69 | size_t index1 = p1 - base64_chars; 70 | unsigned int id = (index0 << 6) + index1; 71 | 72 | if (id < 4001) 73 | { 74 | id += id_plus; 75 | ids.push_back(id); 76 | last_id = id; 77 | } 78 | else for (unsigned int j = 0; j < id - 4001; ++j) 79 | { 80 | ids.push_back(last_id); 81 | } 82 | } 83 | } 84 | 85 | void encode_id_wmt_b64(std::stringstream &ios, unsigned card_id) 86 | { 87 | if(card_id > 4000) 88 | { 89 | ios << wmt_b64_magic_chars[(card_id - 1) / 4000 - 1]; 90 | card_id = (card_id - 1) % 4000 + 1; 91 | } 92 | ios << base64_chars[card_id / 64]; 93 | ios << base64_chars[card_id % 64]; 94 | } 95 | 96 | void encode_deck_wmt_b64(std::stringstream &ios, const Card* commander, std::vector cards) 97 | { 98 | if (commander) 99 | { 100 | encode_id_wmt_b64(ios, commander->m_id); 101 | } 102 | unsigned last_id = 0; 103 | unsigned num_repeat = 0; 104 | for(const Card* card: cards) 105 | { 106 | auto card_id = card->m_id; 107 | if(card_id == last_id) 108 | { 109 | ++ num_repeat; 110 | } 111 | else 112 | { 113 | if(num_repeat > 1) 114 | { 115 | ios << base64_chars[(num_repeat + 4000) / 64]; 116 | ios << base64_chars[(num_repeat + 4000) % 64]; 117 | } 118 | last_id = card_id; 119 | num_repeat = 1; 120 | encode_id_wmt_b64(ios, card_id); 121 | } 122 | } 123 | if(num_repeat > 1) 124 | { 125 | ios << base64_chars[(num_repeat + 4000) / 64]; 126 | ios << base64_chars[(num_repeat + 4000) % 64]; 127 | } 128 | } 129 | 130 | void hash_to_ids_ext_b64(const char* hash, std::vector& ids) 131 | { 132 | const char* pc = hash; 133 | while (*pc) 134 | { 135 | unsigned id = 0; 136 | unsigned factor = 1; 137 | const char* p = strchr(base64_chars, *pc); 138 | if (!p) 139 | { throw std::runtime_error("Invalid hash character"); } 140 | size_t d = p - base64_chars; 141 | while (d < 32) 142 | { 143 | id += factor * d; 144 | factor *= 32; 145 | ++ pc; 146 | p = strchr(base64_chars, *pc); 147 | if (!p) 148 | { throw std::runtime_error("Invalid hash character"); } 149 | d = p - base64_chars; 150 | } 151 | id += factor * (d - 32); 152 | ++ pc; 153 | ids.push_back(id); 154 | } 155 | } 156 | 157 | void encode_id_ext_b64(std::stringstream &ios, unsigned card_id) 158 | { 159 | while (card_id >= 32) 160 | { 161 | ios << base64_chars[card_id % 32]; 162 | card_id /= 32; 163 | } 164 | ios << base64_chars[card_id + 32]; 165 | } 166 | 167 | void encode_deck_ext_b64(std::stringstream &ios, const Card* commander, std::vector cards) 168 | { 169 | if (commander) 170 | { 171 | encode_id_ext_b64(ios, commander->m_id); 172 | } 173 | for (const Card* card: cards) 174 | { 175 | encode_id_ext_b64(ios, card->m_id); 176 | } 177 | } 178 | 179 | void hash_to_ids_ddd_b64(const char* hash, std::vector& ids) 180 | { 181 | const char* pc = hash; 182 | while(*pc) 183 | { 184 | if(!*pc || !*(pc + 1) || !*(pc + 2)) 185 | { 186 | throw std::runtime_error("Invalid hash length"); 187 | } 188 | const char* p0 = strchr(base64_chars, *pc); 189 | const char* p1 = strchr(base64_chars, *(pc + 1)); 190 | const char* p2 = strchr(base64_chars, *(pc + 2)); 191 | if (!p0 || !p1 || !p2) 192 | { 193 | throw std::runtime_error("Invalid hash character"); 194 | } 195 | pc += 3; 196 | size_t index0 = p0 - base64_chars; 197 | size_t index1 = p1 - base64_chars; 198 | size_t index2 = p2 - base64_chars; 199 | unsigned int id = (index0 << 12) + (index1 << 6) + index2; 200 | ids.push_back(id); 201 | } 202 | } 203 | 204 | void encode_id_ddd_b64(std::stringstream &ios, unsigned card_id) 205 | { 206 | ios << base64_chars[card_id / 4096]; 207 | ios << base64_chars[card_id % 4096 / 64]; 208 | ios << base64_chars[card_id % 64]; 209 | } 210 | 211 | void encode_deck_ddd_b64(std::stringstream &ios, const Card* commander, std::vector cards) 212 | { 213 | if (commander) 214 | { 215 | encode_id_ddd_b64(ios, commander->m_id); 216 | } 217 | for (const Card* card: cards) 218 | { 219 | encode_id_ddd_b64(ios, card->m_id); 220 | } 221 | } 222 | 223 | DeckDecoder hash_to_ids = hash_to_ids_ext_b64; 224 | DeckEncoder encode_deck = encode_deck_ext_b64; 225 | 226 | namespace range = boost::range; 227 | 228 | void Deck::set(const std::vector& ids, const std::map &marks) 229 | { 230 | commander = nullptr; 231 | strategy = DeckStrategy::random; 232 | for(auto id: ids) 233 | { 234 | const Card* card{all_cards.by_id(id)}; 235 | if(card->m_type == CardType::commander) 236 | { 237 | if (commander == nullptr) 238 | { 239 | commander = card; 240 | } 241 | else 242 | { 243 | std::cerr << "WARNING: Ignoring additional commander " << card->m_name << " (" << commander->m_name << " already in deck)\n"; 244 | } 245 | } 246 | else 247 | { 248 | cards.emplace_back(card); 249 | } 250 | } 251 | if (commander == nullptr) 252 | { 253 | throw std::runtime_error("While constructing a deck: no commander found"); 254 | } 255 | commander_max_level = commander->m_top_level_card->m_level; 256 | deck_size = cards.size(); 257 | card_marks = marks; 258 | } 259 | 260 | void Deck::set(const std::string& deck_string_) 261 | { 262 | deck_string = deck_string_; 263 | } 264 | 265 | void Deck::resolve() 266 | { 267 | if (commander != nullptr) 268 | { 269 | return; 270 | } 271 | auto && id_marks = string_to_ids(all_cards, deck_string, short_description()); 272 | set(id_marks.first, id_marks.second); 273 | deck_string.clear(); 274 | } 275 | 276 | void Deck::shrink(const unsigned deck_len) 277 | { 278 | if (cards.size() > deck_len) 279 | { 280 | cards.resize(deck_len); 281 | } 282 | } 283 | 284 | void Deck::set_vip_cards(const std::string& deck_string) 285 | { 286 | auto && id_marks = string_to_ids(all_cards, deck_string, "vip"); 287 | for (const auto & cid : id_marks.first) 288 | { 289 | vip_cards.insert(cid); 290 | } 291 | } 292 | 293 | void Deck::set_given_hand(const std::string& deck_string) 294 | { 295 | auto && id_marks = string_to_ids(all_cards, deck_string, "hand"); 296 | given_hand = id_marks.first; 297 | } 298 | 299 | void Deck::add_forts(const std::string& deck_string) 300 | { 301 | auto && id_marks = string_to_ids(all_cards, deck_string, "fort_cards"); 302 | for (auto id: id_marks.first) 303 | { 304 | fort_cards.push_back(all_cards.by_id(id)); 305 | } 306 | } 307 | 308 | std::string Deck::hash() const 309 | { 310 | std::stringstream ios; 311 | if (strategy == DeckStrategy::random) 312 | { 313 | auto sorted_cards = cards; 314 | std::sort(sorted_cards.begin(), sorted_cards.end(), [](const Card* a, const Card* b) { return a->m_id < b->m_id; }); 315 | encode_deck(ios, commander, sorted_cards); 316 | } 317 | else 318 | { 319 | encode_deck(ios, commander, cards); 320 | } 321 | return ios.str(); 322 | } 323 | 324 | std::string Deck::short_description() const 325 | { 326 | std::stringstream ios; 327 | ios << decktype_names[decktype]; 328 | if(id > 0) { ios << " #" << id; } 329 | if(!name.empty()) { ios << " \"" << name << "\""; } 330 | if(deck_string.empty()) 331 | { 332 | if(variable_cards.empty()) { ios << ": " << hash(); } 333 | } 334 | else 335 | { 336 | ios << ": " << deck_string; 337 | } 338 | return ios.str(); 339 | } 340 | 341 | std::string Deck::medium_description() const 342 | { 343 | std::stringstream ios; 344 | ios << short_description() << std::endl; 345 | if (commander) 346 | { 347 | ios << commander->m_name; 348 | } 349 | else 350 | { 351 | ios << "No commander"; 352 | } 353 | for (const Card * card: fort_cards) 354 | { 355 | ios << ", " << card->m_name; 356 | } 357 | for(const Card * card: cards) 358 | { 359 | ios << ", " << card->m_name; 360 | } 361 | unsigned num_pool_cards = 0; 362 | for(auto& pool: variable_cards) 363 | { 364 | num_pool_cards += std::get<0>(pool) * std::get<1>(pool); 365 | } 366 | if(num_pool_cards > 0) 367 | { 368 | ios << ", and " << num_pool_cards << " cards from pool"; 369 | } 370 | if (upgrade_points > 0) 371 | { 372 | ios << " +" << upgrade_points << "/" << upgrade_opportunities; 373 | } 374 | return ios.str(); 375 | } 376 | 377 | extern std::string card_description(const Cards& all_cards, const Card* c); 378 | 379 | std::string Deck::long_description() const 380 | { 381 | std::stringstream ios; 382 | ios << medium_description() << "\n"; 383 | if (commander) 384 | { 385 | show_upgrades(ios, commander, commander_max_level, ""); 386 | } 387 | else 388 | { 389 | ios << "No commander\n"; 390 | } 391 | for (const Card * card: fort_cards) 392 | { 393 | show_upgrades(ios, card, card->m_top_level_card->m_level, ""); 394 | } 395 | for(const Card* card: cards) 396 | { 397 | show_upgrades(ios, card, card->m_top_level_card->m_level, " "); 398 | } 399 | for(auto& pool: variable_cards) 400 | { 401 | if (std::get<1>(pool) > 1) 402 | { 403 | ios << std::get<1>(pool) << " copies of each of "; 404 | } 405 | ios << std::get<0>(pool) << " in:\n"; 406 | for(auto& card: std::get<2>(pool)) 407 | { 408 | show_upgrades(ios, card, card->m_top_level_card->m_level, " "); 409 | } 410 | } 411 | return ios.str(); 412 | } 413 | 414 | void Deck::show_upgrades(std::stringstream &ios, const Card* card, unsigned card_max_level, const char * leading_chars) const 415 | { 416 | ios << leading_chars << card_description(all_cards, card) << "\n"; 417 | if (upgrade_points == 0 || card->m_level == card_max_level) 418 | { 419 | return; 420 | } 421 | if (debug_print < 2 && decktype != DeckType::raid) 422 | { 423 | while (card->m_level != card_max_level) 424 | { card = card->upgraded(); } 425 | ios << leading_chars << "-> " << card_description(all_cards, card) << "\n"; 426 | return; 427 | } 428 | // nCm * p^m / q^(n-m) 429 | double p = 1.0 * upgrade_points / upgrade_opportunities; 430 | double q = 1.0 - p; 431 | unsigned n = card_max_level - card->m_level; 432 | unsigned m = 0; 433 | double prob = 100.0 * pow(q, n); 434 | ios << leading_chars << std::fixed << std::setprecision(2) << std::setw(5) << prob << "% no up\n"; 435 | while (card->m_level != card_max_level) 436 | { 437 | card = card->upgraded(); 438 | ++m; 439 | prob = prob * (n + 1 - m) / m * p / q; 440 | ios << leading_chars << std::setw(5) << prob << "% -> " << card_description(all_cards, card) << "\n"; 441 | } 442 | } 443 | 444 | Deck* Deck::clone() const 445 | { 446 | return(new Deck(*this)); 447 | } 448 | 449 | const Card* Deck::next() 450 | { 451 | if(shuffled_cards.empty()) 452 | { 453 | return(nullptr); 454 | } 455 | else if(strategy == DeckStrategy::random || strategy == DeckStrategy::exact_ordered) 456 | { 457 | const Card* card = shuffled_cards.front(); 458 | shuffled_cards.pop_front(); 459 | return(card); 460 | } 461 | else if(strategy == DeckStrategy::ordered) 462 | { 463 | auto cardIter = std::min_element(shuffled_cards.begin(), shuffled_cards.begin() + std::min(3u, shuffled_cards.size()), [this](const Card* card1, const Card* card2) -> bool 464 | { 465 | auto card1_order = order.find(card1->m_id); 466 | if(!card1_order->second.empty()) 467 | { 468 | auto card2_order = order.find(card2->m_id); 469 | if(!card2_order->second.empty()) 470 | { 471 | return(*card1_order->second.begin() < *card2_order->second.begin()); 472 | } 473 | else 474 | { 475 | return(true); 476 | } 477 | } 478 | else 479 | { 480 | return(false); 481 | } 482 | }); 483 | auto card = *cardIter; 484 | shuffled_cards.erase(cardIter); 485 | auto card_order = order.find(card->m_id); 486 | if(!card_order->second.empty()) 487 | { 488 | card_order->second.erase(card_order->second.begin()); 489 | } 490 | return(card); 491 | } 492 | throw std::runtime_error("Unknown strategy for deck."); 493 | } 494 | 495 | const Card* Deck::upgrade_card(const Card* card, unsigned card_max_level, std::mt19937& re, unsigned &remaining_upgrade_points, unsigned &remaining_upgrade_opportunities) 496 | { 497 | unsigned oppos = card_max_level - card->m_level; 498 | if (remaining_upgrade_points > 0) 499 | { 500 | for (; oppos > 0; -- oppos) 501 | { 502 | std::mt19937::result_type rnd = re(); 503 | if (rnd % remaining_upgrade_opportunities < remaining_upgrade_points) 504 | { 505 | card = card->upgraded(); 506 | -- remaining_upgrade_points; 507 | } 508 | -- remaining_upgrade_opportunities; 509 | } 510 | } 511 | return card; 512 | } 513 | 514 | void Deck::shuffle(std::mt19937& re) 515 | { 516 | shuffled_commander = commander; 517 | shuffled_forts.clear(); 518 | boost::insert(shuffled_forts, shuffled_forts.end(), fort_cards); 519 | shuffled_cards.clear(); 520 | boost::insert(shuffled_cards, shuffled_cards.end(), cards); 521 | if(!variable_cards.empty()) 522 | { 523 | if(strategy != DeckStrategy::random) 524 | { 525 | throw std::runtime_error("Support only random strategy for raid/quest deck."); 526 | } 527 | for(auto& card_pool: variable_cards) 528 | { 529 | auto & amount = std::get<0>(card_pool); 530 | auto & replicates = std::get<1>(card_pool); 531 | auto & card_list = std::get<2>(card_pool); 532 | assert(amount <= card_list.size()); 533 | partial_shuffle(card_list.begin(), card_list.begin() + amount, card_list.end(), re); 534 | for (unsigned rep = 0; rep < replicates; ++ rep) 535 | { 536 | shuffled_cards.insert(shuffled_cards.end(), card_list.begin(), card_list.begin() + amount); 537 | } 538 | } 539 | } 540 | if (upgrade_points > 0) 541 | { 542 | unsigned remaining_upgrade_points = upgrade_points; 543 | unsigned remaining_upgrade_opportunities = upgrade_opportunities; 544 | shuffled_commander = upgrade_card(commander, commander_max_level, re, remaining_upgrade_points, remaining_upgrade_opportunities); 545 | for (auto && card: shuffled_forts) 546 | { 547 | card = upgrade_card(card, card->m_top_level_card->m_level, re, remaining_upgrade_points, remaining_upgrade_opportunities); 548 | } 549 | for (auto && card: shuffled_cards) 550 | { 551 | card = upgrade_card(card, card->m_top_level_card->m_level, re, remaining_upgrade_points, remaining_upgrade_opportunities); 552 | } 553 | } 554 | if(strategy == DeckStrategy::ordered) 555 | { 556 | unsigned i = 0; 557 | order.clear(); 558 | for(auto card: cards) 559 | { 560 | order[card->m_id].push_back(i); 561 | ++i; 562 | } 563 | } 564 | if(strategy != DeckStrategy::exact_ordered) 565 | { 566 | auto shufflable_iter = shuffled_cards.begin(); 567 | for(auto hand_card_id: given_hand) 568 | { 569 | auto it = std::find_if(shufflable_iter, shuffled_cards.end(), [hand_card_id](const Card* card) -> bool { return card->m_id == hand_card_id; }); 570 | if(it != shuffled_cards.end()) 571 | { 572 | std::swap(*shufflable_iter, *it); 573 | ++ shufflable_iter; 574 | } 575 | } 576 | std::shuffle(shufflable_iter, shuffled_cards.end(), re); 577 | #if 0 578 | if(!given_hand.empty()) 579 | { 580 | for(auto card: cards) std::cout << ", " << card->m_name; 581 | std::cout << std::endl; 582 | std::cout << strategy; 583 | for(auto card: shuffled_cards) std::cout << ", " << card->m_name; 584 | std::cout << std::endl; 585 | } 586 | #endif 587 | } 588 | } 589 | 590 | void Deck::place_at_bottom(const Card* card) 591 | { 592 | shuffled_cards.push_back(card); 593 | } 594 | 595 | void Decks::add_deck(Deck* deck, const std::string& deck_name) 596 | { 597 | by_name[deck_name] = deck; 598 | by_name[simplify_name(deck_name)] = deck; 599 | } 600 | 601 | Deck* Decks::find_deck_by_name(const std::string& deck_name) 602 | { 603 | auto it = by_name.find(simplify_name(deck_name)); 604 | return it == by_name.end() ? nullptr : it->second; 605 | } 606 | 607 | -------------------------------------------------------------------------------- /deck.h: -------------------------------------------------------------------------------- 1 | #ifndef DECK_H_INCLUDED 2 | #define DECK_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "tyrant.h" 11 | #include "card.h" 12 | 13 | class Cards; 14 | 15 | //---------------------- $30 Deck: a commander + a sequence of cards ----------- 16 | // Can be shuffled. 17 | // Implementations: random player and raid decks, ordered player decks. 18 | //------------------------------------------------------------------------------ 19 | namespace DeckStrategy 20 | { 21 | enum DeckStrategy 22 | { 23 | random, 24 | ordered, 25 | exact_ordered, 26 | num_deckstrategies 27 | }; 28 | } 29 | typedef void (*DeckDecoder)(const char* hash, std::vector& ids); 30 | typedef void (*DeckEncoder)(std::stringstream &ios, const Card* commander, std::vector cards); 31 | void hash_to_ids_wmt_b64(const char* hash, std::vector& ids); 32 | void encode_deck_wmt_b64(std::stringstream &ios, const Card* commander, std::vector cards); 33 | void hash_to_ids_ext_b64(const char* hash, std::vector& ids); 34 | void encode_deck_ext_b64(std::stringstream &ios, const Card* commander, std::vector cards); 35 | void hash_to_ids_ddd_b64(const char* hash, std::vector& ids); 36 | void encode_deck_ddd_b64(std::stringstream &ios, const Card* commander, std::vector cards); 37 | extern DeckDecoder hash_to_ids; 38 | extern DeckEncoder encode_deck; 39 | 40 | //------------------------------------------------------------------------------ 41 | // No support for ordered raid decks 42 | class Deck 43 | { 44 | public: 45 | const Cards& all_cards; 46 | DeckType::DeckType decktype; 47 | unsigned id; 48 | std::string name; 49 | unsigned upgrade_points; 50 | unsigned upgrade_opportunities; 51 | DeckStrategy::DeckStrategy strategy; 52 | 53 | const Card* commander; 54 | unsigned commander_max_level; 55 | std::vector cards; 56 | std::map card_marks; // : -1 indicating the commander. E.g, used as a mark to be kept in attacking deck when optimizing. 57 | 58 | const Card* shuffled_commander; 59 | std::deque shuffled_forts; 60 | std::deque shuffled_cards; 61 | 62 | // card id -> card order 63 | std::map> order; 64 | std::vector>> variable_cards; // amount, replicates, card pool 65 | unsigned deck_size; 66 | unsigned mission_req; 67 | 68 | std::string deck_string; 69 | std::set vip_cards; 70 | std::vector given_hand; 71 | std::vector fort_cards; 72 | 73 | Deck( 74 | const Cards& all_cards_, 75 | DeckType::DeckType decktype_ = DeckType::deck, 76 | unsigned id_ = 0, 77 | std::string name_ = "", 78 | unsigned upgrade_points_ = 0, 79 | unsigned upgrade_opportunities_ = 0, 80 | DeckStrategy::DeckStrategy strategy_ = DeckStrategy::random) : 81 | all_cards(all_cards_), 82 | decktype(decktype_), 83 | id(id_), 84 | name(name_), 85 | upgrade_points(upgrade_points_), 86 | upgrade_opportunities(upgrade_opportunities_), 87 | strategy(strategy_), 88 | commander(nullptr), 89 | shuffled_commander(nullptr), 90 | deck_size(0), 91 | mission_req(0) 92 | { 93 | } 94 | 95 | ~Deck() {} 96 | 97 | void set( 98 | const Card* commander_, 99 | unsigned commander_max_level_, 100 | const std::vector& cards_, 101 | std::vector>> variable_cards_ = {}, 102 | unsigned mission_req_ = 0) 103 | { 104 | commander = commander_; 105 | commander_max_level = commander_max_level_; 106 | cards = std::vector(std::begin(cards_), std::end(cards_)); 107 | variable_cards = variable_cards_; 108 | deck_size = cards.size(); 109 | for (const auto & pool: variable_cards) 110 | { 111 | deck_size += std::get<0>(pool) * std::get<1>(pool); 112 | } 113 | mission_req = mission_req_; 114 | } 115 | 116 | void set(const std::vector& ids, const std::map &marks); 117 | void set(const std::vector& ids) 118 | { 119 | std::map empty; 120 | set(ids, empty); 121 | } 122 | void set(const std::string& deck_string_); 123 | void resolve(); 124 | void shrink(const unsigned deck_len); 125 | void set_vip_cards(const std::string& deck_string_); 126 | void set_given_hand(const std::string& deck_string_); 127 | void add_forts(const std::string& deck_string_); 128 | 129 | Deck* clone() const; 130 | std::string hash() const; 131 | std::string short_description() const; 132 | std::string medium_description() const; 133 | std::string long_description() const; 134 | void show_upgrades(std::stringstream &ios, const Card* card, unsigned card_max_level, const char * leading_chars) const; 135 | const Card* next(); 136 | const Card* upgrade_card(const Card* card, unsigned card_max_level, std::mt19937& re, unsigned &remaining_upgrade_points, unsigned &remaining_upgrade_opportunities); 137 | void shuffle(std::mt19937& re); 138 | void place_at_bottom(const Card* card); 139 | }; 140 | 141 | typedef std::map DeckList; 142 | class Decks 143 | { 144 | public: 145 | void add_deck(Deck* deck, const std::string& deck_name); 146 | Deck* find_deck_by_name(const std::string& deck_name); 147 | std::list decks; 148 | std::map, Deck*> by_type_id; 149 | std::map by_name; 150 | }; 151 | 152 | #endif 153 | -------------------------------------------------------------------------------- /rapidxml_license.txt: -------------------------------------------------------------------------------- 1 | Use of this software is granted under one of the following two licenses, 2 | to be chosen freely by the user. 3 | 4 | 1. Boost Software License - Version 1.0 - August 17th, 2003 5 | =============================================================================== 6 | 7 | Copyright (c) 2006, 2007 Marcin Kalicinski 8 | 9 | Permission is hereby granted, free of charge, to any person or organization 10 | obtaining a copy of the software and accompanying documentation covered by 11 | this license (the "Software") to use, reproduce, display, distribute, 12 | execute, and transmit the Software, and to prepare derivative works of the 13 | Software, and to permit third-parties to whom the Software is furnished to 14 | do so, all subject to the following: 15 | 16 | The copyright notices in the Software and this entire statement, including 17 | the above license grant, this restriction and the following disclaimer, 18 | must be included in all copies of the Software, in whole or in part, and 19 | all derivative works of the Software, unless such copies or derivative 20 | works are solely in the form of machine-executable object code generated by 21 | a source language processor. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 26 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 27 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 28 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 29 | DEALINGS IN THE SOFTWARE. 30 | 31 | 2. The MIT License 32 | =============================================================================== 33 | 34 | Copyright (c) 2006, 2007 Marcin Kalicinski 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining a copy 37 | of this software and associated documentation files (the "Software"), to deal 38 | in the Software without restriction, including without limitation the rights 39 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 40 | of the Software, and to permit persons to whom the Software is furnished to do so, 41 | subject to the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be included in all 44 | copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 47 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 48 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 49 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 50 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 51 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 52 | IN THE SOFTWARE. 53 | -------------------------------------------------------------------------------- /read.cpp: -------------------------------------------------------------------------------- 1 | #include "read.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "tyrant.h" 15 | #include "card.h" 16 | #include "cards.h" 17 | #include "deck.h" 18 | 19 | template Iterator advance_until(Iterator it, Iterator it_end, Functor f) 20 | { 21 | while(it != it_end) 22 | { 23 | if(f(*it)) 24 | { 25 | break; 26 | } 27 | ++it; 28 | } 29 | return(it); 30 | } 31 | 32 | // take care that "it" is 1 past current. 33 | template Iterator recede_until(Iterator it, Iterator it_beg, Functor f) 34 | { 35 | if(it == it_beg) { return(it_beg); } 36 | --it; 37 | do 38 | { 39 | if(f(*it)) 40 | { 41 | return(++it); 42 | } 43 | --it; 44 | } while(it != it_beg); 45 | return(it_beg); 46 | } 47 | 48 | template Iterator read_token(Iterator it, Iterator it_end, Functor f, Token& token) 49 | { 50 | Iterator token_start = advance_until(it, it_end, [](const char& c){return(c != ' ');}); 51 | Iterator token_end_after_spaces = advance_until(token_start, it_end, f); 52 | if(token_start != token_end_after_spaces) 53 | { 54 | Iterator token_end = recede_until(token_end_after_spaces, token_start, [](const char& c){return(c != ' ');}); 55 | token = boost::lexical_cast(std::string{token_start, token_end}); 56 | } 57 | return(token_end_after_spaces); 58 | } 59 | 60 | DeckList & normalize(DeckList & decklist) 61 | { 62 | long double factor_sum = 0; 63 | for (const auto & it : decklist) 64 | { 65 | factor_sum += it.second; 66 | } 67 | if (factor_sum > 0) 68 | { 69 | for (auto & it : decklist) 70 | { 71 | it.second /= factor_sum; 72 | } 73 | } 74 | return decklist; 75 | } 76 | 77 | DeckList expand_deck_to_list(std::string deck_name, Decks& decks) 78 | { 79 | static std::unordered_set expanding_decks; 80 | if (expanding_decks.count(deck_name)) 81 | { 82 | std::cerr << "Warning: circular referred deck: " << deck_name << std::endl; 83 | return DeckList(); 84 | } 85 | auto deck_string = deck_name; 86 | Deck* deck = decks.find_deck_by_name(deck_name); 87 | if (deck != nullptr) 88 | { 89 | deck_string = deck->deck_string; 90 | if (deck_string.find_first_of(";:") != std::string::npos || decks.find_deck_by_name(deck_string) != nullptr) 91 | { 92 | // deck_name refers to a deck list 93 | expanding_decks.insert(deck_name); 94 | auto && decklist = parse_deck_list(deck_string, decks); 95 | expanding_decks.erase(deck_name); 96 | return normalize(decklist); 97 | } 98 | } 99 | 100 | if (deck_string.length() >= 3 && deck_string.front() == '/' && deck_string.back() == '/') 101 | { 102 | // deck_name is, or refers to, a regex 103 | DeckList res; 104 | std::string regex_string(deck_string, 1, deck_string.length() - 2); 105 | boost::regex regex(regex_string); 106 | boost::smatch smatch; 107 | expanding_decks.insert(deck_name); 108 | for (const auto & deck_it: decks.by_name) 109 | { 110 | if (boost::regex_search(deck_it.first, smatch, regex)) 111 | { 112 | auto && decklist = expand_deck_to_list(deck_it.first, decks); 113 | for (const auto & it : decklist) 114 | { 115 | res[it.first] += it.second; 116 | } 117 | } 118 | } 119 | expanding_decks.erase(deck_name); 120 | if (res.size() == 0) 121 | { 122 | std::cerr << "Warning: regular expression matches nothing: /" << regex_string << "/." << std::endl; 123 | } 124 | return normalize(res); 125 | } 126 | else 127 | { 128 | return {{deck_name, 1}}; 129 | } 130 | } 131 | 132 | DeckList parse_deck_list(std::string list_string, Decks& decks) 133 | { 134 | DeckList res; 135 | boost::tokenizer> list_tokens{list_string, boost::char_delimiters_separator{false, ";", ""}}; 136 | for(const auto & list_token : list_tokens) 137 | { 138 | boost::tokenizer> deck_tokens{list_token, boost::char_delimiters_separator{false, ":", ""}}; 139 | auto deck_token = deck_tokens.begin(); 140 | auto deck_name = *deck_token; 141 | double factor = 1.0; 142 | ++ deck_token; 143 | if (deck_token != deck_tokens.end()) 144 | { 145 | try 146 | { 147 | factor = boost::lexical_cast(*deck_token); 148 | } 149 | catch (const boost::bad_lexical_cast & e) 150 | { 151 | std::cerr << "Warning: Is ':' a typo? Skip deck [" << list_token << "]\n"; 152 | continue; 153 | } 154 | } 155 | auto && decklist = expand_deck_to_list(deck_name, decks); 156 | for (const auto & it : decklist) 157 | { 158 | res[it.first] += it.second * factor; 159 | } 160 | } 161 | return res; 162 | } 163 | 164 | void parse_card_spec(const Cards& all_cards, const std::string& card_spec, unsigned& card_id, unsigned& card_num, char& num_sign, char& mark) 165 | { 166 | // static std::set recognized_abbr; 167 | auto card_spec_iter = card_spec.begin(); 168 | card_id = 0; 169 | card_num = 1; 170 | num_sign = 0; 171 | mark = 0; 172 | std::string card_name; 173 | card_spec_iter = read_token(card_spec_iter, card_spec.end(), [](char c){return(c=='#' || c=='(' || c=='\r');}, card_name); 174 | if(card_name[0] == '!') 175 | { 176 | mark = card_name[0]; 177 | card_name.erase(0, 1); 178 | } 179 | // If card name is not found, try find card id quoted in '[]' in name, ignoring other characters. 180 | std::string simple_name{simplify_name(card_name)}; 181 | const auto && abbr_it = all_cards.player_cards_abbr.find(simple_name); 182 | if(abbr_it != all_cards.player_cards_abbr.end()) 183 | { 184 | // if(recognized_abbr.count(card_name) == 0) 185 | // { 186 | // std::cout << "Recognize abbreviation " << card_name << ": " << abbr_it->second << std::endl; 187 | // recognized_abbr.insert(card_name); 188 | // } 189 | simple_name = simplify_name(abbr_it->second); 190 | } 191 | auto card_it = all_cards.cards_by_name.find(simple_name); 192 | auto card_id_iter = advance_until(simple_name.begin(), simple_name.end(), [](char c){return(c=='[');}); 193 | if (card_it != all_cards.cards_by_name.end()) 194 | { 195 | card_id = card_it->second->m_id; 196 | if (all_cards.ambiguous_names.count(simple_name)) 197 | { 198 | std::cerr << "Warning: There are multiple cards named " << card_name << " in cards.xml. [" << card_id << "] is used.\n"; 199 | } 200 | } 201 | else if(card_id_iter != simple_name.end()) 202 | { 203 | ++ card_id_iter; 204 | card_id_iter = read_token(card_id_iter, simple_name.end(), [](char c){return(c==']');}, card_id); 205 | } 206 | if(card_spec_iter != card_spec.end() && (*card_spec_iter == '#' || *card_spec_iter == '(')) 207 | { 208 | ++card_spec_iter; 209 | if(card_spec_iter != card_spec.end()) 210 | { 211 | if(strchr("+-$", *card_spec_iter)) 212 | { 213 | num_sign = *card_spec_iter; 214 | ++card_spec_iter; 215 | } 216 | } 217 | card_spec_iter = read_token(card_spec_iter, card_spec.end(), [](char c){return(c < '0' || c > '9');}, card_num); 218 | } 219 | if(card_id == 0) 220 | { 221 | throw std::runtime_error("Unknown card: " + card_name); 222 | } 223 | } 224 | 225 | const std::pair, std::map> string_to_ids(const Cards& all_cards, const std::string& deck_string, const std::string & description) 226 | { 227 | std::vector card_ids; 228 | std::map card_marks; 229 | std::vector error_list; 230 | boost::tokenizer> deck_tokens{deck_string, boost::char_delimiters_separator{false, ":,", ""}}; 231 | auto token_iter = deck_tokens.begin(); 232 | signed p = -1; 233 | for(; token_iter != deck_tokens.end(); ++token_iter) 234 | { 235 | std::string card_spec(*token_iter); 236 | unsigned card_id{0}; 237 | unsigned card_num{1}; 238 | char num_sign{0}; 239 | char mark{0}; 240 | try 241 | { 242 | parse_card_spec(all_cards, card_spec, card_id, card_num, num_sign, mark); 243 | assert(num_sign == 0); 244 | for(unsigned i(0); i < card_num; ++i) 245 | { 246 | card_ids.push_back(card_id); 247 | if(mark) { card_marks[p] = mark; } 248 | ++ p; 249 | } 250 | } 251 | catch(std::exception& e) 252 | { 253 | error_list.push_back(e.what()); 254 | continue; 255 | } 256 | } 257 | if (! card_ids.empty()) 258 | { 259 | if (! error_list.empty()) 260 | { 261 | std::cerr << "Warning: Ignore some cards while resolving " << description << ": "; 262 | for (auto error: error_list) 263 | { 264 | std::cerr << '[' << error << ']'; 265 | } 266 | std::cerr << std::endl; 267 | } 268 | return {card_ids, card_marks}; 269 | } 270 | try 271 | { 272 | hash_to_ids(deck_string.c_str(), card_ids); 273 | for (auto & card_id: card_ids) 274 | { 275 | try 276 | { 277 | all_cards.by_id(card_id); 278 | } 279 | catch(std::exception& e) 280 | { 281 | throw std::runtime_error(std::string("Deck not found. Error to treat as hash: ") + e.what()); 282 | } 283 | } 284 | } 285 | catch(std::exception& e) 286 | { 287 | std::cerr << "Error: Failed to resolve " << description << ": " << e.what() << std::endl; 288 | throw; 289 | } 290 | return {card_ids, card_marks}; 291 | } 292 | 293 | unsigned read_card_abbrs(Cards& all_cards, const std::string& filename) 294 | { 295 | if(!boost::filesystem::exists(filename)) 296 | { 297 | return(0); 298 | } 299 | std::ifstream abbr_file(filename); 300 | if(!abbr_file.is_open()) 301 | { 302 | std::cerr << "Error: Card abbreviation file " << filename << " could not be opened\n"; 303 | return(2); 304 | } 305 | unsigned num_line(0); 306 | abbr_file.exceptions(std::ifstream::badbit); 307 | try 308 | { 309 | while(abbr_file && !abbr_file.eof()) 310 | { 311 | std::string abbr_string; 312 | getline(abbr_file, abbr_string); 313 | ++num_line; 314 | if(abbr_string.size() == 0 || strncmp(abbr_string.c_str(), "//", 2) == 0) 315 | { 316 | continue; 317 | } 318 | std::string abbr_name; 319 | auto abbr_string_iter = read_token(abbr_string.begin(), abbr_string.end(), [](char c){return(c == ':');}, abbr_name); 320 | if(abbr_string_iter == abbr_string.end() || abbr_name.empty()) 321 | { 322 | std::cerr << "Error in card abbreviation file " << filename << " at line " << num_line << ", could not read the name.\n"; 323 | continue; 324 | } 325 | abbr_string_iter = advance_until(abbr_string_iter + 1, abbr_string.end(), [](const char& c){return(c != ' ');}); 326 | if(all_cards.cards_by_name.find(abbr_name) != all_cards.cards_by_name.end()) 327 | { 328 | std::cerr << "Warning in card abbreviation file " << filename << " at line " << num_line << ": ignored because the name has been used by an existing card." << std::endl; 329 | } 330 | else 331 | { 332 | all_cards.player_cards_abbr[simplify_name(abbr_name)] = std::string{abbr_string_iter, abbr_string.end()}; 333 | } 334 | } 335 | } 336 | catch (std::exception& e) 337 | { 338 | std::cerr << "Exception while parsing the card abbreviation file " << filename; 339 | if(num_line > 0) 340 | { 341 | std::cerr << " at line " << num_line; 342 | } 343 | std::cerr << ": " << e.what() << ".\n"; 344 | return(3); 345 | } 346 | return(0); 347 | } 348 | 349 | 350 | // Error codes: 351 | // 2 -> file not readable 352 | // 3 -> error while parsing file 353 | unsigned load_custom_decks(Decks& decks, Cards& all_cards, const std::string & filename) 354 | { 355 | if (!boost::filesystem::exists(filename)) 356 | { 357 | return 0; 358 | } 359 | std::ifstream decks_file(filename); 360 | if (!decks_file.is_open()) 361 | { 362 | std::cerr << "Error: Custom deck file " << filename << " could not be opened\n"; 363 | return 2; 364 | } 365 | unsigned num_line(0); 366 | decks_file.exceptions(std::ifstream::badbit); 367 | try 368 | { 369 | while(decks_file && !decks_file.eof()) 370 | { 371 | std::string deck_string; 372 | getline(decks_file, deck_string); 373 | ++num_line; 374 | if(deck_string.size() == 0 || strncmp(deck_string.c_str(), "//", 2) == 0) 375 | { 376 | continue; 377 | } 378 | std::string deck_name; 379 | auto deck_string_iter = read_token(deck_string.begin(), deck_string.end(), [](char c){return(strchr(":,", c));}, deck_name); 380 | if(deck_string_iter == deck_string.end() || deck_name.empty()) 381 | { 382 | std::cerr << "Error in custom deck file " << filename << " at line " << num_line << ", could not read the deck name.\n"; 383 | continue; 384 | } 385 | deck_string_iter = advance_until(deck_string_iter + 1, deck_string.end(), [](const char& c){return(c != ' ');}); 386 | Deck* deck = decks.find_deck_by_name(deck_name); 387 | if (deck != nullptr) 388 | { 389 | std::cerr << "Warning in custom deck file " << filename << " at line " << num_line << ", name conflicts, overrides " << deck->short_description() << std::endl; 390 | } 391 | decks.decks.push_back(Deck{all_cards, DeckType::custom_deck, num_line, deck_name}); 392 | deck = &decks.decks.back(); 393 | deck->set(std::string{deck_string_iter, deck_string.end()}); 394 | decks.add_deck(deck, deck_name); 395 | std::stringstream alt_name; 396 | alt_name << decktype_names[deck->decktype] << " #" << deck->id; 397 | decks.add_deck(deck, alt_name.str()); 398 | } 399 | } 400 | catch (std::exception& e) 401 | { 402 | std::cerr << "Exception while parsing the custom deck file " << filename; 403 | if(num_line > 0) 404 | { 405 | std::cerr << " at line " << num_line; 406 | } 407 | std::cerr << ": " << e.what() << ".\n"; 408 | return 3; 409 | } 410 | return(0); 411 | } 412 | 413 | void add_owned_card(Cards& all_cards, std::map& owned_cards, std::string& card_spec) 414 | { 415 | unsigned card_id{0}; 416 | unsigned card_num{1}; 417 | char num_sign{0}; 418 | char mark{0}; 419 | parse_card_spec(all_cards, card_spec, card_id, card_num, num_sign, mark); 420 | all_cards.by_id(card_id); // check that the id is valid 421 | assert(mark == 0); 422 | if(num_sign == 0) 423 | { 424 | owned_cards[card_id] = card_num; 425 | } 426 | else if(num_sign == '+') 427 | { 428 | owned_cards[card_id] += card_num; 429 | } 430 | else if(num_sign == '-') 431 | { 432 | owned_cards[card_id] = owned_cards[card_id] > card_num ? owned_cards[card_id] - card_num : 0; 433 | } 434 | } 435 | 436 | void read_owned_cards(Cards& all_cards, std::map& owned_cards, const std::string & filename) 437 | { 438 | std::ifstream owned_file{filename}; 439 | if(!owned_file.good()) 440 | { 441 | // try parse the string as a cards instead of as a filename 442 | try 443 | { 444 | std::string card_list(filename); 445 | boost::tokenizer> card_tokens{card_list, boost::char_delimiters_separator{false, ",", ""}}; 446 | auto token_iter = card_tokens.begin(); 447 | for (; token_iter != card_tokens.end(); ++token_iter) 448 | { 449 | std::string card_spec(*token_iter); 450 | add_owned_card(all_cards, owned_cards, card_spec); 451 | } 452 | } 453 | catch (std::exception& e) 454 | { 455 | std::cerr << "Error: Failed to parse owned cards: '" << filename << "' is neither a file nor a valid set of cards (" << e.what() << ")" << std::endl; 456 | exit(0); 457 | } 458 | return; 459 | } 460 | unsigned num_line(0); 461 | while(owned_file && !owned_file.eof()) 462 | { 463 | std::string card_spec; 464 | getline(owned_file, card_spec); 465 | ++num_line; 466 | if(card_spec.size() == 0 || strncmp(card_spec.c_str(), "//", 2) == 0) 467 | { 468 | continue; 469 | } 470 | try 471 | { 472 | add_owned_card(all_cards, owned_cards, card_spec); 473 | } 474 | catch(std::exception& e) 475 | { 476 | std::cerr << "Error in owned cards file " << filename << " at line " << num_line << " while parsing card '" << card_spec << "': " << e.what() << "\n"; 477 | } 478 | } 479 | } 480 | 481 | unsigned read_bge_aliases(std::unordered_map & bge_aliases, const std::string& filename) 482 | { 483 | if(!boost::filesystem::exists(filename)) 484 | { 485 | return(0); 486 | } 487 | std::ifstream bgefile(filename); 488 | if(!bgefile.is_open()) 489 | { 490 | std::cerr << "Error: BGE file " << filename << " could not be opened\n"; 491 | return(2); 492 | } 493 | unsigned num_line(0); 494 | bgefile.exceptions(std::ifstream::badbit); 495 | try 496 | { 497 | while(bgefile && !bgefile.eof()) 498 | { 499 | std::string bge_string; 500 | getline(bgefile, bge_string); 501 | ++num_line; 502 | if(bge_string.size() == 0 || strncmp(bge_string.c_str(), "//", 2) == 0) 503 | { 504 | continue; 505 | } 506 | std::string bge_name; 507 | auto bge_string_iter = read_token(bge_string.begin(), bge_string.end(), [](char c){return(c == ':');}, bge_name); 508 | if(bge_string_iter == bge_string.end() || bge_name.empty()) 509 | { 510 | std::cerr << "Error in BGE file " << filename << " at line " << num_line << ", could not read the name.\n"; 511 | continue; 512 | } 513 | bge_string_iter = advance_until(bge_string_iter + 1, bge_string.end(), [](const char& c){return(c != ' ');}); 514 | bge_aliases[simplify_name(bge_name)] = std::string{bge_string_iter, bge_string.end()}; 515 | } 516 | } 517 | catch (std::exception& e) 518 | { 519 | std::cerr << "Exception while parsing the BGE file " << filename; 520 | if(num_line > 0) 521 | { 522 | std::cerr << " at line " << num_line; 523 | } 524 | std::cerr << ": " << e.what() << ".\n"; 525 | return(3); 526 | } 527 | return(0); 528 | } 529 | 530 | -------------------------------------------------------------------------------- /read.h: -------------------------------------------------------------------------------- 1 | #ifndef READ_H_INCLUDED 2 | #define READ_H_INCLUDED 3 | 4 | #include 5 | #include 6 | 7 | #include "deck.h" 8 | 9 | class Cards; 10 | class Decks; 11 | class Deck; 12 | 13 | DeckList parse_deck_list(std::string list_string, Decks& decks); 14 | void parse_card_spec(const Cards& cards, const std::string& card_spec, unsigned& card_id, unsigned& card_num, char& num_sign, char& mark); 15 | const std::pair, std::map> string_to_ids(const Cards& all_cards, const std::string& deck_string, const std::string & description); 16 | unsigned load_custom_decks(Decks& decks, Cards& cards, const std::string & filename); 17 | void read_owned_cards(Cards& cards, std::map& owned_cards, const std::string & filename); 18 | unsigned read_card_abbrs(Cards& cards, const std::string& filename); 19 | unsigned read_bge_aliases(std::unordered_map & bge_aliases, const std::string & filename); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /sim.cpp: -------------------------------------------------------------------------------- 1 | #include "sim.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "tyrant.h" 12 | #include "card.h" 13 | #include "cards.h" 14 | #include "deck.h" 15 | 16 | //------------------------------------------------------------------------------ 17 | inline std::string status_description(const CardStatus* status) 18 | { 19 | return status->description(); 20 | } 21 | //------------------------------------------------------------------------------ 22 | template 23 | inline unsigned Field::make_selection_array(CardsIter first, CardsIter last, Functor f) 24 | { 25 | this->selection_array.clear(); 26 | for(auto c = first; c != last; ++c) 27 | { 28 | if (f(*c)) 29 | { 30 | this->selection_array.push_back(*c); 31 | } 32 | } 33 | return(this->selection_array.size()); 34 | } 35 | inline const std::vector Field::adjacent_assaults(const CardStatus * status) 36 | { 37 | std::vector res; 38 | auto & assaults = this->players[status->m_player]->assaults; 39 | if (status->m_index > 0) 40 | { 41 | auto left_status = &assaults[status->m_index - 1]; 42 | if (left_status->m_hp > 0) 43 | { 44 | res.push_back(left_status); 45 | } 46 | } 47 | if (status->m_index + 1 < assaults.size()) 48 | { 49 | auto right_status = &assaults[status->m_index + 1]; 50 | if (right_status->m_hp > 0) 51 | { 52 | res.push_back(right_status); 53 | } 54 | } 55 | return res; 56 | } 57 | inline void Field::print_selection_array() 58 | { 59 | #ifndef NDEBUG 60 | for(auto c: this->selection_array) 61 | { 62 | _DEBUG_MSG(2, "+ %s\n", status_description(c).c_str()); 63 | } 64 | #endif 65 | } 66 | //------------------------------------------------------------------------------ 67 | inline unsigned CardStatus::skill_base_value(Skill skill_id) const 68 | { 69 | return m_card->m_skill_value[skill_id + m_primary_skill_offset[skill_id]]; 70 | } 71 | //------------------------------------------------------------------------------ 72 | inline unsigned CardStatus::skill(Skill skill_id) const 73 | { 74 | return skill_base_value(skill_id) + enhanced(skill_id); 75 | } 76 | //------------------------------------------------------------------------------ 77 | inline bool CardStatus::has_skill(Skill skill_id) const 78 | { 79 | return skill_base_value(skill_id); 80 | } 81 | //------------------------------------------------------------------------------ 82 | inline unsigned CardStatus::enhanced(Skill skill_id) const 83 | { 84 | return m_enhanced_value[skill_id + m_primary_skill_offset[skill_id]]; 85 | } 86 | //------------------------------------------------------------------------------ 87 | inline unsigned CardStatus::protected_value() const 88 | { 89 | return m_protected; 90 | } 91 | //------------------------------------------------------------------------------ 92 | inline void CardStatus::set(const Card* card) 93 | { 94 | this->set(*card); 95 | } 96 | //------------------------------------------------------------------------------ 97 | inline void CardStatus::set(const Card& card) 98 | { 99 | m_card = &card; 100 | m_index = 0; 101 | m_player = 0; 102 | m_delay = card.m_delay; 103 | m_faction = card.m_faction; 104 | m_attack = card.m_attack; 105 | m_hp = m_max_hp = card.m_health; 106 | m_step = CardStep::none; 107 | 108 | m_corroded_rate = 0; 109 | m_corroded_weakened = 0; 110 | m_enfeebled = 0; 111 | m_evaded = 0; 112 | m_inhibited = 0; 113 | m_jammed = false; 114 | m_overloaded = false; 115 | m_paybacked = 0; 116 | m_poisoned = 0; 117 | m_protected = 0; 118 | m_rallied = 0; 119 | m_rush_attempted = false; 120 | m_sundered = false; 121 | m_weakened = 0; 122 | 123 | std::memset(m_primary_skill_offset, 0, sizeof m_primary_skill_offset); 124 | std::memset(m_evolved_skill_offset, 0, sizeof m_evolved_skill_offset); 125 | std::memset(m_enhanced_value, 0, sizeof m_enhanced_value); 126 | std::memset(m_skill_cd, 0, sizeof m_skill_cd); 127 | } 128 | //------------------------------------------------------------------------------ 129 | inline unsigned attack_power(const CardStatus* att) 130 | { 131 | return(safe_minus(att->m_attack + att->m_rallied, att->m_weakened + att->m_corroded_weakened)); 132 | } 133 | //------------------------------------------------------------------------------ 134 | std::string skill_description(const Cards& cards, const SkillSpec& s) 135 | { 136 | return skill_names[s.id] + 137 | (s.all ? " all" : s.n == 0 ? "" : std::string(" ") + to_string(s.n)) + 138 | (s.y == allfactions ? "" : std::string(" ") + faction_names[s.y]) + 139 | (s.s == no_skill ? "" : std::string(" ") + skill_names[s.s]) + 140 | (s.s2 == no_skill ? "" : std::string(" ") + skill_names[s.s2]) + 141 | (s.x == 0 ? "" : std::string(" ") + to_string(s.x)) + 142 | (s.c == 0 ? "" : std::string(" every ") + to_string(s.c)); 143 | } 144 | std::string skill_short_description(const SkillSpec& s) 145 | { 146 | // NOTE: not support summon 147 | return skill_names[s.id] + 148 | (s.s == no_skill ? "" : std::string(" ") + skill_names[s.s]) + 149 | (s.s2 == no_skill ? "" : std::string(" ") + skill_names[s.s2]) + 150 | (s.x == 0 ? "" : std::string(" ") + to_string(s.x)); 151 | } 152 | //------------------------------------------------------------------------------ 153 | std::string card_description(const Cards& cards, const Card* c) 154 | { 155 | std::string desc; 156 | desc = c->m_name; 157 | switch(c->m_type) 158 | { 159 | case CardType::assault: 160 | desc += ": " + to_string(c->m_attack) + "/" + to_string(c->m_health) + "/" + to_string(c->m_delay); 161 | break; 162 | case CardType::structure: 163 | desc += ": " + to_string(c->m_health) + "/" + to_string(c->m_delay); 164 | break; 165 | case CardType::commander: 166 | desc += ": hp:" + to_string(c->m_health); 167 | break; 168 | case CardType::num_cardtypes: 169 | assert(false); 170 | break; 171 | } 172 | if(c->m_rarity >= 4) { desc += " " + rarity_names[c->m_rarity]; } 173 | if(c->m_faction != allfactions) { desc += " " + faction_names[c->m_faction]; } 174 | for(auto& skill: c->m_skills) { desc += ", " + skill_description(cards, skill); } 175 | return(desc); 176 | } 177 | //------------------------------------------------------------------------------ 178 | std::string CardStatus::description() const 179 | { 180 | std::string desc = "P" + to_string(m_player) + " "; 181 | switch(m_card->m_type) 182 | { 183 | case CardType::commander: desc += "Commander "; break; 184 | case CardType::assault: desc += "Assault " + to_string(m_index) + " "; break; 185 | case CardType::structure: desc += "Structure " + to_string(m_index) + " "; break; 186 | case CardType::num_cardtypes: assert(false); break; 187 | } 188 | desc += "[" + m_card->m_name; 189 | switch(m_card->m_type) 190 | { 191 | case CardType::assault: 192 | desc += " att:" + to_string(m_attack); 193 | { 194 | std::string att_desc; 195 | if(m_rallied > 0) { att_desc += "+" + to_string(m_rallied) + "(rallied)"; } 196 | if(m_weakened > 0) { att_desc += "-" + to_string(m_weakened) + "(weakened)"; } 197 | if(m_corroded_weakened > 0) { att_desc += "-" + to_string(m_corroded_weakened) + "(corroded)"; } 198 | if(!att_desc.empty()) { desc += att_desc + "=" + to_string(attack_power(this)); } 199 | } 200 | case CardType::structure: 201 | case CardType::commander: 202 | desc += " hp:" + to_string(m_hp); 203 | break; 204 | case CardType::num_cardtypes: 205 | assert(false); 206 | break; 207 | } 208 | if(m_delay > 0) { 209 | desc += " cd:" + to_string(m_delay); 210 | } 211 | // Status w/o value 212 | if(m_jammed) { desc += ", jammed"; } 213 | if(m_overloaded) { desc += ", overloaded"; } 214 | if(m_sundered) { desc += ", sundered"; } 215 | // Status w/ value 216 | if(m_corroded_rate > 0) { desc += ", corroded " + to_string(m_corroded_rate); } 217 | if(m_enfeebled > 0) { desc += ", enfeebled " + to_string(m_enfeebled); } 218 | if(m_inhibited > 0) { desc += ", inhibited " + to_string(m_inhibited); } 219 | if(m_poisoned > 0) { desc += ", poisoned " + to_string(m_poisoned); } 220 | if(m_protected > 0) { desc += ", protected " + to_string(m_protected); } 221 | // if(m_step != CardStep::none) { desc += ", Step " + to_string(static_cast(m_step)); } 222 | for (const auto & ss: m_card->m_skills) 223 | { 224 | std::string skill_desc; 225 | if (m_evolved_skill_offset[ss.id] != 0) { skill_desc += "->" + skill_names[ss.id + m_evolved_skill_offset[ss.id]]; } 226 | if (m_enhanced_value[ss.id] != 0) { skill_desc += " +" + to_string(m_enhanced_value[ss.id]); } 227 | if (!skill_desc.empty()) { desc += ", " + skill_names[ss.id] + skill_desc; } 228 | } 229 | desc += "]"; 230 | return(desc); 231 | } 232 | //------------------------------------------------------------------------------ 233 | void Hand::reset(std::mt19937& re) 234 | { 235 | assaults.reset(); 236 | structures.reset(); 237 | deck->shuffle(re); 238 | commander.set(deck->shuffled_commander); 239 | } 240 | //---------------------- $40 Game rules implementation ------------------------- 241 | // Everything about how a battle plays out, except the following: 242 | // the implementation of the attack by an assault card is in the next section; 243 | // the implementation of the active skills is in the section after that. 244 | unsigned turn_limit{50}; 245 | //------------------------------------------------------------------------------ 246 | inline unsigned opponent(unsigned player) 247 | { 248 | return((player + 1) % 2); 249 | } 250 | //------------------------------------------------------------------------------ 251 | SkillSpec apply_evolve(const SkillSpec& s, signed offset) 252 | { 253 | SkillSpec evolved_s = s; 254 | evolved_s.id = static_cast(evolved_s.id + offset); 255 | return(evolved_s); 256 | } 257 | //------------------------------------------------------------------------------ 258 | SkillSpec apply_enhance(const SkillSpec& s, unsigned enhanced_value) 259 | { 260 | SkillSpec enahnced_s = s; 261 | enahnced_s.x += enhanced_value; 262 | return(enahnced_s); 263 | } 264 | //------------------------------------------------------------------------------ 265 | void prepend_on_death(Field* fd) 266 | { 267 | if (fd->killed_units.empty()) 268 | { 269 | return; 270 | } 271 | std::vector> od_skills; 272 | auto & assaults = fd->players[fd->killed_units[0]->m_player]->assaults; 273 | unsigned stacked_poison_value = 0; 274 | unsigned last_index = 99; 275 | CardStatus * left_virulence_victim = nullptr; 276 | for (auto status: fd->killed_units) 277 | { 278 | if (status->m_card->m_type == CardType::assault) 279 | { 280 | // Avenge 281 | for (auto && adj_status: fd->adjacent_assaults(status)) 282 | { 283 | unsigned avenge_value = adj_status->skill(avenge); 284 | if (avenge_value > 0) 285 | { 286 | _DEBUG_MSG(1, "%s activates Avenge %u\n", status_description(adj_status).c_str(), avenge_value); 287 | if (! adj_status->m_sundered) 288 | { adj_status->m_attack += avenge_value; } 289 | adj_status->m_max_hp += avenge_value; 290 | adj_status->m_hp += avenge_value; 291 | } 292 | } 293 | // Virulence 294 | if (fd->bg_effects.count(virulence)) 295 | { 296 | if (status->m_index != last_index + 1) 297 | { 298 | stacked_poison_value = 0; 299 | left_virulence_victim = nullptr; 300 | if (status->m_index > 0) 301 | { 302 | auto left_status = &assaults[status->m_index - 1]; 303 | if (left_status->m_hp > 0) 304 | { 305 | left_virulence_victim = left_status; 306 | } 307 | } 308 | } 309 | if (status->m_poisoned > 0) 310 | { 311 | if (left_virulence_victim != nullptr) 312 | { 313 | _DEBUG_MSG(1, "Virulence: %s spreads left poison +%u to %s\n", status_description(status).c_str(), status->m_poisoned, status_description(left_virulence_victim).c_str()); 314 | left_virulence_victim->m_poisoned += status->m_poisoned; 315 | } 316 | stacked_poison_value += status->m_poisoned; 317 | _DEBUG_MSG(1, "Virulence: %s spreads right poison +%u = %u\n", status_description(status).c_str(), status->m_poisoned, stacked_poison_value); 318 | } 319 | if (status->m_index + 1 < assaults.size()) 320 | { 321 | auto right_status = &assaults[status->m_index + 1]; 322 | if (right_status->m_hp > 0) 323 | { 324 | _DEBUG_MSG(1, "Virulence: spreads stacked poison +%u to %s\n", stacked_poison_value, status_description(right_status).c_str()); 325 | right_status->m_poisoned += stacked_poison_value; 326 | } 327 | } 328 | last_index = status->m_index; 329 | } 330 | } 331 | // Revenge 332 | if (fd->bg_effects.count(revenge)) 333 | { 334 | SkillSpec ss_heal{heal, fd->bg_effects.at(revenge), allfactions, 0, 0, no_skill, no_skill, true,}; 335 | SkillSpec ss_rally{rally, fd->bg_effects.at(revenge), allfactions, 0, 0, no_skill, no_skill, true,}; 336 | CardStatus * commander = &fd->players[status->m_player]->commander; 337 | _DEBUG_MSG(2, "Revenge: Preparing skill %s and %s\n", skill_description(fd->cards, ss_heal).c_str(), skill_description(fd->cards, ss_rally).c_str()); 338 | od_skills.emplace_back(commander, ss_heal); 339 | od_skills.emplace_back(commander, ss_rally); 340 | } 341 | } 342 | fd->skill_queue.insert(fd->skill_queue.begin(), od_skills.begin(), od_skills.end()); 343 | fd->killed_units.clear(); 344 | } 345 | //------------------------------------------------------------------------------ 346 | void(*skill_table[num_skills])(Field*, CardStatus* src, const SkillSpec&); 347 | void resolve_skill(Field* fd) 348 | { 349 | while(!fd->skill_queue.empty()) 350 | { 351 | auto skill_instance(fd->skill_queue.front()); 352 | auto& status(std::get<0>(skill_instance)); 353 | const auto& ss(std::get<1>(skill_instance)); 354 | fd->skill_queue.pop_front(); 355 | if (status->m_jammed) 356 | { 357 | _DEBUG_MSG(2, "%s failed to %s because it is Jammed.", status_description(status).c_str(), skill_description(fd->cards, ss).c_str()); 358 | continue; 359 | } 360 | signed evolved_offset = status->m_evolved_skill_offset[ss.id]; 361 | auto& evolved_s = status->m_evolved_skill_offset[ss.id] != 0 ? apply_evolve(ss, evolved_offset) : ss; 362 | unsigned enhanced_value = status->enhanced(evolved_s.id); 363 | auto& enhanced_s = enhanced_value > 0 ? apply_enhance(evolved_s, enhanced_value) : evolved_s; 364 | auto& modified_s = enhanced_s; 365 | skill_table[modified_s.id](fd, status, modified_s); 366 | } 367 | } 368 | //------------------------------------------------------------------------------ 369 | inline bool has_attacked(CardStatus* c) { return(c->m_step == CardStep::attacked); } 370 | inline bool can_act(CardStatus* c) { return(c->m_hp > 0 && !c->m_jammed); } 371 | inline bool is_active(CardStatus* c) { return(can_act(c) && c->m_delay == 0); } 372 | inline bool is_active_next_turn(CardStatus* c) { return(can_act(c) && c->m_delay <= 1); } 373 | // Can be healed / repaired 374 | inline bool can_be_healed(CardStatus* c) { return(c->m_hp > 0 && c->m_hp < c->m_max_hp); } 375 | //------------------------------------------------------------------------------ 376 | bool attack_phase(Field* fd); 377 | template 378 | bool check_and_perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s, bool is_evadable, bool & has_counted_quest); 379 | bool check_and_perform_valor(Field* fd, CardStatus* src); 380 | template 381 | void evaluate_skills(Field* fd, CardStatus* status, const std::vector& skills, bool* attacked=nullptr) 382 | { 383 | assert(status); 384 | unsigned num_actions(1); 385 | for (unsigned action_index(0); action_index < num_actions; ++ action_index) 386 | { 387 | assert(fd->skill_queue.size() == 0); 388 | for (auto & ss: skills) 389 | { 390 | // check if activation skill, assuming activation skills can be evolved from only activation skills 391 | if (skill_table[ss.id] == nullptr) 392 | { 393 | continue; 394 | } 395 | if (status->m_skill_cd[ss.id] > 0) 396 | { 397 | continue; 398 | } 399 | _DEBUG_MSG(2, "Evaluating %s skill %s\n", status_description(status).c_str(), skill_description(fd->cards, ss).c_str()); 400 | fd->skill_queue.emplace_back(status, ss); 401 | resolve_skill(fd); 402 | if(__builtin_expect(fd->end, false)) { break; } 403 | } 404 | if (type == CardType::assault) 405 | { 406 | // Attack 407 | if (can_act(status)) 408 | { 409 | if (attack_phase(fd) && !*attacked) 410 | { 411 | *attacked = true; 412 | if (__builtin_expect(fd->end, false)) { break; } 413 | } 414 | } 415 | else 416 | { 417 | _DEBUG_MSG(2, "%s cannot take attack.\n", status_description(status).c_str()); 418 | } 419 | } 420 | // Flurry 421 | if (can_act(status) && fd->tip->commander.m_hp > 0 && status->has_skill(flurry) && status->m_skill_cd[flurry] == 0) 422 | { 423 | if (status->m_player == 0) 424 | { 425 | fd->inc_counter(QuestType::skill_use, flurry); 426 | } 427 | _DEBUG_MSG(1, "%s activates Flurry\n", status_description(status).c_str()); 428 | num_actions = 2; 429 | for (const auto & ss : skills) 430 | { 431 | Skill evolved_skill_id = static_cast(ss.id + status->m_evolved_skill_offset[ss.id]); 432 | if (evolved_skill_id == flurry) 433 | { 434 | status->m_skill_cd[ss.id] = ss.c; 435 | } 436 | } 437 | } 438 | } 439 | } 440 | 441 | struct PlayCard 442 | { 443 | const Card* card; 444 | Field* fd; 445 | CardStatus* status; 446 | Storage* storage; 447 | 448 | PlayCard(const Card* card_, Field* fd_) : 449 | card{card_}, 450 | fd{fd_}, 451 | status{nullptr}, 452 | storage{nullptr} 453 | {} 454 | 455 | template 456 | bool op() 457 | { 458 | setStorage(); 459 | placeCard(); 460 | return(true); 461 | } 462 | 463 | template 464 | void setStorage() 465 | { 466 | } 467 | 468 | template 469 | void placeCard() 470 | { 471 | status = &storage->add_back(); 472 | status->set(card); 473 | status->m_index = storage->size() - 1; 474 | status->m_player = fd->tapi; 475 | if (status->m_player == 0) 476 | { 477 | if (status->m_card->m_type == CardType::assault) 478 | { 479 | fd->inc_counter(QuestType::faction_assault_card_use, card->m_faction); 480 | } 481 | fd->inc_counter(QuestType::type_card_use, type); 482 | } 483 | _DEBUG_MSG(1, "%s plays %s %u [%s]\n", status_description(&fd->tap->commander).c_str(), cardtype_names[type].c_str(), static_cast(storage->size() - 1), card_description(fd->cards, card).c_str()); 484 | if (status->m_delay == 0) 485 | { 486 | check_and_perform_valor(fd, status); 487 | } 488 | } 489 | }; 490 | // assault 491 | template <> 492 | void PlayCard::setStorage() 493 | { 494 | storage = &fd->tap->assaults; 495 | } 496 | // structure 497 | template <> 498 | void PlayCard::setStorage() 499 | { 500 | storage = &fd->tap->structures; 501 | } 502 | 503 | // Check if a skill actually proc'ed. 504 | template 505 | inline bool skill_check(Field* fd, CardStatus* c, CardStatus* ref) 506 | { return(true); } 507 | 508 | template<> 509 | inline bool skill_check(Field* fd, CardStatus* c, CardStatus* ref) 510 | { 511 | return(c->m_player != ref->m_player); 512 | } 513 | 514 | template<> 515 | inline bool skill_check(Field* fd, CardStatus* c, CardStatus* ref) 516 | { 517 | return(can_be_healed(c)); 518 | } 519 | 520 | template<> 521 | inline bool skill_check(Field* fd, CardStatus* c, CardStatus* ref) 522 | { 523 | return(is_active(c)); 524 | } 525 | 526 | template<> 527 | inline bool skill_check(Field* fd, CardStatus* c, CardStatus* ref) 528 | { 529 | return(ref->m_card->m_type == CardType::assault && ref->m_hp > 0); 530 | } 531 | 532 | template<> 533 | inline bool skill_check(Field* fd, CardStatus* c, CardStatus* ref) 534 | { 535 | return(can_be_healed(c)); 536 | } 537 | 538 | void remove_hp(Field* fd, CardStatus* status, unsigned dmg) 539 | { 540 | assert(status->m_hp > 0); 541 | _DEBUG_MSG(2, "%s takes %u damage\n", status_description(status).c_str(), dmg); 542 | status->m_hp = safe_minus(status->m_hp, dmg); 543 | if(status->m_hp == 0) 544 | { 545 | if (status->m_player == 1) 546 | { 547 | if (status->m_card->m_type == CardType::assault) 548 | { 549 | fd->inc_counter(QuestType::faction_assault_card_kill, status->m_card->m_faction); 550 | } 551 | fd->inc_counter(QuestType::type_card_kill, status->m_card->m_type); 552 | } 553 | _DEBUG_MSG(1, "%s dies\n", status_description(status).c_str()); 554 | if(status->m_card->m_type != CardType::commander) 555 | { 556 | fd->killed_units.push_back(status); 557 | } 558 | if (status->m_player == 0 && fd->players[0]->deck->vip_cards.count(status->m_card->m_id)) 559 | { 560 | fd->players[0]->commander.m_hp = 0; 561 | fd->end = true; 562 | } 563 | } 564 | } 565 | 566 | inline bool is_it_dead(CardStatus& c) 567 | { 568 | if(c.m_hp == 0) // yes it is 569 | { 570 | _DEBUG_MSG(1, "Dead and removed: %s\n", status_description(&c).c_str()); 571 | return(true); 572 | } 573 | else { return(false); } // nope still kickin' 574 | } 575 | inline void remove_dead(Storage& storage) 576 | { 577 | storage.remove(is_it_dead); 578 | } 579 | inline void add_hp(Field* fd, CardStatus* target, unsigned v) 580 | { 581 | target->m_hp = std::min(target->m_hp + v, target->m_max_hp); 582 | } 583 | void cooldown_skills(CardStatus * status) 584 | { 585 | for (const auto & ss : status->m_card->m_skills) 586 | { 587 | if (status->m_skill_cd[ss.id] > 0) 588 | { 589 | _DEBUG_MSG(2, "%s reduces timer (%u) of skill %s\n", status_description(status).c_str(), status->m_skill_cd[ss.id], skill_names[ss.id].c_str()); 590 | -- status->m_skill_cd[ss.id]; 591 | } 592 | } 593 | } 594 | void turn_start_phase(Field* fd) 595 | { 596 | // Active player's commander card: 597 | cooldown_skills(&fd->tap->commander); 598 | // Active player's assault cards: 599 | // update index 600 | // reduce delay; reduce skill cooldown 601 | { 602 | auto& assaults(fd->tap->assaults); 603 | for(unsigned index(0), end(assaults.size()); 604 | index < end; 605 | ++index) 606 | { 607 | CardStatus * status = &assaults[index]; 608 | status->m_index = index; 609 | if(status->m_delay > 0) 610 | { 611 | _DEBUG_MSG(1, "%s reduces its timer\n", status_description(status).c_str()); 612 | -- status->m_delay; 613 | if (status->m_delay == 0) 614 | { 615 | check_and_perform_valor(fd, status); 616 | } 617 | } 618 | else 619 | { 620 | cooldown_skills(status); 621 | } 622 | } 623 | } 624 | // Active player's structure cards: 625 | // update index 626 | // reduce delay; reduce skill cooldown 627 | { 628 | auto& structures(fd->tap->structures); 629 | for(unsigned index(0), end(structures.size()); 630 | index < end; 631 | ++index) 632 | { 633 | CardStatus * status = &structures[index]; 634 | status->m_index = index; 635 | if(status->m_delay > 0) 636 | { 637 | _DEBUG_MSG(1, "%s reduces its timer\n", status_description(status).c_str()); 638 | --status->m_delay; 639 | } 640 | else 641 | { 642 | cooldown_skills(status); 643 | } 644 | } 645 | } 646 | // Defending player's assault cards: 647 | // update index 648 | { 649 | auto& assaults(fd->tip->assaults); 650 | for(unsigned index(0), end(assaults.size()); 651 | index < end; 652 | ++index) 653 | { 654 | CardStatus& status(assaults[index]); 655 | status.m_index = index; 656 | } 657 | } 658 | // Defending player's structure cards: 659 | // update index 660 | { 661 | auto& structures(fd->tip->structures); 662 | for(unsigned index(0), end(structures.size()); 663 | index < end; 664 | ++index) 665 | { 666 | CardStatus& status(structures[index]); 667 | status.m_index = index; 668 | } 669 | } 670 | } 671 | void turn_end_phase(Field* fd) 672 | { 673 | // Inactive player's assault cards: 674 | { 675 | auto& assaults(fd->tip->assaults); 676 | for(unsigned index(0), end(assaults.size()); 677 | index < end; 678 | ++index) 679 | { 680 | CardStatus& status(assaults[index]); 681 | if (status.m_hp <= 0) 682 | { 683 | continue; 684 | } 685 | status.m_enfeebled = 0; 686 | status.m_protected = 0; 687 | std::memset(status.m_primary_skill_offset, 0, sizeof status.m_primary_skill_offset); 688 | std::memset(status.m_evolved_skill_offset, 0, sizeof status.m_evolved_skill_offset); 689 | std::memset(status.m_enhanced_value, 0, sizeof status.m_enhanced_value); 690 | status.m_evaded = 0; // so far only useful in Inactive turn 691 | status.m_paybacked = 0; // ditto 692 | } 693 | } 694 | // Inactive player's structure cards: 695 | { 696 | auto& structures(fd->tip->structures); 697 | for(unsigned index(0), end(structures.size()); 698 | index < end; 699 | ++index) 700 | { 701 | CardStatus& status(structures[index]); 702 | if (status.m_hp <= 0) 703 | { 704 | continue; 705 | } 706 | status.m_evaded = 0; // so far only useful in Inactive turn 707 | } 708 | } 709 | 710 | // Active player's assault cards: 711 | { 712 | auto& assaults(fd->tap->assaults); 713 | for(unsigned index(0), end(assaults.size()); 714 | index < end; 715 | ++index) 716 | { 717 | CardStatus& status(assaults[index]); 718 | if (status.m_hp <= 0) 719 | { 720 | continue; 721 | } 722 | unsigned refresh_value = status.skill(refresh); 723 | if (refresh_value > 0 && skill_check(fd, &status, nullptr)) 724 | { 725 | _DEBUG_MSG(1, "%s refreshes %u health\n", status_description(&status).c_str(), refresh_value); 726 | add_hp(fd, &status, refresh_value); 727 | } 728 | if (status.m_poisoned > 0) 729 | { 730 | unsigned poison_dmg = safe_minus(status.m_poisoned + status.m_enfeebled, status.protected_value()); 731 | if (poison_dmg > 0) 732 | { 733 | if (status.m_player == 1) 734 | { 735 | fd->inc_counter(QuestType::skill_damage, poison, 0, poison_dmg); 736 | } 737 | _DEBUG_MSG(1, "%s takes poison damage %u\n", status_description(&status).c_str(), poison_dmg); 738 | remove_hp(fd, &status, poison_dmg); // simultaneous 739 | } 740 | } 741 | // end of the opponent's next turn for enemy units 742 | status.m_jammed = false; 743 | status.m_rallied = 0; 744 | status.m_sundered = false; 745 | status.m_weakened = 0; 746 | status.m_inhibited = 0; 747 | status.m_overloaded = false; 748 | status.m_step = CardStep::none; 749 | } 750 | } 751 | // Active player's structure cards: 752 | // nothing so far 753 | 754 | prepend_on_death(fd); // poison 755 | resolve_skill(fd); 756 | remove_dead(fd->tap->assaults); 757 | remove_dead(fd->tap->structures); 758 | remove_dead(fd->tip->assaults); 759 | remove_dead(fd->tip->structures); 760 | } 761 | //---------------------- $50 attack by assault card implementation ------------- 762 | // Counter damage dealt to the attacker (att) by defender (def) 763 | // pre-condition: only valid if m_card->m_counter > 0 764 | inline unsigned counter_damage(Field* fd, CardStatus* att, CardStatus* def) 765 | { 766 | assert(att->m_card->m_type == CardType::assault); 767 | return(safe_minus(def->skill(counter) + att->m_enfeebled, att->protected_value())); 768 | } 769 | inline CardStatus* select_first_enemy_wall(Field* fd) 770 | { 771 | for(unsigned i(0); i < fd->tip->structures.size(); ++i) 772 | { 773 | CardStatus& c(fd->tip->structures[i]); 774 | if(c.has_skill(wall) && c.m_hp > 0 && skill_check(fd, &c, nullptr)) 775 | { 776 | return(&c); 777 | } 778 | } 779 | return(nullptr); 780 | } 781 | 782 | inline bool alive_assault(Storage& assaults, unsigned index) 783 | { 784 | return(assaults.size() > index && assaults[index].m_hp > 0); 785 | } 786 | 787 | void remove_commander_hp(Field* fd, CardStatus& status, unsigned dmg, bool count_points) 788 | { 789 | //assert(status.m_hp > 0); 790 | assert(status.m_card->m_type == CardType::commander); 791 | _DEBUG_MSG(2, "%s takes %u damage\n", status_description(&status).c_str(), dmg); 792 | status.m_hp = safe_minus(status.m_hp, dmg); 793 | if(status.m_hp == 0) 794 | { 795 | _DEBUG_MSG(1, "%s dies\n", status_description(&status).c_str()); 796 | fd->end = true; 797 | } 798 | } 799 | //------------------------------------------------------------------------------ 800 | // implementation of one attack by an assault card, against either an enemy 801 | // assault card, the first enemy wall, or the enemy commander. 802 | struct PerformAttack 803 | { 804 | Field* fd; 805 | CardStatus* att_status; 806 | CardStatus* def_status; 807 | unsigned att_dmg; 808 | 809 | PerformAttack(Field* fd_, CardStatus* att_status_, CardStatus* def_status_) : 810 | fd(fd_), att_status(att_status_), def_status(def_status_), att_dmg(0) 811 | {} 812 | 813 | template 814 | unsigned op() 815 | { 816 | unsigned pre_modifier_dmg = attack_power(att_status); 817 | 818 | // Evaluation order: 819 | // modify damage 820 | // deal damage 821 | // assaults only: (poison) 822 | // counter, berserk 823 | // assaults only: (leech if still alive) 824 | 825 | modify_attack_damage(pre_modifier_dmg); 826 | if (att_dmg == 0) { return 0; } 827 | 828 | attack_damage(); 829 | if(__builtin_expect(fd->end, false)) { return att_dmg; } 830 | damage_dependant_pre_oa(); 831 | 832 | if (att_status->m_hp > 0 && def_status->has_skill(counter) && skill_check(fd, def_status, att_status)) 833 | { 834 | // perform_skill_counter 835 | unsigned counter_dmg(counter_damage(fd, att_status, def_status)); 836 | if (def_status->m_player == 0) 837 | { 838 | fd->inc_counter(QuestType::skill_use, counter); 839 | fd->inc_counter(QuestType::skill_damage, counter, 0, counter_dmg); 840 | } 841 | _DEBUG_MSG(1, "%s takes %u counter damage from %s\n", status_description(att_status).c_str(), counter_dmg, status_description(def_status).c_str()); 842 | remove_hp(fd, att_status, counter_dmg); 843 | prepend_on_death(fd); 844 | resolve_skill(fd); 845 | if (def_cardtype == CardType::assault && def_status->m_hp > 0 && fd->bg_effects.count(counterflux)) 846 | { 847 | unsigned flux_denominator = fd->bg_effects.at(counterflux) ? fd->bg_effects.at(counterflux) : 4; 848 | unsigned flux_value = (def_status->skill(counter) - 1) / flux_denominator + 1; 849 | _DEBUG_MSG(1, "Counterflux: %s heals itself and berserks for %u\n", status_description(def_status).c_str(), flux_value); 850 | add_hp(fd, def_status, flux_value); 851 | if (! def_status->m_sundered) 852 | { def_status->m_attack += flux_value; } 853 | } 854 | } 855 | unsigned corrosive_value = def_status->skill(corrosive); 856 | if (att_status->m_hp > 0 && corrosive_value > att_status->m_corroded_rate && skill_check(fd, def_status, att_status)) 857 | { 858 | // perform_skill_corrosive 859 | _DEBUG_MSG(1, "%s corrodes %s by %u\n", status_description(def_status).c_str(), status_description(att_status).c_str(), corrosive_value); 860 | att_status->m_corroded_rate = corrosive_value; 861 | } 862 | unsigned berserk_value = att_status->skill(berserk); 863 | if (att_status->m_hp > 0 && ! att_status->m_sundered && berserk_value > 0 && skill_check(fd, att_status, nullptr)) 864 | { 865 | // perform_skill_berserk 866 | att_status->m_attack += berserk_value; 867 | if (att_status->m_player == 0) 868 | { 869 | fd->inc_counter(QuestType::skill_use, berserk); 870 | } 871 | if (fd->bg_effects.count(enduringrage)) 872 | { 873 | unsigned bge_denominator = fd->bg_effects.at(enduringrage) ? fd->bg_effects.at(enduringrage) : 2; 874 | unsigned bge_value = (berserk_value - 1) / bge_denominator + 1; 875 | _DEBUG_MSG(1, "EnduringRage: %s heals and protects itself for %u\n", status_description(att_status).c_str(), bge_value); 876 | add_hp(fd, att_status, bge_value); 877 | att_status->m_protected += bge_value; 878 | } 879 | } 880 | do_leech(); 881 | unsigned valor_value = att_status->skill(valor); 882 | if (valor_value > 0 && ! att_status->m_sundered && fd->bg_effects.count(heroism) && def_cardtype == CardType::assault && def_status->m_hp <= 0) 883 | { 884 | _DEBUG_MSG(1, "Heroism: %s gain %u attack\n", status_description(att_status).c_str(), valor_value); 885 | att_status->m_attack += valor_value; 886 | } 887 | return att_dmg; 888 | } 889 | 890 | template 891 | void modify_attack_damage(unsigned pre_modifier_dmg) 892 | { 893 | assert(att_status->m_card->m_type == CardType::assault); 894 | att_dmg = pre_modifier_dmg; 895 | if (att_dmg == 0) 896 | { return; } 897 | std::string desc; 898 | unsigned legion_value = 0; 899 | if (! att_status->m_sundered) 900 | { 901 | // enhance damage 902 | unsigned legion_base = att_status->skill(legion); 903 | if (legion_base > 0) 904 | { 905 | auto & assaults = fd->tap->assaults; 906 | legion_value += att_status->m_index > 0 && assaults[att_status->m_index - 1].m_hp > 0 && assaults[att_status->m_index - 1].m_faction == att_status->m_faction; 907 | legion_value += att_status->m_index + 1 < assaults.size() && assaults[att_status->m_index + 1].m_hp > 0 && assaults[att_status->m_index + 1].m_faction == att_status->m_faction; 908 | if (legion_value > 0 && skill_check(fd, att_status, nullptr)) 909 | { 910 | legion_value *= legion_base; 911 | if (debug_print > 0) { desc += "+" + to_string(legion_value) + "(legion)"; } 912 | att_dmg += legion_value; 913 | } 914 | } 915 | unsigned rupture_value = att_status->skill(rupture); 916 | if (rupture_value > 0) 917 | { 918 | if (debug_print > 0) { desc += "+" + to_string(rupture_value) + "(rupture)"; } 919 | att_dmg += rupture_value; 920 | } 921 | unsigned venom_value = att_status->skill(venom); 922 | if (venom_value > 0 && def_status->m_poisoned > 0) 923 | { 924 | if (debug_print > 0) { desc += "+" + to_string(venom_value) + "(venom)"; } 925 | att_dmg += venom_value; 926 | } 927 | if (fd->bloodlust_value > 0) 928 | { 929 | if (debug_print > 0) { desc += "+" + to_string(fd->bloodlust_value) + "(bloodlust)"; } 930 | att_dmg += fd->bloodlust_value; 931 | } 932 | if(def_status->m_enfeebled > 0) 933 | { 934 | if(debug_print > 0) { desc += "+" + to_string(def_status->m_enfeebled) + "(enfeebled)"; } 935 | att_dmg += def_status->m_enfeebled; 936 | } 937 | } 938 | // prevent damage 939 | std::string reduced_desc; 940 | unsigned reduced_dmg(0); 941 | unsigned armor_value = def_status->skill(armor); 942 | if (def_status->m_card->m_type == CardType::assault && fd->bg_effects.count(fortification)) 943 | { 944 | for (auto && adj_status: fd->adjacent_assaults(def_status)) 945 | { 946 | armor_value = std::max(armor_value, adj_status->skill(armor)); 947 | } 948 | } 949 | if(armor_value > 0) 950 | { 951 | if(debug_print > 0) { reduced_desc += to_string(armor_value) + "(armor)"; } 952 | reduced_dmg += armor_value; 953 | } 954 | if(def_status->protected_value() > 0) 955 | { 956 | if(debug_print > 0) { reduced_desc += (reduced_desc.empty() ? "" : "+") + to_string(def_status->protected_value()) + "(protected)"; } 957 | reduced_dmg += def_status->protected_value(); 958 | } 959 | unsigned pierce_value = att_status->skill(pierce) + att_status->skill(rupture); 960 | if (reduced_dmg > 0 && pierce_value > 0) 961 | { 962 | if (debug_print > 0) { reduced_desc += "-" + to_string(pierce_value) + "(pierce)"; } 963 | reduced_dmg = safe_minus(reduced_dmg, pierce_value); 964 | } 965 | att_dmg = safe_minus(att_dmg, reduced_dmg); 966 | if(debug_print > 0) 967 | { 968 | if(!reduced_desc.empty()) { desc += "-[" + reduced_desc + "]"; } 969 | if(!desc.empty()) { desc += "=" + to_string(att_dmg); } 970 | _DEBUG_MSG(1, "%s attacks %s for %u%s damage\n", status_description(att_status).c_str(), status_description(def_status).c_str(), pre_modifier_dmg, desc.c_str()); 971 | } 972 | if (legion_value > 0 && can_be_healed(att_status) && fd->bg_effects.count(brigade)) 973 | { 974 | _DEBUG_MSG(1, "Brigade: %s heals itself for %u\n", status_description(att_status).c_str(), legion_value); 975 | add_hp(fd, att_status, legion_value); 976 | } 977 | } 978 | 979 | template 980 | void attack_damage() 981 | { 982 | remove_hp(fd, def_status, att_dmg); 983 | prepend_on_death(fd); 984 | resolve_skill(fd); 985 | } 986 | 987 | template 988 | void damage_dependant_pre_oa() {} 989 | 990 | template 991 | void do_leech() {} 992 | }; 993 | 994 | template<> 995 | void PerformAttack::attack_damage() 996 | { 997 | remove_commander_hp(fd, *def_status, att_dmg, true); 998 | } 999 | 1000 | template<> 1001 | void PerformAttack::damage_dependant_pre_oa() 1002 | { 1003 | unsigned poison_value = std::max(att_status->skill(poison), att_status->skill(venom)); 1004 | if (poison_value > def_status->m_poisoned && skill_check(fd, att_status, def_status)) 1005 | { 1006 | // perform_skill_poison 1007 | if (att_status->m_player == 0) 1008 | { 1009 | fd->inc_counter(QuestType::skill_use, poison); 1010 | } 1011 | _DEBUG_MSG(1, "%s poisons %s by %u\n", status_description(att_status).c_str(), status_description(def_status).c_str(), poison_value); 1012 | def_status->m_poisoned = poison_value; 1013 | } 1014 | unsigned inhibit_value = att_status->skill(inhibit); 1015 | if (inhibit_value > def_status->m_inhibited && skill_check(fd, att_status, def_status)) 1016 | { 1017 | // perform_skill_inhibit 1018 | _DEBUG_MSG(1, "%s inhibits %s by %u\n", status_description(att_status).c_str(), status_description(def_status).c_str(), inhibit_value); 1019 | def_status->m_inhibited = inhibit_value; 1020 | } 1021 | } 1022 | 1023 | template<> 1024 | void PerformAttack::do_leech() 1025 | { 1026 | unsigned leech_value = std::min(att_dmg, att_status->skill(leech)); 1027 | if(leech_value > 0 && skill_check(fd, att_status, nullptr)) 1028 | { 1029 | if (att_status->m_player == 0) 1030 | { 1031 | fd->inc_counter(QuestType::skill_use, leech); 1032 | } 1033 | _DEBUG_MSG(1, "%s leeches %u health\n", status_description(att_status).c_str(), leech_value); 1034 | add_hp(fd, att_status, leech_value); 1035 | } 1036 | } 1037 | 1038 | // General attack phase by the currently evaluated assault, taking into accounts exotic stuff such as flurry, etc. 1039 | unsigned attack_commander(Field* fd, CardStatus* att_status) 1040 | { 1041 | CardStatus* def_status{select_first_enemy_wall(fd)}; // defending wall 1042 | if(def_status != nullptr) 1043 | { 1044 | return PerformAttack{fd, att_status, def_status}.op(); 1045 | } 1046 | else 1047 | { 1048 | return PerformAttack{fd, att_status, &fd->tip->commander}.op(); 1049 | } 1050 | } 1051 | // Return true if actually attacks 1052 | bool attack_phase(Field* fd) 1053 | { 1054 | CardStatus* att_status(&fd->tap->assaults[fd->current_ci]); // attacking card 1055 | Storage& def_assaults(fd->tip->assaults); 1056 | 1057 | if (attack_power(att_status) == 0) 1058 | { 1059 | { // Bizarre behavior: Swipe activates if attack is corroded to 0 1060 | CardStatus * def_status = &fd->tip->assaults[fd->current_ci]; 1061 | unsigned swipe_value = att_status->skill(swipe); 1062 | if (alive_assault(def_assaults, fd->current_ci) && att_status->m_attack + att_status->m_rallied > att_status->m_weakened && swipe_value > 0) 1063 | { 1064 | for (auto && adj_status: fd->adjacent_assaults(def_status)) 1065 | { 1066 | unsigned swipe_dmg = safe_minus(swipe_value + def_status->m_enfeebled, def_status->protected_value()); 1067 | _DEBUG_MSG(1, "%s swipes %s for %u damage\n", status_description(att_status).c_str(), status_description(adj_status).c_str(), swipe_dmg); 1068 | remove_hp(fd, adj_status, swipe_dmg); 1069 | } 1070 | prepend_on_death(fd); 1071 | resolve_skill(fd); 1072 | } 1073 | } 1074 | return false; 1075 | } 1076 | 1077 | unsigned att_dmg = 0; 1078 | if (alive_assault(def_assaults, fd->current_ci)) 1079 | { 1080 | CardStatus * def_status = &fd->tip->assaults[fd->current_ci]; 1081 | att_dmg = PerformAttack{fd, att_status, def_status}.op(); 1082 | unsigned swipe_value = att_status->skill(swipe); 1083 | if (att_dmg > 0 && swipe_value > 0) 1084 | { 1085 | for (auto && adj_status: fd->adjacent_assaults(def_status)) 1086 | { 1087 | unsigned swipe_dmg = safe_minus(swipe_value + def_status->m_enfeebled, def_status->protected_value()); 1088 | _DEBUG_MSG(1, "%s swipes %s for %u damage\n", status_description(att_status).c_str(), status_description(adj_status).c_str(), swipe_dmg); 1089 | remove_hp(fd, adj_status, swipe_dmg); 1090 | } 1091 | prepend_on_death(fd); 1092 | resolve_skill(fd); 1093 | } 1094 | } 1095 | else 1096 | { 1097 | // might be blocked by walls 1098 | att_dmg = attack_commander(fd, att_status); 1099 | } 1100 | 1101 | if (att_dmg > 0 && !fd->assault_bloodlusted && fd->bg_effects.count(bloodlust)) 1102 | { 1103 | fd->bloodlust_value += fd->bg_effects.at(bloodlust); 1104 | fd->assault_bloodlusted = true; 1105 | } 1106 | 1107 | return true; 1108 | } 1109 | 1110 | //---------------------- $65 active skills implementation ---------------------- 1111 | template< 1112 | bool C 1113 | , typename T1 1114 | , typename T2 1115 | > 1116 | struct if_ 1117 | { 1118 | typedef T1 type; 1119 | }; 1120 | 1121 | template< 1122 | typename T1 1123 | , typename T2 1124 | > 1125 | struct if_ 1126 | { 1127 | typedef T2 type; 1128 | }; 1129 | 1130 | template 1131 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1132 | { return dst->m_hp > 0; } 1133 | 1134 | template<> 1135 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1136 | { 1137 | return dst->has_skill(s.s) && (!(BEGIN_ACTIVATION < s.s && s.s < END_ACTIVATION) || is_active(dst)); 1138 | } 1139 | 1140 | /* 1141 | * Target active units: Activation (Mortar) 1142 | * Target everything: Defensive (Refresh), Combat-Modifier (Rupture, Venom) 1143 | */ 1144 | template<> 1145 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1146 | { 1147 | return dst->has_skill(s.s) && !dst->has_skill(s.s2) && (!(BEGIN_ACTIVATION < s.s2 && s.s2 < END_ACTIVATION) || is_active(dst)); 1148 | } 1149 | 1150 | template<> 1151 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1152 | { return(can_be_healed(dst)); } 1153 | 1154 | template<> 1155 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1156 | { return(can_be_healed(dst)); } 1157 | 1158 | template<> 1159 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1160 | { 1161 | return is_active_next_turn(dst); 1162 | } 1163 | 1164 | template<> 1165 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1166 | { 1167 | if (dst->m_overloaded || has_attacked(dst) || !is_active(dst)) 1168 | { 1169 | return false; 1170 | } 1171 | bool has_inhibited_unit = false; 1172 | for (const auto & c: fd->players[dst->m_player]->assaults.m_indirect) 1173 | { 1174 | if (c->m_hp > 0 && c->m_inhibited) 1175 | { 1176 | has_inhibited_unit = true; 1177 | break; 1178 | } 1179 | } 1180 | for (const auto & ss: dst->m_card->m_skills) 1181 | { 1182 | if (dst->m_skill_cd[ss.id] > 0) 1183 | { 1184 | continue; 1185 | } 1186 | Skill evolved_skill_id = static_cast(ss.id + dst->m_evolved_skill_offset[ss.id]); 1187 | if (BEGIN_ACTIVATION_HARMFUL < evolved_skill_id && evolved_skill_id < END_ACTIVATION_HARMFUL) 1188 | { 1189 | return true; 1190 | } 1191 | if (has_inhibited_unit && (evolved_skill_id == heal || evolved_skill_id == protect || evolved_skill_id == rally)) 1192 | { 1193 | return true; 1194 | } 1195 | } 1196 | return false; 1197 | } 1198 | 1199 | template<> 1200 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1201 | { 1202 | return ! dst->m_sundered && (fd->tapi == dst->m_player ? is_active(dst) && !has_attacked(dst) : is_active_next_turn(dst)); 1203 | } 1204 | 1205 | template<> 1206 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1207 | { 1208 | return ! src->m_rush_attempted && dst->m_delay >= (src->m_card->m_type == CardType::assault && dst->m_index < src->m_index ? 2u : 1u); 1209 | } 1210 | 1211 | template<> 1212 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1213 | { 1214 | return attack_power(dst) > 0 && is_active_next_turn(dst); 1215 | } 1216 | 1217 | template<> 1218 | inline bool skill_predicate(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1219 | { 1220 | return attack_power(dst) > 0 && is_active_next_turn(dst); 1221 | } 1222 | 1223 | template 1224 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1225 | { assert(false); } 1226 | 1227 | template<> 1228 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1229 | { 1230 | dst->m_enfeebled += s.x; 1231 | } 1232 | 1233 | template<> 1234 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1235 | { 1236 | dst->m_enhanced_value[s.s + dst->m_primary_skill_offset[s.s]] += s.x; 1237 | } 1238 | 1239 | template<> 1240 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1241 | { 1242 | auto primary_s1 = dst->m_primary_skill_offset[s.s] + s.s; 1243 | auto primary_s2 = dst->m_primary_skill_offset[s.s2] + s.s2; 1244 | dst->m_primary_skill_offset[s.s] = primary_s2 - s.s; 1245 | dst->m_primary_skill_offset[s.s2] = primary_s1 - s.s2; 1246 | dst->m_evolved_skill_offset[primary_s1] = s.s2 - primary_s1; 1247 | dst->m_evolved_skill_offset[primary_s2] = s.s - primary_s2; 1248 | } 1249 | 1250 | template<> 1251 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1252 | { 1253 | add_hp(fd, dst, s.x); 1254 | } 1255 | 1256 | template<> 1257 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1258 | { 1259 | dst->m_jammed = true; 1260 | } 1261 | 1262 | template<> 1263 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1264 | { 1265 | add_hp(fd, dst, s.x); 1266 | } 1267 | 1268 | template<> 1269 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1270 | { 1271 | if (dst->m_card->m_type == CardType::structure) 1272 | { 1273 | remove_hp(fd, dst, s.x); 1274 | } 1275 | else 1276 | { 1277 | unsigned strike_dmg = safe_minus((s.x + 1) / 2 + dst->m_enfeebled, src->m_overloaded ? 0 : dst->protected_value()); 1278 | remove_hp(fd, dst, strike_dmg); 1279 | } 1280 | } 1281 | 1282 | template<> 1283 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1284 | { 1285 | dst->m_overloaded = true; 1286 | } 1287 | 1288 | template<> 1289 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1290 | { 1291 | dst->m_protected += s.x; 1292 | } 1293 | 1294 | template<> 1295 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1296 | { 1297 | dst->m_rallied += s.x; 1298 | } 1299 | 1300 | template<> 1301 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1302 | { 1303 | dst->m_delay -= 1; 1304 | if (dst->m_delay == 0) 1305 | { 1306 | check_and_perform_valor(fd, dst); 1307 | } 1308 | } 1309 | 1310 | template<> 1311 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1312 | { 1313 | remove_hp(fd, dst, s.x); 1314 | } 1315 | 1316 | template<> 1317 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1318 | { 1319 | unsigned strike_dmg = safe_minus(s.x + dst->m_enfeebled, src->m_overloaded ? 0 : dst->protected_value()); 1320 | remove_hp(fd, dst, strike_dmg); 1321 | } 1322 | 1323 | template<> 1324 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1325 | { 1326 | dst->m_sundered = true; 1327 | dst->m_weakened += std::min(s.x, attack_power(dst)); 1328 | } 1329 | 1330 | template<> 1331 | inline void perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s) 1332 | { 1333 | dst->m_weakened += std::min(s.x, attack_power(dst)); 1334 | } 1335 | 1336 | template 1337 | inline unsigned select_fast(Field* fd, CardStatus* src, const std::vector& cards, const SkillSpec& s) 1338 | { 1339 | if (s.y == allfactions || fd->bg_effects.count(metamorphosis)) 1340 | { 1341 | return(fd->make_selection_array(cards.begin(), cards.end(), [fd, src, s](CardStatus* c){return(skill_predicate(fd, src, c, s));})); 1342 | } 1343 | else 1344 | { 1345 | return(fd->make_selection_array(cards.begin(), cards.end(), [fd, src, s](CardStatus* c){return((c->m_faction == s.y || c->m_faction == progenitor) && skill_predicate(fd, src, c, s));})); 1346 | } 1347 | } 1348 | 1349 | template<> 1350 | inline unsigned select_fast(Field* fd, CardStatus* src, const std::vector& cards, const SkillSpec& s) 1351 | { 1352 | fd->selection_array.clear(); 1353 | for (auto && adj_status: fd->adjacent_assaults(src)) 1354 | { 1355 | if (skill_predicate(fd, src, adj_status, s)) 1356 | { 1357 | fd->selection_array.push_back(adj_status); 1358 | } 1359 | } 1360 | return fd->selection_array.size(); 1361 | } 1362 | 1363 | inline std::vector& skill_targets_hostile_assault(Field* fd, CardStatus* src) 1364 | { 1365 | return(fd->players[opponent(src->m_player)]->assaults.m_indirect); 1366 | } 1367 | 1368 | inline std::vector& skill_targets_allied_assault(Field* fd, CardStatus* src) 1369 | { 1370 | return(fd->players[src->m_player]->assaults.m_indirect); 1371 | } 1372 | 1373 | inline std::vector& skill_targets_hostile_structure(Field* fd, CardStatus* src) 1374 | { 1375 | return(fd->players[opponent(src->m_player)]->structures.m_indirect); 1376 | } 1377 | 1378 | inline std::vector& skill_targets_allied_structure(Field* fd, CardStatus* src) 1379 | { 1380 | return(fd->players[src->m_player]->structures.m_indirect); 1381 | } 1382 | 1383 | template 1384 | std::vector& skill_targets(Field* fd, CardStatus* src) 1385 | { 1386 | std::cerr << "skill_targets: Error: no specialization for " << skill_names[skill] << "\n"; 1387 | throw; 1388 | } 1389 | 1390 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1391 | { return(skill_targets_hostile_assault(fd, src)); } 1392 | 1393 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1394 | { return(skill_targets_allied_assault(fd, src)); } 1395 | 1396 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1397 | { return(skill_targets_allied_assault(fd, src)); } 1398 | 1399 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1400 | { return(skill_targets_allied_assault(fd, src)); } 1401 | 1402 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1403 | { return(skill_targets_hostile_assault(fd, src)); } 1404 | 1405 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1406 | { return(skill_targets_allied_assault(fd, src)); } 1407 | 1408 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1409 | { return(skill_targets_allied_assault(fd, src)); } 1410 | 1411 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1412 | { return(skill_targets_allied_assault(fd, src)); } 1413 | 1414 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1415 | { return(skill_targets_allied_assault(fd, src)); } 1416 | 1417 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1418 | { return(skill_targets_allied_assault(fd, src)); } 1419 | 1420 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1421 | { return(skill_targets_hostile_structure(fd, src)); } 1422 | 1423 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1424 | { return(skill_targets_hostile_assault(fd, src)); } 1425 | 1426 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1427 | { return(skill_targets_hostile_assault(fd, src)); } 1428 | 1429 | template<> std::vector& skill_targets(Field* fd, CardStatus* src) 1430 | { return(skill_targets_hostile_assault(fd, src)); } 1431 | 1432 | template 1433 | bool check_and_perform_skill(Field* fd, CardStatus* src, CardStatus* dst, const SkillSpec& s, bool is_evadable, bool & has_counted_quest) 1434 | { 1435 | if(skill_check(fd, src, dst)) 1436 | { 1437 | if (src->m_player == 0 && ! has_counted_quest) 1438 | { 1439 | fd->inc_counter(QuestType::skill_use, skill_id, dst->m_card->m_id); 1440 | has_counted_quest = true; 1441 | } 1442 | if (is_evadable && 1443 | dst->m_evaded < dst->skill(evade) && 1444 | skill_check(fd, dst, src)) 1445 | { 1446 | ++ dst->m_evaded; 1447 | _DEBUG_MSG(1, "%s %s on %s but it evades\n", status_description(src).c_str(), skill_short_description(s).c_str(), status_description(dst).c_str()); 1448 | return(false); 1449 | } 1450 | _DEBUG_MSG(1, "%s %s on %s\n", status_description(src).c_str(), skill_short_description(s).c_str(), status_description(dst).c_str()); 1451 | perform_skill(fd, src, dst, s); 1452 | if (s.c > 0) 1453 | { 1454 | src->m_skill_cd[skill_id] = s.c; 1455 | } 1456 | return(true); 1457 | } 1458 | _DEBUG_MSG(1, "(CANCELLED) %s %s on %s\n", status_description(src).c_str(), skill_short_description(s).c_str(), status_description(dst).c_str()); 1459 | return(false); 1460 | } 1461 | 1462 | bool check_and_perform_valor(Field* fd, CardStatus* src) 1463 | { 1464 | unsigned valor_value = src->skill(valor); 1465 | if (valor_value > 0 && ! src->m_sundered && skill_check(fd, src, nullptr)) 1466 | { 1467 | unsigned opponent_player = opponent(src->m_player); 1468 | const CardStatus * dst = fd->players[opponent_player]->assaults.size() > src->m_index ? 1469 | &fd->players[opponent_player]->assaults[src->m_index] : 1470 | nullptr; 1471 | if (dst == nullptr || dst->m_hp <= 0) 1472 | { 1473 | _DEBUG_MSG(1, "%s loses Valor (no blocker)\n", status_description(src).c_str()); 1474 | return false; 1475 | } 1476 | else if (attack_power(dst) <= attack_power(src)) 1477 | { 1478 | _DEBUG_MSG(1, "%s loses Valor (weak blocker %s)\n", status_description(src).c_str(), status_description(dst).c_str()); 1479 | return false; 1480 | } 1481 | if (src->m_player == 0) 1482 | { 1483 | fd->inc_counter(QuestType::skill_use, valor); 1484 | } 1485 | _DEBUG_MSG(1, "%s activates Valor %u\n", status_description(src).c_str(), valor_value); 1486 | src->m_attack += valor_value; 1487 | return true; 1488 | } 1489 | return false; 1490 | } 1491 | 1492 | template 1493 | size_t select_targets(Field* fd, CardStatus* src, const SkillSpec& s) 1494 | { 1495 | std::vector& cards(skill_targets(fd, src)); 1496 | size_t n_candidates = select_fast(fd, src, cards, s); 1497 | if (n_candidates == 0) 1498 | { 1499 | return n_candidates; 1500 | } 1501 | _DEBUG_SELECTION("%s", skill_names[skill_id].c_str()); 1502 | unsigned n_targets = s.n > 0 ? s.n : 1; 1503 | if (s.all || n_targets >= n_candidates || skill_id == mend) // target all or mend 1504 | { 1505 | return n_candidates; 1506 | } 1507 | for (unsigned i = 0; i < n_targets; ++i) 1508 | { 1509 | std::swap(fd->selection_array[i], fd->selection_array[fd->rand(i, n_candidates - 1)]); 1510 | } 1511 | fd->selection_array.resize(n_targets); 1512 | if (n_targets > 1) 1513 | { 1514 | std::sort(fd->selection_array.begin(), fd->selection_array.end(), [](const CardStatus * a, const CardStatus * b) { return a->m_index < b->m_index; }); 1515 | } 1516 | return n_targets; 1517 | } 1518 | 1519 | template<> 1520 | size_t select_targets(Field* fd, CardStatus* src, const SkillSpec& s) 1521 | { 1522 | size_t n_candidates = select_fast(fd, src, skill_targets(fd, src), s); 1523 | if (n_candidates == 0) 1524 | { 1525 | n_candidates = select_fast(fd, src, skill_targets(fd, src), s); 1526 | if (n_candidates == 0) 1527 | { 1528 | return n_candidates; 1529 | } 1530 | } 1531 | _DEBUG_SELECTION("%s", skill_names[mortar].c_str()); 1532 | unsigned n_targets = s.n > 0 ? s.n : 1; 1533 | if (s.all || n_targets >= n_candidates) 1534 | { 1535 | return n_candidates; 1536 | } 1537 | for (unsigned i = 0; i < n_targets; ++i) 1538 | { 1539 | std::swap(fd->selection_array[i], fd->selection_array[fd->rand(i, n_candidates - 1)]); 1540 | } 1541 | fd->selection_array.resize(n_targets); 1542 | if (n_targets > 1) 1543 | { 1544 | std::sort(fd->selection_array.begin(), fd->selection_array.end(), [](const CardStatus * a, const CardStatus * b) { return a->m_index < b->m_index; }); 1545 | } 1546 | return n_targets; 1547 | } 1548 | 1549 | template 1550 | void perform_targetted_allied_fast(Field* fd, CardStatus* src, const SkillSpec& s) 1551 | { 1552 | select_targets(fd, src, s); 1553 | unsigned num_inhibited = 0; 1554 | bool has_counted_quest = false; 1555 | for (CardStatus * dst: fd->selection_array) 1556 | { 1557 | if (dst->m_inhibited > 0 && !src->m_overloaded) 1558 | { 1559 | _DEBUG_MSG(1, "%s %s on %s but it is inhibited\n", status_description(src).c_str(), skill_short_description(s).c_str(), status_description(dst).c_str()); 1560 | -- dst->m_inhibited; 1561 | ++ num_inhibited; 1562 | continue; 1563 | } 1564 | check_and_perform_skill(fd, src, dst, s, false, has_counted_quest); 1565 | } 1566 | if (num_inhibited > 0 && fd->bg_effects.count(divert)) 1567 | { 1568 | SkillSpec diverted_ss = s; 1569 | diverted_ss.y = allfactions; 1570 | diverted_ss.n = 1; 1571 | diverted_ss.all = false; 1572 | for (unsigned i = 0; i < num_inhibited; ++ i) 1573 | { 1574 | select_targets(fd, &fd->tip->commander, diverted_ss); 1575 | for (CardStatus * dst: fd->selection_array) 1576 | { 1577 | if (dst->m_inhibited > 0) 1578 | { 1579 | _DEBUG_MSG(1, "%s %s (Diverted) on %s but it is inhibited\n", status_description(src).c_str(), skill_short_description(diverted_ss).c_str(), status_description(dst).c_str()); 1580 | -- dst->m_inhibited; 1581 | continue; 1582 | } 1583 | _DEBUG_MSG(1, "%s %s (Diverted) on %s\n", status_description(src).c_str(), skill_short_description(diverted_ss).c_str(), status_description(dst).c_str()); 1584 | perform_skill(fd, src, dst, diverted_ss); 1585 | } 1586 | } 1587 | } 1588 | } 1589 | 1590 | void perform_targetted_allied_fast_rush(Field* fd, CardStatus* src, const SkillSpec& s) 1591 | { 1592 | if (src->m_card->m_type == CardType::commander) 1593 | { // BGE skills are casted as by commander 1594 | perform_targetted_allied_fast(fd, src, s); 1595 | return; 1596 | } 1597 | if (src->m_rush_attempted) 1598 | { 1599 | _DEBUG_MSG(2, "%s does not check Rush again.\n", status_description(src).c_str()); 1600 | return; 1601 | } 1602 | _DEBUG_MSG(1, "%s attempts to activate Rush.\n", status_description(src).c_str()); 1603 | perform_targetted_allied_fast(fd, src, s); 1604 | src->m_rush_attempted = true; 1605 | } 1606 | 1607 | template 1608 | void perform_targetted_hostile_fast(Field* fd, CardStatus* src, const SkillSpec& s) 1609 | { 1610 | select_targets(fd, src, s); 1611 | bool has_counted_quest = false; 1612 | std::vector paybackers; 1613 | if (fd->bg_effects.count(turningtides) && skill_id == weaken) 1614 | { 1615 | unsigned turningtides_value = 0; 1616 | for (CardStatus * dst: fd->selection_array) 1617 | { 1618 | unsigned old_attack = attack_power(dst); 1619 | if (check_and_perform_skill(fd, src, dst, s, ! src->m_overloaded, has_counted_quest)) 1620 | { 1621 | turningtides_value = std::max(turningtides_value, safe_minus(old_attack, attack_power(dst))); 1622 | // Payback 1623 | if(dst->m_paybacked < dst->skill(payback) && skill_check(fd, dst, src) && 1624 | skill_predicate(fd, src, src, s) && skill_check(fd, src, dst)) 1625 | { 1626 | paybackers.push_back(dst); 1627 | } 1628 | } 1629 | } 1630 | if (turningtides_value > 0) 1631 | { 1632 | SkillSpec ss_rally{rally, turningtides_value, allfactions, 0, 0, no_skill, no_skill, s.all,}; 1633 | _DEBUG_MSG(1, "TurningTides %u!\n", turningtides_value); 1634 | perform_targetted_allied_fast(fd, &fd->players[src->m_player]->commander, ss_rally); 1635 | } 1636 | for (CardStatus * pb_status: paybackers) 1637 | { 1638 | ++ pb_status->m_paybacked; 1639 | unsigned old_attack = attack_power(src); 1640 | _DEBUG_MSG(1, "%s Payback %s on %s\n", status_description(pb_status).c_str(), skill_short_description(s).c_str(), status_description(src).c_str()); 1641 | perform_skill(fd, pb_status, src, s); 1642 | turningtides_value = std::max(turningtides_value, safe_minus(old_attack, attack_power(src))); 1643 | if (turningtides_value > 0) 1644 | { 1645 | SkillSpec ss_rally{rally, turningtides_value, allfactions, 0, 0, no_skill, no_skill, false,}; 1646 | _DEBUG_MSG(1, "Paybacked TurningTides %u!\n", turningtides_value); 1647 | perform_targetted_allied_fast(fd, &fd->players[pb_status->m_player]->commander, ss_rally); 1648 | } 1649 | } 1650 | return; 1651 | } 1652 | for (CardStatus * dst: fd->selection_array) 1653 | { 1654 | if (check_and_perform_skill(fd, src, dst, s, ! src->m_overloaded, has_counted_quest)) 1655 | { 1656 | // Payback 1657 | if(dst->m_paybacked < dst->skill(payback) && skill_check(fd, dst, src) && 1658 | skill_predicate(fd, src, src, s) && skill_check(fd, src, dst)) 1659 | { 1660 | paybackers.push_back(dst); 1661 | } 1662 | } 1663 | } 1664 | prepend_on_death(fd); // skills 1665 | for (CardStatus * pb_status: paybackers) 1666 | { 1667 | ++ pb_status->m_paybacked; 1668 | _DEBUG_MSG(1, "%s Payback %s on %s\n", status_description(pb_status).c_str(), skill_short_description(s).c_str(), status_description(src).c_str()); 1669 | perform_skill(fd, pb_status, src, s); 1670 | } 1671 | prepend_on_death(fd); // paybacked skills 1672 | } 1673 | 1674 | //------------------------------------------------------------------------------ 1675 | Results play(Field* fd) 1676 | { 1677 | fd->players[0]->commander.m_player = 0; 1678 | fd->players[1]->commander.m_player = 1; 1679 | fd->tapi = fd->gamemode == surge ? 1 : 0; 1680 | fd->tipi = opponent(fd->tapi); 1681 | fd->tap = fd->players[fd->tapi]; 1682 | fd->tip = fd->players[fd->tipi]; 1683 | fd->end = false; 1684 | 1685 | // Play fortresses 1686 | for (unsigned _ = 0; _ < 2; ++ _) 1687 | { 1688 | for (const Card* played_card: fd->tap->deck->shuffled_forts) 1689 | { 1690 | PlayCard(played_card, fd).op(); 1691 | } 1692 | std::swap(fd->tapi, fd->tipi); 1693 | std::swap(fd->tap, fd->tip); 1694 | } 1695 | 1696 | while(__builtin_expect(fd->turn <= turn_limit && !fd->end, true)) 1697 | { 1698 | fd->current_phase = Field::playcard_phase; 1699 | // Initialize stuff, remove dead cards 1700 | _DEBUG_MSG(1, "------------------------------------------------------------------------\n" 1701 | "TURN %u begins for %s\n", fd->turn, status_description(&fd->tap->commander).c_str()); 1702 | turn_start_phase(fd); 1703 | 1704 | // Play a card 1705 | const Card* played_card(fd->tap->deck->next()); 1706 | if(played_card) 1707 | { 1708 | // Evaluate skill Allegiance 1709 | for (CardStatus * status : fd->tap->assaults.m_indirect) 1710 | { 1711 | unsigned allegiance_value = status->skill(allegiance); 1712 | assert(status->m_card); 1713 | if (allegiance_value > 0 && status->m_hp > 0 && status->m_card->m_faction == played_card->m_faction) 1714 | { 1715 | _DEBUG_MSG(1, "%s activates Allegiance %u\n", status_description(status).c_str(), allegiance_value); 1716 | if (! status->m_sundered) 1717 | { status->m_attack += allegiance_value; } 1718 | status->m_max_hp += allegiance_value; 1719 | status->m_hp += allegiance_value; 1720 | } 1721 | } 1722 | // End Evaluate skill Allegiance 1723 | switch(played_card->m_type) 1724 | { 1725 | case CardType::assault: 1726 | PlayCard(played_card, fd).op(); 1727 | break; 1728 | case CardType::structure: 1729 | PlayCard(played_card, fd).op(); 1730 | break; 1731 | case CardType::commander: 1732 | case CardType::num_cardtypes: 1733 | _DEBUG_MSG(0, "Unknown card type: #%u %s: %u\n", played_card->m_id, card_description(fd->cards, played_card).c_str(), played_card->m_type); 1734 | assert(false); 1735 | break; 1736 | } 1737 | } 1738 | if(__builtin_expect(fd->end, false)) { break; } 1739 | 1740 | // Evaluate Heroism BGE skills 1741 | if (fd->bg_effects.count(heroism)) 1742 | { 1743 | for (CardStatus * dst: fd->tap->assaults.m_indirect) 1744 | { 1745 | unsigned bge_value = (dst->skill(valor) + 1) / 2; 1746 | if (bge_value <= 0) 1747 | { continue; } 1748 | SkillSpec ss_protect{protect, bge_value, allfactions, 0, 0, no_skill, no_skill, false,}; 1749 | if (dst->m_inhibited > 0) 1750 | { 1751 | _DEBUG_MSG(1, "Heroism: %s on %s but it is inhibited\n", skill_short_description(ss_protect).c_str(), status_description(dst).c_str()); 1752 | -- dst->m_inhibited; 1753 | if (fd->bg_effects.count(divert)) 1754 | { 1755 | SkillSpec diverted_ss = ss_protect; 1756 | diverted_ss.y = allfactions; 1757 | diverted_ss.n = 1; 1758 | diverted_ss.all = false; 1759 | // for (unsigned i = 0; i < num_inhibited; ++ i) 1760 | { 1761 | select_targets(fd, &fd->tip->commander, diverted_ss); 1762 | for (CardStatus * dst: fd->selection_array) 1763 | { 1764 | if (dst->m_inhibited > 0) 1765 | { 1766 | _DEBUG_MSG(1, "Heroism: %s (Diverted) on %s but it is inhibited\n", skill_short_description(diverted_ss).c_str(), status_description(dst).c_str()); 1767 | -- dst->m_inhibited; 1768 | continue; 1769 | } 1770 | _DEBUG_MSG(1, "Heroism: %s (Diverted) on %s\n", skill_short_description(diverted_ss).c_str(), status_description(dst).c_str()); 1771 | perform_skill(fd, &fd->tap->commander, dst, diverted_ss); // XXX: the caster 1772 | } 1773 | } 1774 | } 1775 | continue; 1776 | } 1777 | bool has_counted_quest = false; 1778 | check_and_perform_skill(fd, &fd->tap->commander, dst, ss_protect, false, has_counted_quest); 1779 | } 1780 | } 1781 | 1782 | // Evaluate activation BGE skills 1783 | for (const auto & bg_skill: fd->bg_skills[fd->tapi]) 1784 | { 1785 | _DEBUG_MSG(2, "Evaluating BG skill %s\n", skill_description(fd->cards, bg_skill).c_str()); 1786 | fd->skill_queue.emplace_back(&fd->tap->commander, bg_skill); 1787 | resolve_skill(fd); 1788 | } 1789 | if (__builtin_expect(fd->end, false)) { break; } 1790 | 1791 | // Evaluate commander 1792 | fd->current_phase = Field::commander_phase; 1793 | evaluate_skills(fd, &fd->tap->commander, fd->tap->commander.m_card->m_skills); 1794 | if(__builtin_expect(fd->end, false)) { break; } 1795 | 1796 | // Evaluate structures 1797 | fd->current_phase = Field::structures_phase; 1798 | for(fd->current_ci = 0; !fd->end && fd->current_ci < fd->tap->structures.size(); ++fd->current_ci) 1799 | { 1800 | CardStatus* current_status(&fd->tap->structures[fd->current_ci]); 1801 | if (!is_active(current_status)) 1802 | { 1803 | _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str()); 1804 | } 1805 | else 1806 | { 1807 | evaluate_skills(fd, current_status, current_status->m_card->m_skills); 1808 | } 1809 | } 1810 | // Evaluate assaults 1811 | fd->current_phase = Field::assaults_phase; 1812 | fd->bloodlust_value = 0; 1813 | for(fd->current_ci = 0; !fd->end && fd->current_ci < fd->tap->assaults.size(); ++fd->current_ci) 1814 | { 1815 | // ca: current assault 1816 | CardStatus* current_status(&fd->tap->assaults[fd->current_ci]); 1817 | bool attacked = false; 1818 | if (!is_active(current_status)) 1819 | { 1820 | _DEBUG_MSG(2, "%s cannot take action.\n", status_description(current_status).c_str()); 1821 | } 1822 | else 1823 | { 1824 | fd->assault_bloodlusted = false; 1825 | evaluate_skills(fd, current_status, current_status->m_card->m_skills, &attacked); 1826 | if (__builtin_expect(fd->end, false)) { break; } 1827 | } 1828 | if (current_status->m_corroded_rate > 0) 1829 | { 1830 | if (attacked) 1831 | { 1832 | unsigned v = std::min(current_status->m_corroded_rate, attack_power(current_status)); 1833 | _DEBUG_MSG(1, "%s loses Attack by %u.\n", status_description(current_status).c_str(), v); 1834 | current_status->m_corroded_weakened += v; 1835 | } 1836 | else 1837 | { 1838 | _DEBUG_MSG(1, "%s loses Status corroded.\n", status_description(current_status).c_str()); 1839 | current_status->m_corroded_rate = 0; 1840 | current_status->m_corroded_weakened = 0; 1841 | } 1842 | } 1843 | current_status->m_step = CardStep::attacked; 1844 | } 1845 | fd->current_phase = Field::end_phase; 1846 | turn_end_phase(fd); 1847 | if(__builtin_expect(fd->end, false)) { break; } 1848 | _DEBUG_MSG(1, "TURN %u ends for %s\n", fd->turn, status_description(&fd->tap->commander).c_str()); 1849 | std::swap(fd->tapi, fd->tipi); 1850 | std::swap(fd->tap, fd->tip); 1851 | ++fd->turn; 1852 | } 1853 | const auto & p = fd->players; 1854 | unsigned raid_damage = 0; 1855 | unsigned quest_score = 0; 1856 | switch (fd->optimization_mode) 1857 | { 1858 | case OptimizationMode::raid: 1859 | raid_damage = 15 + (std::min(p[1]->deck->deck_size, (fd->turn + 1) / 2) - p[1]->assaults.size() - p[1]->structures.size()) - (10 * p[1]->commander.m_hp / p[1]->commander.m_max_hp); 1860 | break; 1861 | case OptimizationMode::quest: 1862 | if (fd->quest.quest_type == QuestType::card_survival) 1863 | { 1864 | for (const auto & status: p[0]->assaults.m_indirect) 1865 | { fd->quest_counter += (fd->quest.quest_key == status->m_card->m_id); } 1866 | for (const auto & status: p[0]->structures.m_indirect) 1867 | { fd->quest_counter += (fd->quest.quest_key == status->m_card->m_id); } 1868 | for (const auto & card: p[0]->deck->shuffled_cards) 1869 | { fd->quest_counter += (fd->quest.quest_key == card->m_id); } 1870 | } 1871 | quest_score = fd->quest.must_fulfill ? (fd->quest_counter >= fd->quest.quest_value ? fd->quest.quest_score : 0) : std::min(fd->quest.quest_score, fd->quest.quest_score * fd->quest_counter / fd->quest.quest_value); 1872 | _DEBUG_MSG(1, "Quest: %u / %u = %u%%.\n", fd->quest_counter, fd->quest.quest_value, quest_score); 1873 | break; 1874 | default: 1875 | break; 1876 | } 1877 | // you lose 1878 | if(fd->players[0]->commander.m_hp == 0) 1879 | { 1880 | _DEBUG_MSG(1, "You lose.\n"); 1881 | switch (fd->optimization_mode) 1882 | { 1883 | case OptimizationMode::raid: return {0, 0, 1, raid_damage}; 1884 | case OptimizationMode::brawl: return {0, 0, 1, 5}; 1885 | case OptimizationMode::quest: return {0, 0, 1, fd->quest.must_win ? 0 : quest_score}; 1886 | default: return {0, 0, 1, 0}; 1887 | } 1888 | } 1889 | // you win 1890 | if(fd->players[1]->commander.m_hp == 0) 1891 | { 1892 | _DEBUG_MSG(1, "You win.\n"); 1893 | switch (fd->optimization_mode) 1894 | { 1895 | case OptimizationMode::brawl: 1896 | { 1897 | unsigned brawl_score = 57 1898 | - (10 * (p[0]->commander.m_max_hp - p[0]->commander.m_hp) / p[0]->commander.m_max_hp) 1899 | + (p[0]->assaults.size() + p[0]->structures.size() + p[0]->deck->shuffled_cards.size()) 1900 | - (p[1]->assaults.size() + p[1]->structures.size() + p[1]->deck->shuffled_cards.size()) 1901 | - fd->turn / 4; 1902 | return {1, 0, 0, brawl_score}; 1903 | } 1904 | case OptimizationMode::campaign: 1905 | { 1906 | unsigned campaign_score = 100 - 10 * (std::min(p[0]->deck->cards.size(), (fd->turn + 1) / 2) - p[0]->assaults.size() - p[0]->structures.size()); 1907 | return {1, 0, 0, campaign_score}; 1908 | } 1909 | case OptimizationMode::quest: return {1, 0, 0, fd->quest.win_score + quest_score}; 1910 | default: 1911 | return {1, 0, 0, 100}; 1912 | } 1913 | } 1914 | if (fd->turn > turn_limit) 1915 | { 1916 | _DEBUG_MSG(1, "Stall after %u turns.\n", turn_limit); 1917 | switch (fd->optimization_mode) 1918 | { 1919 | case OptimizationMode::defense: return {0, 1, 0, 100}; 1920 | case OptimizationMode::raid: return {0, 1, 0, raid_damage}; 1921 | case OptimizationMode::brawl: return {0, 1, 0, 5}; 1922 | case OptimizationMode::quest: return {0, 1, 0, fd->quest.must_win ? 0 : quest_score}; 1923 | default: return {0, 1, 0, 0}; 1924 | } 1925 | } 1926 | 1927 | // Huh? How did we get here? 1928 | assert(false); 1929 | return {0, 0, 0, 0}; 1930 | } 1931 | //------------------------------------------------------------------------------ 1932 | void fill_skill_table() 1933 | { 1934 | memset(skill_table, 0, sizeof skill_table); 1935 | skill_table[mortar] = perform_targetted_hostile_fast; 1936 | skill_table[enfeeble] = perform_targetted_hostile_fast; 1937 | skill_table[enhance] = perform_targetted_allied_fast; 1938 | skill_table[evolve] = perform_targetted_allied_fast; 1939 | skill_table[heal] = perform_targetted_allied_fast; 1940 | skill_table[jam] = perform_targetted_hostile_fast; 1941 | skill_table[mend] = perform_targetted_allied_fast; 1942 | skill_table[overload] = perform_targetted_allied_fast; 1943 | skill_table[protect] = perform_targetted_allied_fast; 1944 | skill_table[rally] = perform_targetted_allied_fast; 1945 | skill_table[rush] = perform_targetted_allied_fast_rush; 1946 | skill_table[siege] = perform_targetted_hostile_fast; 1947 | skill_table[strike] = perform_targetted_hostile_fast; 1948 | skill_table[sunder] = perform_targetted_hostile_fast; 1949 | skill_table[weaken] = perform_targetted_hostile_fast; 1950 | } 1951 | -------------------------------------------------------------------------------- /sim.h: -------------------------------------------------------------------------------- 1 | #ifndef SIM_H_INCLUDED 2 | #define SIM_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "tyrant.h" 15 | 16 | class Card; 17 | class Cards; 18 | class Deck; 19 | class Field; 20 | class Achievement; 21 | 22 | extern unsigned turn_limit; 23 | 24 | inline unsigned safe_minus(unsigned x, unsigned y) 25 | { 26 | return(x - std::min(x, y)); 27 | } 28 | 29 | //---------------------- Represent Simulation Results ---------------------------- 30 | template 31 | struct Results 32 | { 33 | result_type wins; 34 | result_type draws; 35 | result_type losses; 36 | result_type points; 37 | template 38 | Results& operator+=(const Results& other) 39 | { 40 | wins += other.wins; 41 | draws += other.draws; 42 | losses += other.losses; 43 | points += other.points; 44 | return *this; 45 | } 46 | }; 47 | 48 | typedef std::pair>, unsigned> EvaluatedResults; 49 | 50 | template 51 | struct FinalResults 52 | { 53 | result_type wins; 54 | result_type draws; 55 | result_type losses; 56 | result_type points; 57 | result_type points_lower_bound; 58 | result_type points_upper_bound; 59 | uint64_t n_sims; 60 | }; 61 | 62 | void fill_skill_table(); 63 | Results play(Field* fd); 64 | // Pool-based indexed storage. 65 | //---------------------- Pool-based indexed storage ---------------------------- 66 | template 67 | class Storage 68 | { 69 | public: 70 | typedef typename std::vector::size_type size_type; 71 | typedef T value_type; 72 | Storage(size_type size) : 73 | m_pool(sizeof(T)) 74 | { 75 | m_indirect.reserve(size); 76 | } 77 | 78 | inline T& operator[](size_type i) 79 | { 80 | return(*m_indirect[i]); 81 | } 82 | 83 | inline T& add_back() 84 | { 85 | m_indirect.emplace_back((T*) m_pool.malloc()); 86 | return(*m_indirect.back()); 87 | } 88 | 89 | template 90 | void remove(Pred p) 91 | { 92 | size_type head(0); 93 | for(size_type current(0); current < m_indirect.size(); ++current) 94 | { 95 | if(p((*this)[current])) 96 | { 97 | m_pool.free(m_indirect[current]); 98 | } 99 | else 100 | { 101 | if(current != head) 102 | { 103 | m_indirect[head] = m_indirect[current]; 104 | } 105 | ++head; 106 | } 107 | } 108 | m_indirect.erase(m_indirect.begin() + head, m_indirect.end()); 109 | } 110 | 111 | void reset() 112 | { 113 | for(auto index: m_indirect) 114 | { 115 | m_pool.free(index); 116 | } 117 | m_indirect.clear(); 118 | } 119 | 120 | inline size_type size() const 121 | { 122 | return(m_indirect.size()); 123 | } 124 | 125 | std::vector m_indirect; 126 | boost::pool<> m_pool; 127 | }; 128 | //------------------------------------------------------------------------------ 129 | enum class CardStep 130 | { 131 | none, 132 | attacking, 133 | attacked, 134 | }; 135 | //------------------------------------------------------------------------------ 136 | struct CardStatus 137 | { 138 | const Card* m_card; 139 | unsigned m_index; 140 | unsigned m_player; 141 | unsigned m_delay; 142 | Faction m_faction; 143 | unsigned m_attack; 144 | unsigned m_hp; 145 | unsigned m_max_hp; 146 | CardStep m_step; 147 | 148 | unsigned m_corroded_rate; 149 | unsigned m_corroded_weakened; 150 | unsigned m_enfeebled; 151 | unsigned m_evaded; 152 | unsigned m_inhibited; 153 | bool m_jammed; 154 | bool m_overloaded; 155 | unsigned m_paybacked; 156 | unsigned m_poisoned; 157 | unsigned m_protected; 158 | unsigned m_rallied; 159 | bool m_rush_attempted; 160 | bool m_sundered; 161 | unsigned m_weakened; 162 | 163 | signed m_primary_skill_offset[num_skills]; 164 | signed m_evolved_skill_offset[num_skills]; 165 | unsigned m_enhanced_value[num_skills]; 166 | unsigned m_skill_cd[num_skills]; 167 | 168 | CardStatus() {} 169 | 170 | void set(const Card* card); 171 | void set(const Card& card); 172 | std::string description() const; 173 | inline unsigned skill_base_value(Skill skill_id) const; 174 | unsigned skill(Skill skill_id) const; 175 | bool has_skill(Skill skill_id) const; 176 | unsigned enhanced(Skill skill) const; 177 | unsigned protected_value() const; 178 | }; 179 | //------------------------------------------------------------------------------ 180 | // Represents a particular draw from a deck. 181 | // Persistent object: call reset to get a new draw. 182 | class Hand 183 | { 184 | public: 185 | 186 | Hand(Deck* deck_) : 187 | deck(deck_), 188 | assaults(15), 189 | structures(15) 190 | { 191 | } 192 | 193 | void reset(std::mt19937& re); 194 | 195 | Deck* deck; 196 | CardStatus commander; 197 | Storage assaults; 198 | Storage structures; 199 | }; 200 | 201 | struct Quest 202 | { 203 | QuestType::QuestType quest_type; 204 | unsigned quest_key; 205 | unsigned quest_2nd_key; 206 | unsigned quest_value; 207 | unsigned quest_score; // score for quest goal 208 | unsigned win_score; // score for win regardless quest goal 209 | bool must_fulfill; // true: score iff value is reached; false: score proportion to achieved value 210 | bool must_win; // true: score only if win 211 | Quest() : 212 | quest_type(QuestType::none), 213 | quest_key(0), 214 | quest_value(0), 215 | quest_score(100), 216 | win_score(0), 217 | must_fulfill(false), 218 | must_win(false) 219 | {} 220 | }; 221 | 222 | //------------------------------------------------------------------------------ 223 | // struct Field is the data model of a battle: 224 | // an attacker and a defender deck, list of assaults and structures, etc. 225 | class Field 226 | { 227 | public: 228 | bool end; 229 | std::mt19937& re; 230 | const Cards& cards; 231 | // players[0]: the attacker, players[1]: the defender 232 | std::array players; 233 | unsigned tapi; // current turn's active player index 234 | unsigned tipi; // and inactive 235 | Hand* tap; 236 | Hand* tip; 237 | std::vector selection_array; 238 | unsigned turn; 239 | gamemode_t gamemode; 240 | OptimizationMode optimization_mode; 241 | const Quest quest; 242 | std::unordered_map bg_effects; // passive BGE 243 | std::vector bg_skills[2]; // active BGE, casted every turn 244 | // With the introduction of on death skills, a single skill can trigger arbitrary many skills. 245 | // They are stored in this, and cleared after all have been performed. 246 | std::deque> skill_queue; 247 | std::vector killed_units; 248 | enum phase 249 | { 250 | playcard_phase, 251 | legion_phase, 252 | commander_phase, 253 | structures_phase, 254 | assaults_phase, 255 | end_phase, 256 | }; 257 | // the current phase of the turn: starts with playcard_phase, then commander_phase, structures_phase, and assaults_phase 258 | phase current_phase; 259 | // the index of the card being evaluated in the current phase. 260 | // Meaningless in playcard_phase, 261 | // otherwise is the index of the current card in players->structures or players->assaults 262 | unsigned current_ci; 263 | 264 | bool assault_bloodlusted; 265 | unsigned bloodlust_value; 266 | unsigned quest_counter; 267 | 268 | Field(std::mt19937& re_, const Cards& cards_, Hand& hand1, Hand& hand2, gamemode_t gamemode_, OptimizationMode optimization_mode_, const Quest & quest_, 269 | std::unordered_map& bg_effects_, std::vector& your_bg_skills_, std::vector& enemy_bg_skills_) : 270 | end{false}, 271 | re(re_), 272 | cards(cards_), 273 | players{{&hand1, &hand2}}, 274 | turn(1), 275 | gamemode(gamemode_), 276 | optimization_mode(optimization_mode_), 277 | quest(quest_), 278 | bg_effects{bg_effects_}, 279 | bg_skills{your_bg_skills_, enemy_bg_skills_}, 280 | assault_bloodlusted(false), 281 | bloodlust_value(0), 282 | quest_counter(0) 283 | { 284 | } 285 | 286 | inline unsigned rand(unsigned x, unsigned y) 287 | { 288 | return(std::uniform_int_distribution(x, y)(re)); 289 | } 290 | 291 | inline unsigned flip() 292 | { 293 | return(this->rand(0,1)); 294 | } 295 | 296 | template 297 | inline T random_in_vector(const std::vector& v) 298 | { 299 | assert(v.size() > 0); 300 | return(v[this->rand(0, v.size() - 1)]); 301 | } 302 | 303 | template 304 | inline unsigned make_selection_array(CardsIter first, CardsIter last, Functor f); 305 | inline const std::vector adjacent_assaults(const CardStatus * status); 306 | inline void print_selection_array(); 307 | 308 | inline void inc_counter(QuestType::QuestType quest_type, unsigned quest_key, unsigned quest_2nd_key = 0, unsigned value = 1) 309 | { 310 | if (quest.quest_type == quest_type && quest.quest_key == quest_key && (quest.quest_2nd_key == 0 || quest.quest_2nd_key == quest_2nd_key)) 311 | { 312 | quest_counter += value; 313 | } 314 | } 315 | }; 316 | 317 | #endif 318 | -------------------------------------------------------------------------------- /tyrant.cpp: -------------------------------------------------------------------------------- 1 | #include "tyrant.h" 2 | 3 | #include 4 | 5 | const std::string faction_names[Faction::num_factions] = 6 | { "", "imperial", "raider", "bloodthirsty", "xeno", "righteous", "progenitor" }; 7 | 8 | std::string skill_names[Skill::num_skills] = 9 | { 10 | // Placeholder for no-skill: 11 | "", 12 | // Attack: 13 | "0", 14 | // Activation: 15 | "", "", 16 | "Enfeeble", "Jam", "Mortar", "Siege", "Strike", "Sunder", "Weaken", 17 | "", 18 | "", 19 | "Enhance", "Evolve", "Heal", "Mend", "Overload", "Protect", "Rally", "Rush", 20 | "", "", 21 | // Defensive: 22 | "", 23 | "Armor", "Avenge", "Corrosive", "Counter", "Evade", "Payback", "Refresh", "Wall", 24 | "", 25 | // Combat-Modifier: 26 | "Legion", "Pierce", "Rupture", "Swipe", "Venom", 27 | // Damage-Dependant: 28 | "Berserk", "Inhibit", "Leech", "Poison", 29 | // Triggered: 30 | "Allegiance", "Flurry", "Valor", 31 | // Pseudo-skill for passive BGEs: 32 | "", 33 | "Bloodlust", "Brigade", "Counterflux", "Divert", "EnduringRage", "Fortification", "Heroism", "Metamorphosis", "Revenge", "TurningTides", "Virulence", 34 | "", 35 | }; 36 | 37 | std::string cardtype_names[CardType::num_cardtypes]{"Commander", "Assault", "Structure", }; 38 | 39 | std::string rarity_names[6]{"", "common", "rare", "epic", "legend", "vindi", }; 40 | 41 | unsigned upgrade_cost[]{0, 5, 15, 30, 75, 150}; 42 | unsigned salvaging_income[][7]{{}, {0, 1, 2, 5}, {0, 5, 10, 15, 20}, {0, 20, 25, 30, 40, 50, 65}, {0, 40, 45, 60, 75, 100, 125}, {0, 80, 85, 100, 125, 175, 250}}; 43 | 44 | signed min_possible_score[]{0, 0, 0, 10, 5, 5, 0, 0}; 45 | signed max_possible_score[]{100, 100, 100, 100, 67, 100, 100, 100}; 46 | 47 | std::string decktype_names[DeckType::num_decktypes]{"Deck", "Mission", "Raid", "Campaign", "Custom Deck", }; 48 | 49 | signed debug_print(0); 50 | unsigned debug_cached(0); 51 | bool debug_line(false); 52 | std::string debug_str(""); 53 | -------------------------------------------------------------------------------- /tyrant.h: -------------------------------------------------------------------------------- 1 | #ifndef TYRANT_H_INCLUDED 2 | #define TYRANT_H_INCLUDED 3 | 4 | #define TYRANT_OPTIMIZER_VERSION "2.19.2" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | enum Faction 13 | { 14 | allfactions, 15 | imperial, 16 | raider, 17 | bloodthirsty, 18 | xeno, 19 | righteous, 20 | progenitor, 21 | num_factions 22 | }; 23 | extern const std::string faction_names[num_factions]; 24 | 25 | enum Skill 26 | { 27 | // Placeholder for no-skill: 28 | no_skill, 29 | // Attack: 30 | attack, 31 | // Activation: 32 | BEGIN_ACTIVATION, BEGIN_ACTIVATION_HARMFUL, // TODO skill traits 33 | enfeeble, jam, mortar, siege, strike, sunder, weaken, 34 | END_ACTIVATION_HARMFUL, 35 | BEGIN_ACTIVATION_HELPFUL, 36 | enhance, evolve, heal, mend, overload, protect, rally, rush, 37 | END_ACTIVATION_HELPFUL, END_ACTIVATION, 38 | // Defensive: 39 | BEGIN_DEFENSIVE, 40 | armor, avenge, corrosive, counter, evade, payback, refresh, wall, 41 | END_DEFENSIVE, 42 | // Combat-Modifier: 43 | legion, pierce, rupture, swipe, venom, 44 | // Damage-Dependent: 45 | berserk, inhibit, leech, poison, 46 | // Triggered: 47 | allegiance, flurry, valor, 48 | // Pseudo-Skill for BGE: 49 | BEGIN_BGE_SKILL, 50 | bloodlust, brigade, counterflux, divert, enduringrage, fortification, heroism, metamorphosis, revenge, turningtides, virulence, 51 | END_BGE_SKILL, 52 | num_skills 53 | }; 54 | extern std::string skill_names[num_skills]; 55 | 56 | namespace CardType { 57 | enum CardType { 58 | commander, 59 | assault, 60 | structure, 61 | num_cardtypes 62 | }; 63 | } 64 | 65 | extern std::string cardtype_names[CardType::num_cardtypes]; 66 | 67 | extern std::string rarity_names[]; 68 | 69 | extern unsigned upgrade_cost[]; 70 | extern unsigned salvaging_income[][7]; 71 | 72 | namespace DeckType { 73 | enum DeckType { 74 | deck, 75 | mission, 76 | raid, 77 | campaign, 78 | custom_deck, 79 | num_decktypes 80 | }; 81 | } 82 | 83 | extern std::string decktype_names[DeckType::num_decktypes]; 84 | 85 | enum gamemode_t 86 | { 87 | fight, 88 | surge, 89 | }; 90 | 91 | namespace QuestType 92 | { 93 | enum QuestType 94 | { 95 | none, 96 | skill_use, 97 | skill_damage, 98 | faction_assault_card_use, 99 | type_card_use, 100 | faction_assault_card_kill, 101 | type_card_kill, 102 | card_survival, 103 | num_objective_types 104 | }; 105 | } 106 | 107 | enum class OptimizationMode 108 | { 109 | notset, 110 | winrate, 111 | defense, 112 | war, 113 | brawl, 114 | raid, 115 | campaign, 116 | quest, 117 | num_optimization_mode 118 | }; 119 | 120 | extern signed min_possible_score[(size_t)OptimizationMode::num_optimization_mode]; 121 | extern signed max_possible_score[(size_t)OptimizationMode::num_optimization_mode]; 122 | 123 | struct true_ {}; 124 | 125 | struct false_ {}; 126 | 127 | template 128 | struct skillTriggersRegen { typedef false_ T; }; 129 | 130 | template<> 131 | struct skillTriggersRegen { typedef true_ T; }; 132 | 133 | template<> 134 | struct skillTriggersRegen { typedef true_ T; }; 135 | 136 | enum SkillSourceType 137 | { 138 | source_hostile, 139 | source_allied, 140 | source_global_hostile, 141 | source_global_allied, 142 | source_chaos 143 | }; 144 | 145 | struct SkillSpec 146 | { 147 | Skill id; 148 | unsigned x; 149 | Faction y; 150 | unsigned n; 151 | unsigned c; 152 | Skill s; 153 | Skill s2; 154 | bool all; 155 | }; 156 | 157 | // -------------------------------------------------------------------------------- 158 | // Common functions 159 | template 160 | std::string to_string(const T val) 161 | { 162 | std::stringstream s; 163 | s << val; 164 | return s.str(); 165 | } 166 | 167 | //---------------------- Debugging stuff --------------------------------------- 168 | extern signed debug_print; 169 | extern unsigned debug_cached; 170 | extern bool debug_line; 171 | extern std::string debug_str; 172 | #ifndef NDEBUG 173 | #define _DEBUG_MSG(v, format, args...) \ 174 | { \ 175 | if(__builtin_expect(debug_print >= v, false)) \ 176 | { \ 177 | if(debug_line) { printf("%i - " format, __LINE__ , ##args); } \ 178 | else if(debug_cached) { \ 179 | char buf[4096]; \ 180 | snprintf(buf, sizeof(buf), format, ##args); \ 181 | debug_str += buf; \ 182 | } \ 183 | else { printf(format, ##args); } \ 184 | std::cout << std::flush; \ 185 | } \ 186 | } 187 | #define _DEBUG_SELECTION(format, args...) \ 188 | { \ 189 | if(__builtin_expect(debug_print >= 2, 0)) \ 190 | { \ 191 | _DEBUG_MSG(2, "Possible targets of " format ":\n", ##args); \ 192 | fd->print_selection_array(); \ 193 | } \ 194 | } 195 | #else 196 | #define _DEBUG_MSG(v, format, args...) 197 | #define _DEBUG_SELECTION(format, args...) 198 | #endif 199 | 200 | #endif 201 | -------------------------------------------------------------------------------- /update_xml.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | for fn in fusion_recipes_cj2 missions skills_set `seq -f cards_section_%g 1 9` ; do 3 | curl http://mobile$1.tyrantonline.com/assets/${fn}.xml -R -z data/${fn}.xml -o data/${fn}.xml 4 | done 5 | -------------------------------------------------------------------------------- /xml.cpp: -------------------------------------------------------------------------------- 1 | #include "xml.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "rapidxml.hpp" 11 | #include "card.h" 12 | #include "cards.h" 13 | #include "deck.h" 14 | #include "tyrant.h" 15 | //---------------------- $20 cards.xml parsing --------------------------------- 16 | // Sets: 1 enclave; 2 nexus; 3 blight; 4 purity; 5 homeworld; 17 | // 6 phobos; 7 phobos aftermath; 8 awakening 18 | // 1000 standard; 5000 rewards; 5001 promotional; 9000 exclusive 19 | // mission only and test cards have no set 20 | using namespace rapidxml; 21 | 22 | Skill skill_name_to_id(const std::string & name) 23 | { 24 | static std::map skill_map; 25 | if(skill_map.empty()) 26 | { 27 | for(unsigned i(0); i < Skill::num_skills; ++i) 28 | { 29 | std::string skill_id = boost::to_lower_copy(skill_names[i]); 30 | skill_map[skill_id] = i; 31 | } 32 | skill_map["armored"] = skill_map["armor"]; // Special case for Armor: id and name differ 33 | skill_map["besiege"] = skill_map["mortar"]; // Special case for Mortar: id and name differ 34 | } 35 | auto x = skill_map.find(boost::to_lower_copy(name)); 36 | if (x == skill_map.end()) 37 | { 38 | return no_skill; 39 | } 40 | else 41 | { 42 | return (Skill)x->second; 43 | } 44 | } 45 | 46 | Faction skill_faction(xml_node<>* skill) 47 | { 48 | xml_attribute<>* y(skill->first_attribute("y")); 49 | if (y) 50 | { 51 | return static_cast(atoi(y->value())); 52 | } 53 | return allfactions; 54 | } 55 | 56 | unsigned node_value(xml_node<>* skill, const char* attribute, unsigned default_value = 0) 57 | { 58 | xml_attribute<>* value_node(skill->first_attribute(attribute)); 59 | return value_node ? atoi(value_node->value()) : default_value; 60 | } 61 | 62 | Skill skill_target_skill(xml_node<>* skill, const char* attribute) 63 | { 64 | Skill s(no_skill); 65 | xml_attribute<>* x(skill->first_attribute(attribute)); 66 | if(x) 67 | { 68 | s = skill_name_to_id(x->value()); 69 | } 70 | return(s); 71 | } 72 | 73 | //------------------------------------------------------------------------------ 74 | 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) 75 | { 76 | try 77 | { 78 | read_missions(decks, all_cards, mission_filename, do_warn_on_missing); 79 | } 80 | catch (const rapidxml::parse_error& e) 81 | { 82 | std::cerr << "\nFailed to parse file [" << mission_filename << "]. Skip it.\n"; 83 | } 84 | try 85 | { 86 | read_raids(decks, all_cards, raid_filename, do_warn_on_missing); 87 | } 88 | catch(const rapidxml::parse_error& e) 89 | { 90 | std::cerr << "\nFailed to parse file [" << raid_filename << "]. Skip it.\n"; 91 | } 92 | } 93 | 94 | //------------------------------------------------------------------------------ 95 | void parse_file(const std::string & filename, std::vector& buffer, xml_document<>& doc, bool do_warn_on_missing=true) 96 | { 97 | std::ifstream cards_stream(filename, std::ios::binary); 98 | if (!cards_stream.good()) 99 | { 100 | if (do_warn_on_missing) 101 | { 102 | std::cerr << "Warning: The file '" << filename << "' does not exist. Proceeding without reading from this file.\n"; 103 | } 104 | buffer.resize(1); 105 | buffer[0] = 0; 106 | doc.parse<0>(&buffer[0]); 107 | return; 108 | } 109 | // Get the size of the file 110 | cards_stream.seekg(0,std::ios::end); 111 | std::streampos length = cards_stream.tellg(); 112 | cards_stream.seekg(0,std::ios::beg); 113 | buffer.resize(length + std::streampos(1)); 114 | cards_stream.read(&buffer[0],length); 115 | // zero-terminate 116 | buffer[length] = '\0'; 117 | try 118 | { 119 | doc.parse<0>(&buffer[0]); 120 | } 121 | catch(rapidxml::parse_error& e) 122 | { 123 | std::cerr << "Parse error exception.\n"; 124 | std::cout << e.what(); 125 | throw(e); 126 | } 127 | } 128 | //------------------------------------------------------------------------------ 129 | void parse_card_node(Cards& all_cards, Card* card, xml_node<>* card_node) 130 | { 131 | xml_node<>* id_node(card_node->first_node("id")); 132 | xml_node<>* card_id_node = card_node->first_node("card_id"); 133 | assert(id_node || card_id_node); 134 | xml_node<>* name_node(card_node->first_node("name")); 135 | xml_node<>* attack_node(card_node->first_node("attack")); 136 | xml_node<>* health_node(card_node->first_node("health")); 137 | xml_node<>* cost_node(card_node->first_node("cost")); 138 | xml_node<>* rarity_node(card_node->first_node("rarity")); 139 | xml_node<>* type_node(card_node->first_node("type")); 140 | xml_node<>* set_node(card_node->first_node("set")); 141 | int set(set_node ? atoi(set_node->value()) : card->m_set); 142 | xml_node<>* level_node(card_node->first_node("level")); 143 | xml_node<>* fusion_level_node(card_node->first_node("fusion_level")); 144 | if (id_node) { card->m_base_id = card->m_id = atoi(id_node->value()); } 145 | else if (card_id_node) { card->m_id = atoi(card_id_node->value()); } 146 | if (name_node) { card->m_name = name_node->value(); } 147 | if (level_node) { card->m_level = atoi(level_node->value()); } 148 | if (fusion_level_node) { card->m_fusion_level = atoi(fusion_level_node->value()); } 149 | if (attack_node) { card->m_attack = atoi(attack_node->value()); } 150 | if (health_node) { card->m_health = atoi(health_node->value()); } 151 | if (cost_node) { card->m_delay = atoi(cost_node->value()); } 152 | if (id_node) 153 | { 154 | if (card->m_id < 1000) 155 | { card->m_type = CardType::assault; } 156 | else if (card->m_id < 2000) 157 | { card->m_type = CardType::commander; } 158 | else if (card->m_id < 3000) 159 | { card->m_type = CardType::structure; } 160 | else if (card->m_id < 8000) 161 | { card->m_type = CardType::assault; } 162 | else if (card->m_id < 10000) 163 | { card->m_type = CardType::structure; } 164 | else if (card->m_id < 17000) 165 | { card->m_type = CardType::assault; } 166 | else if (card->m_id < 25000) 167 | { card->m_type = CardType::structure; } 168 | else if (card->m_id < 30000) 169 | { card->m_type = CardType::commander; } 170 | else 171 | { card->m_type = CardType::assault; } 172 | } 173 | if(rarity_node) { card->m_rarity = atoi(rarity_node->value()); } 174 | if(type_node) { card->m_faction = static_cast(atoi(type_node->value())); } 175 | card->m_set = set; 176 | 177 | if (card_node->first_node("skill")) 178 | { // inherit no skill if there is skill node 179 | card->m_skills.clear(); 180 | memset(card->m_skill_value, 0, sizeof card->m_skill_value); 181 | } 182 | for(xml_node<>* skill_node = card_node->first_node("skill"); 183 | skill_node; 184 | skill_node = skill_node->next_sibling("skill")) 185 | { 186 | Skill skill_id = skill_name_to_id(skill_node->first_attribute("id")->value()); 187 | if(skill_id == no_skill) { continue; } 188 | auto x = node_value(skill_node, "x", 0); 189 | auto y = skill_faction(skill_node); 190 | auto n = node_value(skill_node, "n", 0); 191 | auto c = node_value(skill_node, "c", 0); 192 | auto s = skill_target_skill(skill_node, "s"); 193 | auto s2 = skill_target_skill(skill_node, "s2"); 194 | bool all(skill_node->first_attribute("all")); 195 | card->add_skill(skill_id, x, y, n, c, s, s2, all); 196 | } 197 | all_cards.all_cards.push_back(card); 198 | Card * top_card = card; 199 | for(xml_node<>* upgrade_node = card_node->first_node("upgrade"); 200 | upgrade_node; 201 | upgrade_node = upgrade_node->next_sibling("upgrade")) 202 | { 203 | Card * pre_upgraded_card = top_card; 204 | top_card = new Card(*top_card); 205 | parse_card_node(all_cards, top_card, upgrade_node); 206 | if (top_card->m_type == CardType::commander) 207 | { 208 | // Commanders cost twice and cannot be salvaged. 209 | top_card->m_recipe_cost = 2 * upgrade_cost[pre_upgraded_card->m_level]; 210 | } 211 | else 212 | { 213 | // Salvaging income counts? 214 | top_card->m_recipe_cost = upgrade_cost[pre_upgraded_card->m_level]; // + salvaging_income[top_card->m_rarity][pre_upgraded_card->m_level] - salvaging_income[top_card->m_rarity][top_card->m_level]; 215 | } 216 | top_card->m_recipe_cards.clear(); 217 | top_card->m_recipe_cards[pre_upgraded_card] = 1; 218 | pre_upgraded_card->m_used_for_cards[top_card] = 1; 219 | } 220 | card->m_top_level_card = top_card; 221 | } 222 | 223 | void load_cards_xml(Cards & all_cards, const std::string & filename, bool do_warn_on_missing) 224 | { 225 | std::vector buffer; 226 | xml_document<> doc; 227 | parse_file(filename, buffer, doc, do_warn_on_missing); 228 | xml_node<>* root = doc.first_node(); 229 | 230 | if(!root) 231 | { 232 | return; 233 | } 234 | for (xml_node<>* card_node = root->first_node("unit"); 235 | card_node; 236 | card_node = card_node->next_sibling("unit")) 237 | { 238 | auto card = new Card(); 239 | parse_card_node(all_cards, card, card_node); 240 | } 241 | } 242 | 243 | void load_skills_set_xml(Cards & all_cards, const std::string & filename, bool do_warn_on_missing) 244 | { 245 | std::vector buffer; 246 | xml_document<> doc; 247 | parse_file(filename, buffer, doc, do_warn_on_missing); 248 | xml_node<>* root = doc.first_node(); 249 | 250 | if(!root) 251 | { 252 | return; 253 | } 254 | for (xml_node<>* set_node = root->first_node("cardSet"); 255 | set_node; 256 | set_node = set_node->next_sibling("cardSet")) 257 | { 258 | xml_node<>* id_node(set_node->first_node("id")); 259 | xml_node<>* visible_node = set_node->first_node("visible"); 260 | if (id_node && visible_node && atoi(visible_node->value())) 261 | { 262 | all_cards.visible_cardset.insert(atoi(id_node->value())); 263 | } 264 | } 265 | } 266 | //------------------------------------------------------------------------------ 267 | Deck* read_deck(Decks& decks, const Cards& all_cards, xml_node<>* node, DeckType::DeckType decktype, unsigned id, std::string base_deck_name) 268 | { 269 | xml_node<>* commander_node(node->first_node("commander")); 270 | const Card* card = all_cards.by_id(atoi(commander_node->value())); 271 | const Card* commander_card{card}; 272 | xml_node<>* commander_max_level_node(node->first_node("commander_max_level")); 273 | unsigned commander_max_level = commander_max_level_node ? atoi(commander_max_level_node->value()) : commander_card->m_top_level_card->m_level; 274 | unsigned upgrade_opportunities = commander_max_level - card->m_level; 275 | std::vector fort_cards; 276 | for (xml_node<>* fortress_card_node = node->first_node("fortress_card"); 277 | fortress_card_node; 278 | fortress_card_node = fortress_card_node->next_sibling("fortress_card")) 279 | { 280 | const Card * card = all_cards.by_id(atoi(fortress_card_node->first_attribute("id")->value())); 281 | fort_cards.push_back(card); 282 | upgrade_opportunities += card->m_top_level_card->m_level - card->m_level; 283 | } 284 | std::vector always_cards; 285 | std::vector>> some_cards; 286 | xml_node<>* deck_node(node->first_node("deck")); 287 | xml_node<>* levels_node(node->first_node("levels")); 288 | unsigned max_level = levels_node ? atoi(levels_node->value()) : 10; 289 | xml_node<>* always_node{deck_node->first_node("always_include")}; 290 | for(xml_node<>* card_node = (always_node ? always_node : deck_node)->first_node("card"); 291 | card_node; 292 | card_node = card_node->next_sibling("card")) 293 | { 294 | card = all_cards.by_id(atoi(card_node->value())); 295 | always_cards.push_back(card); 296 | upgrade_opportunities += card->m_top_level_card->m_level - card->m_level; 297 | } 298 | for(xml_node<>* pool_node = deck_node->first_node("card_pool"); 299 | pool_node; 300 | pool_node = pool_node->next_sibling("card_pool")) 301 | { 302 | unsigned num_cards_from_pool(atoi(pool_node->first_attribute("amount")->value())); 303 | unsigned replicates(pool_node->first_attribute("replicates") ? atoi(pool_node->first_attribute("replicates")->value()) : 1); 304 | std::vector cards_from_pool; 305 | unsigned upgrade_points = 0; 306 | for(xml_node<>* card_node = pool_node->first_node("card"); 307 | card_node; 308 | card_node = card_node->next_sibling("card")) 309 | { 310 | card = all_cards.by_id(atoi(card_node->value())); 311 | cards_from_pool.push_back(card); 312 | upgrade_points += card->m_top_level_card->m_level - card->m_level; 313 | } 314 | some_cards.push_back(std::make_tuple(num_cards_from_pool, replicates, cards_from_pool)); 315 | upgrade_opportunities += upgrade_points * num_cards_from_pool * replicates / cards_from_pool.size(); 316 | } 317 | xml_node<>* mission_req_node(node->first_node(decktype == DeckType::mission ? "req" : "mission_req")); 318 | unsigned mission_req(mission_req_node ? atoi(mission_req_node->value()) : 0); 319 | 320 | for (unsigned level = 1; level < max_level; ++ level) 321 | { 322 | std::string deck_name = base_deck_name + "-" + to_string(level); 323 | decks.decks.push_back(Deck{all_cards, decktype, id, deck_name, (upgrade_opportunities + 1) * (level - 1) / (max_level - 1), upgrade_opportunities}); 324 | Deck* deck = &decks.decks.back(); 325 | deck->set(commander_card, commander_max_level, always_cards, some_cards, mission_req); 326 | deck->fort_cards = fort_cards; 327 | decks.add_deck(deck, deck_name); 328 | decks.add_deck(deck, decktype_names[decktype] + " #" + to_string(id) + "-" + to_string(level)); 329 | } 330 | 331 | decks.decks.push_back(Deck{all_cards, decktype, id, base_deck_name}); 332 | Deck* deck = &decks.decks.back(); 333 | deck->set(commander_card, commander_max_level, always_cards, some_cards, mission_req); 334 | deck->fort_cards = fort_cards; 335 | 336 | // upgrade cards for full-level missions/raids 337 | if (max_level > 1) 338 | { 339 | while (deck->commander->m_level < commander_max_level) 340 | { deck->commander = deck->commander->upgraded(); } 341 | for (auto && card: deck->fort_cards) 342 | { card = card->m_top_level_card; } 343 | for (auto && card: deck->cards) 344 | { card = card->m_top_level_card; } 345 | for (auto && pool: deck->variable_cards) 346 | { 347 | for (auto && card: std::get<2>(pool)) 348 | { card = card->m_top_level_card; } 349 | } 350 | } 351 | 352 | decks.add_deck(deck, base_deck_name); 353 | decks.add_deck(deck, base_deck_name + "-" + to_string(max_level)); 354 | decks.add_deck(deck, decktype_names[decktype] + " #" + to_string(id)); 355 | decks.add_deck(deck, decktype_names[decktype] + " #" + to_string(id) + "-" + to_string(max_level)); 356 | decks.by_type_id[{decktype, id}] = deck; 357 | return deck; 358 | } 359 | //------------------------------------------------------------------------------ 360 | void read_missions(Decks& decks, const Cards& all_cards, const std::string & filename, bool do_warn_on_missing=true) 361 | { 362 | std::vector buffer; 363 | xml_document<> doc; 364 | parse_file(filename.c_str(), buffer, doc, do_warn_on_missing); 365 | xml_node<>* root = doc.first_node(); 366 | 367 | if(!root) 368 | { 369 | return; 370 | } 371 | 372 | for(xml_node<>* mission_node = root->first_node("mission"); 373 | mission_node; 374 | mission_node = mission_node->next_sibling("mission")) 375 | { 376 | std::vector card_ids; 377 | xml_node<>* id_node(mission_node->first_node("id")); 378 | assert(id_node); 379 | unsigned id(id_node ? atoi(id_node->value()) : 0); 380 | xml_node<>* name_node(mission_node->first_node("name")); 381 | std::string deck_name{name_node->value()}; 382 | try 383 | { 384 | read_deck(decks, all_cards, mission_node, DeckType::mission, id, deck_name); 385 | } 386 | catch (const std::runtime_error& e) 387 | { 388 | std::cerr << "Warning: Failed to parse mission [" << deck_name << "] in file " << filename << ": [" << e.what() << "]. Skip the mission.\n"; 389 | continue; 390 | } 391 | } 392 | } 393 | //------------------------------------------------------------------------------ 394 | void read_raids(Decks& decks, const Cards& all_cards, const std::string & filename, bool do_warn_on_missing=true) 395 | { 396 | std::vector buffer; 397 | xml_document<> doc; 398 | parse_file(filename.c_str(), buffer, doc, do_warn_on_missing); 399 | xml_node<>* root = doc.first_node(); 400 | 401 | if(!root) 402 | { 403 | return; 404 | } 405 | 406 | for(xml_node<>* raid_node = root->first_node("raid"); 407 | raid_node; 408 | raid_node = raid_node->next_sibling("raid")) 409 | { 410 | xml_node<>* id_node(raid_node->first_node("id")); 411 | assert(id_node); 412 | unsigned id(id_node ? atoi(id_node->value()) : 0); 413 | xml_node<>* name_node(raid_node->first_node("name")); 414 | std::string deck_name{name_node->value()}; 415 | try 416 | { 417 | read_deck(decks, all_cards, raid_node, DeckType::raid, id, deck_name); 418 | } 419 | catch (const std::runtime_error& e) 420 | { 421 | std::cerr << "Warning: Failed to parse raid [" << deck_name << "] in file " << filename << ": [" << e.what() << "]. Skip the raid.\n"; 422 | continue; 423 | } 424 | } 425 | 426 | for(xml_node<>* campaign_node = root->first_node("campaign"); 427 | campaign_node; 428 | campaign_node = campaign_node->next_sibling("campaign")) 429 | { 430 | xml_node<>* id_node(campaign_node->first_node("id")); 431 | assert(id_node); 432 | unsigned id(id_node ? atoi(id_node->value()) : 0); 433 | for (auto && name_node = campaign_node->first_node("name"); 434 | name_node; 435 | name_node = name_node->next_sibling("name")) 436 | { 437 | try 438 | { 439 | read_deck(decks, all_cards, campaign_node, DeckType::campaign, id, name_node->value()); 440 | } 441 | catch (const std::runtime_error& e) 442 | { 443 | std::cerr << "Warning: Failed to parse campaign [" << name_node->value() << "] in file " << filename << ": [" << e.what() << "]. Skip the campaign.\n"; 444 | continue; 445 | } 446 | } 447 | } 448 | } 449 | 450 | //------------------------------------------------------------------------------ 451 | void load_recipes_xml(Cards& all_cards, const std::string & filename, bool do_warn_on_missing=true) 452 | { 453 | std::vector buffer; 454 | xml_document<> doc; 455 | parse_file(filename, buffer, doc, do_warn_on_missing); 456 | xml_node<>* root = doc.first_node(); 457 | 458 | if(!root) 459 | { 460 | return; 461 | } 462 | 463 | for(xml_node<>* recipe_node = root->first_node("fusion_recipe"); 464 | recipe_node; 465 | recipe_node = recipe_node->next_sibling("fusion_recipe")) 466 | { 467 | xml_node<>* card_id_node(recipe_node->first_node("card_id")); 468 | if (!card_id_node) { continue; } 469 | unsigned card_id(atoi(card_id_node->value())); 470 | Card * card = all_cards.cards_by_id[card_id]; 471 | if (!card) { 472 | std::cerr << "Could not find card by id " << card_id << std::endl; 473 | continue; 474 | } 475 | 476 | for(xml_node<>* resource_node = recipe_node->first_node("resource"); 477 | resource_node; 478 | resource_node = resource_node->next_sibling("resource")) 479 | { 480 | unsigned card_id(node_value(resource_node, "card_id")); 481 | unsigned number(node_value(resource_node, "number")); 482 | if (card_id == 0 || number == 0) { continue; } 483 | Card * material_card = all_cards.cards_by_id[card_id]; 484 | card->m_recipe_cards[material_card] += number; 485 | material_card->m_used_for_cards[card] += number; 486 | } 487 | } 488 | } 489 | 490 | -------------------------------------------------------------------------------- /xml.h: -------------------------------------------------------------------------------- 1 | #ifndef XML_H_INCLUDED 2 | #define XML_H_INCLUDED 3 | 4 | #include 5 | #include "tyrant.h" 6 | 7 | class Cards; 8 | class Decks; 9 | class Achievement; 10 | 11 | Skill skill_name_to_id(const std::string & name); 12 | void load_cards_xml(Cards & all_cards, const std::string & filename, bool do_warn_on_missing); 13 | void load_skills_set_xml(Cards & all_cards, const std::string & filename, bool do_warn_on_missing); 14 | 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); 15 | void load_recipes_xml(Cards& all_cards, const std::string & filename, bool do_warn_on_missing); 16 | void read_missions(Decks& decks, const Cards& all_cards, const std::string & filename, bool do_warn_on_missing); 17 | void read_raids(Decks& decks, const Cards& all_cards, const std::string & filename, bool do_warn_on_missing); 18 | 19 | #endif 20 | --------------------------------------------------------------------------------