1659 lines
49 KiB
C++
1659 lines
49 KiB
C++
#include "PrecompiledHeader.h"
|
|
|
|
#include "MTGDeck.h"
|
|
#include "utils.h"
|
|
#include "Subtypes.h"
|
|
#include "Translate.h"
|
|
#include "DeckMetaData.h"
|
|
#include "PriceList.h"
|
|
#include "WDataSrc.h"
|
|
#include "MTGPack.h"
|
|
#include "utils.h"
|
|
#include "DeckManager.h"
|
|
#include <iomanip>
|
|
#include "AbilityParser.h"
|
|
|
|
#if defined (WIN32) || defined (LINUX)
|
|
#include <time.h>
|
|
#endif
|
|
|
|
static inline int getGrade(int v)
|
|
{
|
|
switch (v)
|
|
{
|
|
case 'P':
|
|
case 'p':
|
|
return Constants::GRADE_SUPPORTED;
|
|
case 'R':
|
|
case 'r':
|
|
return Constants::GRADE_BORDERLINE;
|
|
case 'O':
|
|
case 'o':
|
|
return Constants::GRADE_UNOFFICIAL;
|
|
case 'A':
|
|
case 'a':
|
|
return Constants::GRADE_CRAPPY;
|
|
case 'S':
|
|
case 's':
|
|
return Constants::GRADE_UNSUPPORTED;
|
|
case 'N':
|
|
case 'n':
|
|
return Constants::GRADE_DANGEROUS;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//MTGAllCards
|
|
int MTGAllCards::processConfLine(string &s, MTGCard *card, CardPrimitive * primitive)
|
|
{
|
|
if ('#' == s[0]) return 1; // a comment shouldn't be treated as an error condition
|
|
size_t del_pos = s.find_first_of('=');
|
|
if (del_pos == string::npos || 0 == del_pos)
|
|
return 0;
|
|
|
|
s[del_pos] = '\0';
|
|
const string key = s.substr(0, del_pos);
|
|
const string val = s.substr(del_pos + 1);
|
|
|
|
switch (key[0])
|
|
{
|
|
case 'a':
|
|
if (key == "aicode")//replacement code for AI. for reveal:number basic version only
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
primitive->setAICustomCode(val);
|
|
}
|
|
else if (key == "auto")
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
primitive->addMagicText(val);
|
|
}
|
|
else if (StartsWith(key, "auto"))
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
primitive->addMagicText(val, key.substr(4));
|
|
}
|
|
else if (key == "alias")
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
primitive->alias = atoi(val.c_str());
|
|
}
|
|
else if (key == "abilities")
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
string value = val;
|
|
//Specific Abilities
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
vector<string> values = split(value, ',');
|
|
for (size_t values_i = 0; values_i < values.size(); ++values_i)
|
|
{
|
|
|
|
for (int j = Constants::NB_BASIC_ABILITIES - 1; j >= 0; --j)
|
|
{
|
|
if (values[values_i].find(Constants::MTGBasicAbilities[j]) != string::npos)
|
|
{
|
|
primitive->basicAbilities[j] = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (key == "anyzone")
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
primitive->addMagicText(val,"hand");
|
|
primitive->addMagicText(val,"library");
|
|
primitive->addMagicText(val,"graveyard");
|
|
primitive->addMagicText(val,"stack");
|
|
primitive->addMagicText(val,"exile");
|
|
primitive->addMagicText(val,"commandzone");
|
|
primitive->addMagicText(val,"reveal");
|
|
primitive->addMagicText(val,"sideboard");
|
|
primitive->addMagicText(val);
|
|
}
|
|
break;
|
|
|
|
case 'b': //buyback/Bestow
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
if (key[1] == 'e' && key[2] == 's')
|
|
{ //bestow
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
if (ManaCost * cost = primitive->getManaCost())
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
cost->setBestow(ManaCost::parseManaCost(value));
|
|
}
|
|
}
|
|
else//buyback
|
|
if (ManaCost * cost = primitive->getManaCost())
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
cost->setBuyback(ManaCost::parseManaCost(value));
|
|
}
|
|
break;
|
|
|
|
case 'c': //crew ability
|
|
if (key == "crewbonus")
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
{
|
|
primitive->setCrewAbility(val);
|
|
break;
|
|
}
|
|
}
|
|
else if (!primitive) primitive = NEW CardPrimitive();
|
|
{//color
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
vector<string> values = split(value, ',');
|
|
int removeAllOthers = 1;
|
|
for (size_t values_i = 0; values_i < values.size(); ++values_i)
|
|
{
|
|
primitive->setColor(values[values_i], removeAllOthers);
|
|
removeAllOthers = 0;
|
|
}
|
|
break;
|
|
}
|
|
case 'd'://double faced card /dredge
|
|
if (key == "doublefaced")
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
{
|
|
primitive->setdoubleFaced(val);
|
|
break;
|
|
}
|
|
}
|
|
else if (!primitive) primitive = NEW CardPrimitive();
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
vector<string> values = parseBetween(value,"dredge(",")");
|
|
if(values.size())
|
|
primitive->dredgeAmount = atoi(values[1].c_str());
|
|
|
|
break;
|
|
}
|
|
case 'f': //flashback//morph
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
if(ManaCost * cost = primitive->getManaCost())
|
|
{
|
|
if( s.find("facedown") != string::npos)//morph
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
cost->setMorph(ManaCost::parseManaCost(value));
|
|
}
|
|
else
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
cost->setFlashback(ManaCost::parseManaCost(value));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'g': //grade
|
|
if (s.size() - del_pos - 1 > 2) currentGrade = getGrade(val[2]);
|
|
break;
|
|
|
|
case 'i': //id
|
|
if (!card) card = NEW MTGCard();
|
|
card->setMTGId(atoi(val.c_str()));
|
|
break;
|
|
|
|
case 'k': //kicker
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
if (ManaCost * cost = primitive->getManaCost())
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
size_t multikick = value.find("multi");
|
|
bool isMultikicker = false;
|
|
if(multikick != string::npos)
|
|
{
|
|
size_t endK = value.find("{",multikick);
|
|
value.erase(multikick, endK - multikick);
|
|
isMultikicker = true;
|
|
}
|
|
cost->setKicker(ManaCost::parseManaCost(value));
|
|
cost->getKicker()->isMulti = isMultikicker;
|
|
}
|
|
break;
|
|
|
|
case 'm': //mana
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
{
|
|
if( key == "modular")//modular
|
|
{
|
|
primitive->setModularValue(val);
|
|
}
|
|
else
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
primitive->setManaCost(value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'n': //name
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
primitive->setName(val);
|
|
break;
|
|
|
|
case 'o': //othercost/otherrestriction
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
if(key[5] == 'r')//otherrestrictions
|
|
{
|
|
string value = val;
|
|
primitive->setOtherRestrictions(value);
|
|
}
|
|
else
|
|
{
|
|
if (ManaCost * cost = primitive->getManaCost())
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
size_t name = value.find("name(");
|
|
string theName = "";
|
|
if(name != string::npos)
|
|
{
|
|
size_t endName = value.find(")",name);
|
|
theName = value.substr(name + 5,endName - name - 5);
|
|
value.erase(name, endName - name + 1);
|
|
}
|
|
cost->setAlternative(ManaCost::parseManaCost(value));
|
|
if(theName.size())
|
|
cost->getAlternative()->alternativeName.append(theName);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'p':
|
|
if (key == "phasedoutbonus")
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
{
|
|
primitive->setPhasedOutAbility(val);
|
|
break;
|
|
}
|
|
}
|
|
else if (key[1] == 'r')
|
|
{ // primitive
|
|
if (!card) card = NEW MTGCard();
|
|
map<string, CardPrimitive*>::iterator it = primitives.find(val);
|
|
if (it != primitives.end()) card->setPrimitive(it->second);
|
|
}
|
|
else
|
|
{ //power
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
primitive->setPower(atoi(val.c_str()));
|
|
}
|
|
break;
|
|
|
|
case 'r': //retrace/rarity//restrictions
|
|
if(key[2] == 's' && key[3] == 't')//restrictions
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
string value = val;
|
|
primitive->setRestrictions(value);
|
|
}
|
|
else if (key[1] == 'e' && key[2] == 't')
|
|
{ //retrace
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
if (ManaCost * cost = primitive->getManaCost())
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
cost->setRetrace(ManaCost::parseManaCost(value));
|
|
}
|
|
}
|
|
else if (s.find("rar") != string::npos)
|
|
{//rarity
|
|
if (!card) card = NEW MTGCard();
|
|
card->setRarity(val[0]);
|
|
}
|
|
break;
|
|
|
|
case 's': //subtype, suspend
|
|
{
|
|
if (s.find("suspend") != string::npos)
|
|
{
|
|
size_t time = s.find("suspend(");
|
|
size_t end = s.find(")=");
|
|
int suspendTime = atoi(s.substr(time + 8,end - 2).c_str());
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
if (ManaCost * cost = primitive->getManaCost())
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
cost->setSuspend(ManaCost::parseManaCost(value));
|
|
primitive->suspendedTime = suspendTime;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
vector<string> values = split(val.c_str(), ' ');
|
|
for (size_t values_i = 0; values_i < values.size(); ++values_i)
|
|
primitive->setSubtype(values[values_i]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 't':
|
|
if (!primitive) primitive = NEW CardPrimitive();
|
|
if (key == "target")
|
|
{
|
|
string value = val;
|
|
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
|
|
primitive->spellTargetType = value;
|
|
}
|
|
else if (key == "text")
|
|
primitive->setText(val);
|
|
else if (key == "type")
|
|
{
|
|
vector<string> values = split(val, ' ');
|
|
for (size_t values_i = 0; values_i < values.size(); ++values_i)
|
|
primitive->setType(values[values_i]);
|
|
}
|
|
else if (key == "toughness") primitive->setToughness(atoi(val.c_str()));
|
|
break;
|
|
|
|
default:
|
|
if(primitive) {
|
|
DebugTrace( endl << "MTGDECK Parsing Error: " << " [" << primitive->getName() << "]" << s << std::endl);
|
|
} else {
|
|
DebugTrace( endl << "MTGDECK Parsing Generic Error: " << s << std::endl);
|
|
}
|
|
break;
|
|
}
|
|
|
|
tempPrimitive = primitive;
|
|
tempCard = card;
|
|
|
|
return del_pos;
|
|
|
|
}
|
|
|
|
void MTGAllCards::initCounters()
|
|
{
|
|
colorsCount.erase(colorsCount.begin(),colorsCount.end());
|
|
for (int i = 0; i < Constants::NB_Colors; i++)
|
|
colorsCount.push_back(0);
|
|
}
|
|
|
|
void MTGAllCards::init()
|
|
{
|
|
tempCard = NULL;
|
|
tempPrimitive = NULL;
|
|
total_cards = 0;
|
|
initCounters();
|
|
}
|
|
|
|
void MTGAllCards::loadFolder(const string& infolder, const string& filename )
|
|
{
|
|
string folder = infolder;
|
|
|
|
// Make sure the base paths finish with a '/' or a '\'
|
|
if (! folder.empty()) {
|
|
string::iterator c = folder.end();//userPath.at(userPath.size()-1);
|
|
c--;
|
|
if ((*c != '/') && (*c != '\\'))
|
|
folder += '/';
|
|
}
|
|
|
|
vector<string> files = JFileSystem::GetInstance()->scanfolder(folder);
|
|
|
|
if (!files.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < files.size(); ++i)
|
|
{
|
|
string afile = folder;
|
|
afile.append(files[i]);
|
|
|
|
if(files[i] == "." || files[i] == "..")
|
|
continue;
|
|
|
|
if(JFileSystem::GetInstance()->DirExists(afile))
|
|
loadFolder(afile, filename);
|
|
|
|
if (!JFileSystem::GetInstance()->FileExists(afile))
|
|
continue;
|
|
|
|
if(filename.size())
|
|
{
|
|
if(filename == files[i])
|
|
{
|
|
load(afile.c_str(), folder.c_str());
|
|
}
|
|
} else {
|
|
load(afile.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
int MTGAllCards::load(const string &config_file)
|
|
{
|
|
return load(config_file, MTGSets::INTERNAL_SET);
|
|
}
|
|
|
|
int MTGAllCards::load(const string& config_file, const string &set_name)
|
|
{
|
|
const int set_id = setlist.Add(set_name);
|
|
return load(config_file, set_id);
|
|
}
|
|
|
|
int MTGAllCards::load(const string &config_file, int set_id)
|
|
{
|
|
conf_read_mode = 0;
|
|
MTGSetInfo *si = setlist.getInfo(set_id);
|
|
|
|
int lineNumber = 0;
|
|
std::string contents;
|
|
izfstream file;
|
|
if (!JFileSystem::GetInstance()->openForRead(file, config_file))
|
|
{
|
|
DebugTrace("MTGAllCards::load: error loading: " << config_file);
|
|
return total_cards;
|
|
}
|
|
|
|
string s;
|
|
|
|
while (getline(file,s))
|
|
{
|
|
lineNumber++;
|
|
if (!s.size()) continue;
|
|
if (s[s.size() - 1] == '\r')
|
|
s.erase(s.size() - 1); //Handle DOS files
|
|
if (!s.size()) continue;
|
|
|
|
if (s.find("#AUTO_DEFINE ") == 0)
|
|
{
|
|
string toAdd = s.substr(13);
|
|
AutoLineMacro::AddMacro(toAdd);
|
|
continue;
|
|
}
|
|
|
|
switch (conf_read_mode)
|
|
{
|
|
case MTGAllCards::READ_ANYTHING:
|
|
if (s[0] == '[')
|
|
{
|
|
currentGrade = Constants::GRADE_SUPPORTED; // Default value
|
|
if (s.size() < 2)
|
|
{
|
|
DebugTrace("FATAL: Card file incorrect");
|
|
}
|
|
else
|
|
{
|
|
conf_read_mode = ('m' == s[1]) ? MTGAllCards::READ_METADATA : MTGAllCards::READ_CARD; // M for metadata.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Global grade for file, to avoid reading the entire file if unnnecessary
|
|
if (s[0] == 'g' && s.size() > 8)
|
|
{
|
|
int fileGrade = getGrade(s[8]);
|
|
int maxGrade = options[Options::MAX_GRADE].number;
|
|
if (!maxGrade) maxGrade = Constants::GRADE_BORDERLINE; //Default setting for grade is borderline?
|
|
if (fileGrade > maxGrade)
|
|
{
|
|
file.close();
|
|
return total_cards;
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
case MTGAllCards::READ_METADATA:
|
|
if (s[0] == '[' && s[1] == '/')
|
|
conf_read_mode = MTGAllCards::READ_ANYTHING;
|
|
else if (si) si->processConfLine(s);
|
|
continue;
|
|
case MTGAllCards::READ_CARD:
|
|
if (s[0] == '[' && s[1] == '/')
|
|
{
|
|
conf_read_mode = MTGAllCards::READ_ANYTHING;
|
|
if (tempPrimitive) tempPrimitive = addPrimitive(tempPrimitive, tempCard);
|
|
if (tempCard)
|
|
{
|
|
if (tempPrimitive) tempCard->setPrimitive(tempPrimitive);
|
|
addCardToCollection(tempCard, set_id);
|
|
}
|
|
tempCard = NULL;
|
|
tempPrimitive = NULL;
|
|
}
|
|
else
|
|
{
|
|
if (!processConfLine(s, tempCard, tempPrimitive))
|
|
DebugTrace("MTGDECK: BAD Line: \n[" << lineNumber << "]: " << s );
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
file.close();
|
|
return total_cards;
|
|
}
|
|
|
|
MTGAllCards* MTGAllCards::instance = NULL;
|
|
|
|
MTGAllCards::MTGAllCards()
|
|
{
|
|
init();
|
|
}
|
|
|
|
MTGAllCards::~MTGAllCards()
|
|
{
|
|
for (map<int, MTGCard *>::iterator it = collection.begin(); it != collection.end(); it++)
|
|
delete (it->second);
|
|
collection.clear();
|
|
ids.clear();
|
|
|
|
for (map<string, CardPrimitive *>::iterator it = primitives.begin(); it != primitives.end(); it++)
|
|
delete (it->second);
|
|
primitives.clear();
|
|
}
|
|
|
|
MTGAllCards* MTGAllCards::getInstance()
|
|
{
|
|
if(!instance)
|
|
instance = new MTGAllCards();
|
|
|
|
return instance;
|
|
}
|
|
|
|
void MTGAllCards::unloadAll()
|
|
{
|
|
if(instance) {
|
|
delete instance;
|
|
instance = NULL;
|
|
}
|
|
}
|
|
|
|
int MTGAllCards::randomCardId()
|
|
{
|
|
int id = (rand() % ids.size());
|
|
return ids[id];
|
|
}
|
|
|
|
int MTGAllCards::countBySet(int setId)
|
|
{
|
|
int result = 0;
|
|
map<int, MTGCard *>::iterator it;
|
|
|
|
for (it = collection.begin(); it != collection.end(); it++)
|
|
{
|
|
MTGCard * c = it->second;
|
|
if (c->setId == setId)
|
|
{
|
|
result++;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//TODO more efficient way ?
|
|
int MTGAllCards::countByType(const string &_type)
|
|
{
|
|
int type_id = findType(_type);
|
|
|
|
int result = 0;
|
|
map<int, MTGCard *>::iterator it;
|
|
for (it = collection.begin(); it != collection.end(); it++)
|
|
{
|
|
MTGCard * c = it->second;
|
|
if (c->data->hasType(type_id))
|
|
{
|
|
result++;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int MTGAllCards::countByColor(int color)
|
|
{
|
|
if (colorsCount[color] == 0)
|
|
{
|
|
for (int i = 0; i < Constants::NB_Colors; i++)
|
|
{
|
|
colorsCount[i] = 0;
|
|
}
|
|
map<int, MTGCard *>::iterator it;
|
|
for (it = collection.begin(); it != collection.end(); it++)
|
|
{
|
|
MTGCard * c = it->second;
|
|
int j = c->data->getColor();
|
|
|
|
colorsCount[j]++;
|
|
}
|
|
}
|
|
return colorsCount[color];
|
|
}
|
|
|
|
int MTGAllCards::totalCards()
|
|
{
|
|
return (total_cards);
|
|
}
|
|
|
|
bool MTGAllCards::addCardToCollection(MTGCard * card, int setId)
|
|
{
|
|
card->setId = setId;
|
|
int newId = card->getId();
|
|
if (collection.find(newId) != collection.end())
|
|
{
|
|
#if defined (_DEBUG)
|
|
string cardName = card->data ? card->data->name : card->getImageName();
|
|
string setName = setId != -1 ? setlist.getInfo(setId)->getName() : "";
|
|
DebugTrace("warning, card id collision! : " << newId << " -> " << cardName << "(" << setName << ")");
|
|
#endif
|
|
SAFE_DELETE(card);
|
|
return false;
|
|
}
|
|
|
|
//Don't add cards that don't have a primitive
|
|
if (!card->data)
|
|
{
|
|
SAFE_DELETE(card);
|
|
return false;
|
|
}
|
|
ids.push_back(newId);
|
|
|
|
collection[newId] = card; //Push card into collection.
|
|
MTGSetInfo * si = setlist.getInfo(setId);
|
|
if (si) si->count(card); //Count card in set info
|
|
++total_cards;
|
|
return true;
|
|
}
|
|
|
|
CardPrimitive * MTGAllCards::addPrimitive(CardPrimitive * primitive, MTGCard * card)
|
|
{
|
|
int maxGrade = options[Options::MAX_GRADE].number;
|
|
if (!maxGrade) maxGrade = Constants::GRADE_BORDERLINE; //Default setting for grade is borderline?
|
|
if (currentGrade > maxGrade)
|
|
{
|
|
SAFE_DELETE(primitive);
|
|
return NULL;
|
|
}
|
|
string key;
|
|
if (card)
|
|
{
|
|
std::stringstream ss;
|
|
ss << card->getId();
|
|
ss >> key;
|
|
}
|
|
else
|
|
key = primitive->name;
|
|
if (primitives.find(key) != primitives.end())
|
|
{
|
|
//ERROR
|
|
//Todo move the deletion somewhere else ?
|
|
DebugTrace("MTGDECK: primitives conflict: "<< key);
|
|
SAFE_DELETE(primitive);
|
|
return NULL;
|
|
}
|
|
//translate cards text
|
|
Translator * t = Translator::GetInstance();
|
|
map<string,string>::iterator it = t->tempValues.find(primitive->name);
|
|
if (it != t->tempValues.end())
|
|
{
|
|
primitive->setText(it->second);
|
|
}
|
|
|
|
//Legacy:
|
|
//For the Deck editor, we need Lands and Artifact to be colors...
|
|
if (primitive->hasType(Subtypes::TYPE_LAND)) primitive->setColor(Constants::MTG_COLOR_LAND);
|
|
if (primitive->hasType(Subtypes::TYPE_ARTIFACT)) primitive->setColor(Constants::MTG_COLOR_ARTIFACT);
|
|
|
|
primitives[key] = primitive;
|
|
return primitive;
|
|
}
|
|
|
|
MTGCard * MTGAllCards::getCardById(int id)
|
|
{
|
|
map<int, MTGCard *>::iterator it = collection.find(id);
|
|
if (it != collection.end())
|
|
{
|
|
return (it->second);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
MTGCard * MTGAllCards::_(int index)
|
|
{
|
|
if (index >= total_cards) return NULL;
|
|
return getCardById(ids[index]);
|
|
}
|
|
|
|
#ifdef TESTSUITE
|
|
void MTGAllCards::prefetchCardNameCache()
|
|
{
|
|
map<int, MTGCard *>::iterator it;
|
|
for (it = collection.begin(); it != collection.end(); it++)
|
|
{
|
|
MTGCard * c = it->second;
|
|
|
|
//Name only
|
|
string cardName = c->data->name;
|
|
std::transform(cardName.begin(), cardName.end(), cardName.begin(), ::tolower);
|
|
mtgCardByNameCache[cardName] = c;
|
|
|
|
//Name + set
|
|
int setId = c->setId;
|
|
MTGSetInfo* setInfo = setlist.getInfo(setId);
|
|
if (setInfo)
|
|
{
|
|
string setName = setInfo->getName();
|
|
std::transform(setName.begin(), setName.end(), setName.begin(), ::tolower);
|
|
cardName = cardName + " (" + setName + ")";
|
|
mtgCardByNameCache[cardName] = c;
|
|
}
|
|
|
|
// id
|
|
std::stringstream out;
|
|
out << c->getMTGId();
|
|
mtgCardByNameCache[out.str()] = c;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
MTGCard * MTGAllCards::getCardByName(string nameDescriptor)
|
|
{
|
|
boost::mutex::scoped_lock lock(instance->mMutex);
|
|
if (!nameDescriptor.size()) return NULL;
|
|
if (nameDescriptor[0] == '#') return NULL;
|
|
|
|
std::transform(nameDescriptor.begin(), nameDescriptor.end(), nameDescriptor.begin(), ::tolower);
|
|
|
|
map<string, MTGCard * >::iterator cached = mtgCardByNameCache.find(nameDescriptor);
|
|
|
|
if (cached!= mtgCardByNameCache.end())
|
|
{
|
|
return cached->second;
|
|
}
|
|
|
|
int cardnb = atoi(nameDescriptor.c_str());
|
|
if (cardnb)
|
|
{
|
|
MTGCard * result = getCardById(cardnb);
|
|
mtgCardByNameCache[nameDescriptor] = result;
|
|
return result;
|
|
}
|
|
|
|
int setId = -1;
|
|
size_t found = nameDescriptor.find(" (");
|
|
string name = nameDescriptor;
|
|
if (found != string::npos)
|
|
{
|
|
size_t end = nameDescriptor.find(")");
|
|
string setName = nameDescriptor.substr(found + 2, end - found - 2);
|
|
trim(setName);
|
|
name = nameDescriptor.substr(0, found);
|
|
trim(name);
|
|
setId = setlist[setName];
|
|
|
|
//Reconstruct a clean string "name (set)" for cache consistency
|
|
nameDescriptor = name + " (" + setName + ")";
|
|
|
|
}
|
|
map<int, MTGCard *>::iterator it;
|
|
for (it = collection.begin(); it != collection.end(); it++)
|
|
{
|
|
MTGCard * c = it->second;
|
|
if (setId != -1 && setId != c->setId) continue;
|
|
string cardName = c->data->name;
|
|
std::transform(cardName.begin(), cardName.end(), cardName.begin(), ::tolower);
|
|
if (cardName.compare(name) == 0) {
|
|
mtgCardByNameCache[nameDescriptor] = c;
|
|
return c;
|
|
}
|
|
|
|
}
|
|
mtgCardByNameCache[nameDescriptor] = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
//MTGDeck
|
|
MTGDeck::MTGDeck(MTGAllCards * _allcards)
|
|
{
|
|
total_cards = 0;
|
|
database = _allcards;
|
|
filename = "";
|
|
meta_name = "";
|
|
meta_commander = false;
|
|
}
|
|
|
|
int MTGDeck::totalPrice()
|
|
{
|
|
int total = 0;
|
|
PriceList * pricelist = NEW PriceList("settings/prices.dat", MTGCollection());
|
|
map<int, int>::iterator it;
|
|
for (it = cards.begin(); it != cards.end(); it++)
|
|
{
|
|
int nb = it->second;
|
|
if (nb) total += pricelist->getPrice(it->first);
|
|
}
|
|
SAFE_DELETE(pricelist);
|
|
return total;
|
|
}
|
|
|
|
MTGDeck::MTGDeck(const string& config_file, MTGAllCards * _allcards, int meta_only, int difficultyRating)
|
|
{
|
|
total_cards = 0;
|
|
database = _allcards;
|
|
filename = config_file;
|
|
size_t slash = filename.find_last_of("/");
|
|
size_t dot = filename.find(".");
|
|
meta_name = filename.substr(slash + 1, dot - slash - 1);
|
|
meta_id = atoi(meta_name.substr(4).c_str());
|
|
meta_commander = false;
|
|
std::string contents;
|
|
if (JFileSystem::GetInstance()->readIntoString(config_file, contents))
|
|
{
|
|
std::stringstream stream(contents);
|
|
std::string s;
|
|
while (std::getline(stream, s))
|
|
{
|
|
if (!s.size()) continue;
|
|
if (s[s.size() - 1] == '\r') s.erase(s.size() - 1); //Handle DOS files
|
|
if (!s.size()) continue;
|
|
if (s[0] == '#')
|
|
{
|
|
size_t found = s.find("NAME:");
|
|
if (found != string::npos)
|
|
{
|
|
meta_name = s.substr(found + 5);
|
|
continue;
|
|
}
|
|
found = s.find("DESC:");
|
|
if (found != string::npos)
|
|
{
|
|
if (meta_desc.size()) meta_desc.append("\n");
|
|
meta_desc.append(s.substr(found + 5));
|
|
continue;
|
|
}
|
|
found = s.find("HINT:");
|
|
if (found != string::npos)
|
|
{
|
|
meta_AIHints.push_back(s.substr(found + 5));
|
|
continue;
|
|
}
|
|
found = s.find("UNLOCK:");
|
|
if (found != string::npos)
|
|
{
|
|
meta_unlockRequirements = s.substr(found + 7);
|
|
continue;
|
|
}
|
|
found = s.find("SB:"); // Now it's possible to add cards to Sideboard even using their Name instead of ID such as normal deck cards.
|
|
if (found != string::npos && database)
|
|
{
|
|
s = s.substr(found + 3);
|
|
s.erase(s.find_last_not_of("\t\n\v\f\r ") + 1);
|
|
s.erase(0, s.find_first_not_of("\t\n\v\f\r "));
|
|
std::string::const_iterator it = s.begin();
|
|
while (it != s.end() && std::isdigit(*it)) ++it;
|
|
if(!s.empty() && it == s.end())
|
|
Sideboard.push_back(s);
|
|
else {
|
|
int numberOfCopies = 1;
|
|
size_t found = s.find(" *");
|
|
if (found != string::npos){
|
|
numberOfCopies = atoi(s.substr(found + 2).c_str());
|
|
s = s.substr(0, found);
|
|
}
|
|
MTGCard * card = database->getCardByName(s);
|
|
if (card){
|
|
for (int i = 0; i < numberOfCopies; i++){
|
|
std::stringstream str_id;
|
|
str_id << card->getId();
|
|
Sideboard.push_back(str_id.str());
|
|
}
|
|
}else {
|
|
DebugTrace("could not add to Sideboard any card with name: " << s);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
found = s.find("CMD:"); // Now it's possible to add a card to Command Zone even using their Name instead of ID such as normal deck cards.
|
|
if (found != string::npos)
|
|
{
|
|
meta_commander = true; //Added to read the command tag in metafile.
|
|
if(!database) continue;
|
|
s = s.substr(found + 4);
|
|
s.erase(s.find_last_not_of("\t\n\v\f\r ") + 1);
|
|
s.erase(0, s.find_first_not_of("\t\n\v\f\r "));
|
|
std::string::const_iterator it = s.begin();
|
|
while (it != s.end() && std::isdigit(*it)) ++it;
|
|
if(!s.empty() && it == s.end()){
|
|
MTGCard * newcard = database->getCardById(atoi(s.c_str()));
|
|
if(!CommandZone.size() && newcard->data->hasType("Legendary") && (newcard->data->hasType("Creature") || newcard->data->basicAbilities[Constants::CANBECOMMANDER])) // If no commander has been added you can add one.
|
|
CommandZone.push_back(s);
|
|
else if(CommandZone.size() == 1 && newcard->data->hasType("Legendary") && (newcard->data->hasType("Creature") || newcard->data->basicAbilities[Constants::CANBECOMMANDER])){ // If a commander has been added you can add a new one just if both have partner ability.
|
|
if(newcard && newcard->data->basicAbilities[Constants::PARTNER]){
|
|
MTGCard * oldcard = database->getCardById(atoi((CommandZone.at(0)).c_str()));
|
|
if(oldcard && oldcard->data->basicAbilities[Constants::PARTNER] && oldcard->data->name != newcard->data->name)
|
|
CommandZone.push_back(s);
|
|
}
|
|
}
|
|
}else {
|
|
size_t found = s.find(" *");
|
|
if (found != string::npos)
|
|
s = s.substr(0, found);
|
|
MTGCard * newcard = database->getCardByName(s);
|
|
if (newcard){
|
|
std::stringstream str_id;
|
|
str_id << newcard->getId();
|
|
if(!CommandZone.size() && newcard->data->hasType("Legendary") && (newcard->data->hasType("Creature") || newcard->data->basicAbilities[Constants::CANBECOMMANDER])) // If no commander has been added you can add one.
|
|
CommandZone.push_back(str_id.str());
|
|
else if(CommandZone.size() == 1 && newcard->data->hasType("Legendary") && (newcard->data->hasType("Creature") || newcard->data->basicAbilities[Constants::CANBECOMMANDER])){ // If a commander has been added you can add a new one just if both have partner ability.
|
|
if(newcard->data->basicAbilities[Constants::PARTNER]){
|
|
MTGCard * oldcard = database->getCardById(atoi((CommandZone.at(0)).c_str()));
|
|
if(oldcard && oldcard->data->basicAbilities[Constants::PARTNER] && oldcard->data->name != newcard->data->name)
|
|
CommandZone.push_back(str_id.str());
|
|
}
|
|
}
|
|
}else {
|
|
DebugTrace("could not add to CommandZone any card with name: " << s);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
continue;
|
|
}
|
|
if (meta_only) continue; //Changed from break in order to read the command tag in metafile.
|
|
int numberOfCopies = 1;
|
|
size_t found = s.find(" *");
|
|
if (found != string::npos)
|
|
{
|
|
numberOfCopies = atoi(s.substr(found + 2).c_str());
|
|
s = s.substr(0, found);
|
|
}
|
|
size_t diff = s.find("toggledifficulty:");
|
|
if(diff != string::npos)
|
|
{
|
|
string cards = s.substr(diff + 17);
|
|
size_t separator = cards.find("|");
|
|
string cardeasy = cards.substr(0,separator);
|
|
string cardhard = cards.substr(separator + 1);
|
|
if(difficultyRating == HARD)
|
|
{
|
|
s = cardhard;
|
|
}
|
|
else
|
|
{
|
|
s = cardeasy;
|
|
}
|
|
}
|
|
MTGCard * card = database->getCardByName(s);
|
|
if (card)
|
|
{
|
|
for (int i = 0; i < numberOfCopies; i++)
|
|
{
|
|
add(card);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugTrace("could not find Card matching name: " << s);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugTrace("FATAL:MTGDeck.cpp:MTGDeck - can't load deck file");
|
|
}
|
|
}
|
|
|
|
int MTGDeck::totalCards()
|
|
{
|
|
return total_cards;
|
|
}
|
|
|
|
string MTGDeck::getFilename()
|
|
{
|
|
return filename;
|
|
}
|
|
|
|
MTGCard * MTGDeck::getCardById(int mtgId)
|
|
{
|
|
return database->getCardById(mtgId);
|
|
}
|
|
|
|
int MTGDeck::addRandomCards(int howmany, int * setIds, int nbSets, int rarity, const string &_subtype, int * colors, int nbcolors)
|
|
{
|
|
if (howmany <= 0) return 1;
|
|
|
|
vector<int> unallowedColors;
|
|
unallowedColors.resize(Constants::NB_Colors + 1);
|
|
for (int i = 0; i < Constants::NB_Colors; ++i)
|
|
{
|
|
if (nbcolors)
|
|
unallowedColors[i] = 1;
|
|
else
|
|
unallowedColors[i] = 0;
|
|
}
|
|
for (int i = 0; i < nbcolors; ++i)
|
|
{
|
|
unallowedColors[colors[i]] = 0;
|
|
}
|
|
|
|
int collectionTotal = database->totalCards();
|
|
if (!collectionTotal) return 0;
|
|
|
|
string subtype;
|
|
if (_subtype.size()) subtype = _subtype;
|
|
|
|
vector<int> subcollection;
|
|
int subtotal = 0;
|
|
for (int i = 0; i < collectionTotal; i++)
|
|
{
|
|
MTGCard * card = database->_(i);
|
|
int r = card->getRarity();
|
|
if (r != Constants::RARITY_T && (rarity == -1 || r == rarity) && // remove tokens
|
|
card->setId != MTGSets::INTERNAL_SET && //remove cards that are defined in primitives. Those are workarounds (usually tokens) and should only be used internally
|
|
(!_subtype.size() || card->data->hasSubtype(subtype)))
|
|
{
|
|
int ok = 0;
|
|
|
|
if (!nbSets) ok = 1;
|
|
for (int j = 0; j < nbSets; ++j)
|
|
{
|
|
if (card->setId == setIds[j])
|
|
{
|
|
ok = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
{
|
|
for (int j = 0; j < Constants::NB_Colors; ++j)
|
|
{
|
|
if (unallowedColors[j] && card->data->hasColor(j))
|
|
{
|
|
ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
{
|
|
subcollection.push_back(card->getId());
|
|
subtotal++;
|
|
}
|
|
}
|
|
}
|
|
if (subtotal == 0)
|
|
{
|
|
if (rarity == Constants::RARITY_M) return addRandomCards(howmany, setIds, nbSets, Constants::RARITY_R, _subtype, colors, nbcolors);
|
|
return 0;
|
|
}
|
|
for (int i = 0; i < howmany; i++)
|
|
{
|
|
int id = (rand() % subtotal);
|
|
add(subcollection[id]);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int MTGDeck::add(MTGDeck * deck)
|
|
{
|
|
map<int, int>::iterator it;
|
|
for (it = deck->cards.begin(); it != deck->cards.end(); it++)
|
|
{
|
|
for (int i = 0; i < it->second; i++)
|
|
{
|
|
add(it->first);
|
|
}
|
|
}
|
|
return deck->totalCards();
|
|
}
|
|
|
|
int MTGDeck::add(int cardid)
|
|
{
|
|
if (!database->getCardById(cardid))
|
|
return 0;
|
|
if (cards.find(cardid) == cards.end())
|
|
{
|
|
cards[cardid] = 1;
|
|
}
|
|
else
|
|
{
|
|
cards[cardid]++;
|
|
}
|
|
++total_cards;
|
|
//initCounters();
|
|
return total_cards;
|
|
}
|
|
|
|
int MTGDeck::add(MTGCard * card)
|
|
{
|
|
if (!card)
|
|
return 0;
|
|
return (add(card->getId()));
|
|
}
|
|
|
|
int MTGDeck::complete()
|
|
{
|
|
/* (PSY) adds cards to the deck/collection. Makes sure that the deck
|
|
or collection has at least 4 of every implemented card. Does not
|
|
change the number of cards of which already 4 or more are present. */
|
|
int id, n;
|
|
bool StypeIsNothing;
|
|
size_t databaseSize = database->ids.size();
|
|
for (size_t it = 0; it < databaseSize; it++)
|
|
{
|
|
id = database->ids[it];
|
|
StypeIsNothing = false;
|
|
if (database->getCardById(id)->data->hasType("nothing"))
|
|
{
|
|
StypeIsNothing = true;
|
|
}
|
|
if (!StypeIsNothing )
|
|
{
|
|
if (cards.find(id) == cards.end())
|
|
{
|
|
cards[id] = 4;
|
|
total_cards += 4;
|
|
}
|
|
else
|
|
{
|
|
n = cards[id];
|
|
if (n < 4)
|
|
{
|
|
total_cards += 4 - n;
|
|
cards[id] = 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int MTGDeck::removeAll()
|
|
{
|
|
total_cards = 0;
|
|
cards.clear();
|
|
//initCounters();
|
|
return 1;
|
|
}
|
|
|
|
void MTGDeck::replaceSB(vector<string> newSB)
|
|
{
|
|
if(newSB.size())
|
|
{
|
|
Sideboard.clear();
|
|
Sideboard = newSB;
|
|
}
|
|
return;
|
|
}
|
|
|
|
void MTGDeck::replaceCMD(vector<string> newCMD)
|
|
{
|
|
if(newCMD.size())
|
|
{
|
|
CommandZone.clear();
|
|
CommandZone = newCMD;
|
|
}
|
|
return;
|
|
}
|
|
|
|
int MTGDeck::remove(int cardid)
|
|
{
|
|
if (cards.find(cardid) == cards.end() || cards[cardid] == 0) return 0;
|
|
cards[cardid]--;
|
|
total_cards--;
|
|
//initCounters();
|
|
return 1;
|
|
}
|
|
|
|
int MTGDeck::remove(MTGCard * card)
|
|
{
|
|
if (!card) return 0;
|
|
return (remove(card->getId()));
|
|
}
|
|
|
|
int MTGDeck::save()
|
|
{
|
|
return save(filename, false, meta_name, meta_desc);
|
|
}
|
|
|
|
int MTGDeck::save(const string& destFileName, bool useExpandedDescriptions, const string& deckTitle, const string& deckDesc)
|
|
{
|
|
string tmp = destFileName;
|
|
tmp.append(".tmp"); //not thread safe
|
|
std::ofstream file;
|
|
if (JFileSystem::GetInstance()->openForWrite(file, tmp))
|
|
{
|
|
char writer[512];
|
|
DebugTrace("Saving Deck: " << deckTitle << " to " << destFileName );
|
|
if (meta_name.size())
|
|
{
|
|
file << "#NAME:" << deckTitle << '\n';
|
|
}
|
|
|
|
if (meta_desc.size())
|
|
{
|
|
size_t found = 0;
|
|
string desc = deckDesc;
|
|
found = desc.find_first_of("\n");
|
|
while (found != string::npos)
|
|
{
|
|
file << "#DESC:" << desc.substr(0, found + 1);
|
|
desc = desc.substr(found + 1);
|
|
found = desc.find_first_of("\n");
|
|
}
|
|
file << "#DESC:" << desc << "\n";
|
|
}
|
|
|
|
bool saveDetailedDeckInfo = options.get( Options::SAVEDETAILEDDECKINFO )->number == 1;
|
|
|
|
if ( filename.find("collection.dat") != string::npos )
|
|
saveDetailedDeckInfo = false;
|
|
|
|
if (useExpandedDescriptions || saveDetailedDeckInfo)
|
|
{
|
|
printDetailedDeckText(file);
|
|
}
|
|
else
|
|
{
|
|
map<int, int>::iterator it;
|
|
for (it = cards.begin(); it != cards.end(); it++)
|
|
{
|
|
sprintf(writer, "%i\n", it->first);
|
|
for (int j = 0; j < it->second; j++)
|
|
{
|
|
file << writer;
|
|
}
|
|
}
|
|
}
|
|
//save sideboards
|
|
if(Sideboard.size())
|
|
{
|
|
sort(Sideboard.begin(), Sideboard.end());
|
|
for(unsigned int k = 0; k < Sideboard.size(); k++)
|
|
{
|
|
int checkID = atoi(Sideboard[k].c_str());
|
|
if(checkID)
|
|
file << "#SB:" << checkID << "\n";
|
|
}
|
|
}
|
|
//save commanders
|
|
if(CommandZone.size())
|
|
{
|
|
sort(CommandZone.begin(), CommandZone.end());
|
|
for(unsigned int k = 0; k < CommandZone.size(); k++)
|
|
{
|
|
int checkID = atoi(CommandZone[k].c_str());
|
|
if(checkID)
|
|
file << "#CMD:" << checkID << "\n";
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
JFileSystem::GetInstance()->Rename(tmp, destFileName);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/***
|
|
print out an expanded version of the deck to file.
|
|
This save meta data about each card to allow easy reading of the deck file. It will
|
|
also save each card by id, to speed up the loading of the deck next time.
|
|
*/
|
|
void MTGDeck::printDetailedDeckText(std::ofstream& file )
|
|
{
|
|
ostringstream currentCard, creatures, lands, spells, types;
|
|
ostringstream ss_creatures, ss_lands, ss_spells;
|
|
int numberOfCreatures = 0;
|
|
int numberOfSpells = 0;
|
|
int numberOfLands = 0;
|
|
|
|
map<int, int>::iterator it;
|
|
for (it = cards.begin(); it != cards.end(); it++)
|
|
{
|
|
int cardId = it->first;
|
|
int nbCards = it->second;
|
|
MTGCard *card = this->getCardById( cardId );
|
|
if (card == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
MTGSetInfo *setInfo = setlist.getInfo(card->setId);
|
|
string setName = setInfo->id;
|
|
string cardName = card->data->getName();
|
|
|
|
currentCard << "#" << nbCards << "x " << cardName << " (" << setName << "), ";
|
|
|
|
if ( !card->data->isLand() )
|
|
currentCard << card->data->getManaCost() << ", ";
|
|
|
|
// Add the card's types
|
|
vector<int>::iterator typeIter;
|
|
for ( typeIter = card->data->types.begin(); typeIter != card->data->types.end(); ++typeIter )
|
|
types << MTGAllCards::findType( *typeIter ) << " ";
|
|
|
|
currentCard << trim(types.str()) << ", ";
|
|
types.str(""); // reset the buffer.
|
|
|
|
// Add P/T if a creature
|
|
if ( card->data->isCreature() )
|
|
currentCard << card->data->getPower() << "/" << card->data->getToughness() << ", ";
|
|
|
|
|
|
if ( card->data->getOtherRestrictions().size() )
|
|
currentCard << ", " << card->data->getOtherRestrictions();
|
|
|
|
for (size_t x = 0; x < card->data->basicAbilities.size(); ++x)
|
|
{
|
|
if ( card->data->basicAbilities[x] == 1 )
|
|
currentCard << Constants::MTGBasicAbilities[x] << "; ";
|
|
}
|
|
currentCard <<endl;
|
|
|
|
for ( int i = 0; i < nbCards; i++ )
|
|
currentCard << cardId << endl;
|
|
|
|
currentCard <<endl;
|
|
setInfo = NULL;
|
|
// Add counter to know number of creatures, non-creature spells and lands present in the deck
|
|
if ( card->data->isLand() )
|
|
{
|
|
lands<< currentCard.str();
|
|
numberOfLands+=nbCards;
|
|
}
|
|
else if ( card->data->isCreature() )
|
|
{
|
|
creatures << currentCard.str();
|
|
numberOfCreatures+=nbCards;
|
|
}
|
|
else
|
|
{
|
|
spells << currentCard.str();
|
|
numberOfSpells+=nbCards;
|
|
}
|
|
currentCard.str("");
|
|
}
|
|
ss_creatures << numberOfCreatures;
|
|
ss_spells << numberOfSpells;
|
|
ss_lands << numberOfLands;
|
|
|
|
file << getCardBlockText( "Creatures x" + ss_creatures.str(), creatures.str() ) ;
|
|
file << getCardBlockText( "Spells x" + ss_spells.str(), spells.str() ) ;
|
|
file << getCardBlockText( "Lands x" + ss_lands.str(), lands.str() ) ;
|
|
creatures.str("");
|
|
spells.str("");
|
|
lands.str("");
|
|
}
|
|
|
|
/***
|
|
* Convience method to print out blocks of card descriptions
|
|
*/
|
|
string MTGDeck::getCardBlockText( const string& title, const string& text )
|
|
{
|
|
ostringstream oss;
|
|
string textBlock (text);
|
|
|
|
oss << setfill('#') << setw( 40 ) << "#" << endl;
|
|
oss << "# " << setfill(' ') << setw(34) << left << title << "#" << endl;
|
|
oss << setfill('#') << setw( 40 ) << "#" << endl;
|
|
oss << trim(textBlock) << endl;
|
|
|
|
return oss.str();
|
|
}
|
|
|
|
//MTGSets
|
|
MTGSets setlist; //Our global.
|
|
|
|
MTGSets::MTGSets()
|
|
{
|
|
}
|
|
|
|
MTGSets::~MTGSets()
|
|
{
|
|
for (size_t i = 0; i < setinfo.size(); ++i)
|
|
{
|
|
delete (setinfo[i]);
|
|
}
|
|
}
|
|
|
|
MTGSetInfo* MTGSets::getInfo(int setID)
|
|
{
|
|
if (setID < 0 || setID >= (int) setinfo.size()) return NULL;
|
|
|
|
return setinfo[setID];
|
|
}
|
|
|
|
MTGSetInfo* MTGSets::randomSet(int blockId, int atleast)
|
|
{
|
|
char * unlocked = (char *) calloc(size(), sizeof(char));
|
|
|
|
//Figure out which sets are available.
|
|
for (int i = 0; i < size(); i++)
|
|
{
|
|
unlocked[i] = options[Options::optionSet(i)].number;
|
|
}
|
|
//No luck randomly. Now iterate from a random location.
|
|
int a = 0, iter = 0;
|
|
while (iter < 3)
|
|
{
|
|
a = rand() % size();
|
|
for (int i = a; i < size(); i++)
|
|
{
|
|
if (unlocked[i] && (blockId == -1 || setinfo[i]->block == blockId) &&
|
|
(atleast == -1 || setinfo[i]->totalCards() >= atleast))
|
|
{
|
|
free(unlocked);
|
|
return setinfo[i];
|
|
}
|
|
}
|
|
for (int i = 0; i < a; i++)
|
|
{
|
|
if (unlocked[i] && (blockId == -1 || setinfo[i]->block == blockId) &&
|
|
(atleast == -1 || setinfo[i]->totalCards() >= atleast))
|
|
{
|
|
free(unlocked);
|
|
return setinfo[i];
|
|
}
|
|
}
|
|
blockId = -1;
|
|
iter++;
|
|
if (iter == 2) atleast = -1;
|
|
}
|
|
free(unlocked);
|
|
return NULL;
|
|
}
|
|
|
|
int blockSize(int blockId);
|
|
|
|
int MTGSets::Add(const string& name)
|
|
{
|
|
int setid = findSet(name);
|
|
if (setid != -1) return setid;
|
|
|
|
MTGSetInfo* s = NEW MTGSetInfo(name);
|
|
setinfo.push_back(s);
|
|
setid = (int) setinfo.size();
|
|
|
|
return setid - 1;
|
|
}
|
|
|
|
int MTGSets::findSet(string name)
|
|
{
|
|
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
|
|
|
for (int i = 0; i < (int) setinfo.size(); i++)
|
|
{
|
|
MTGSetInfo* s = setinfo[i];
|
|
if (!s) continue;
|
|
string set = s->id;
|
|
std::transform(set.begin(), set.end(), set.begin(), ::tolower);
|
|
if (set.compare(name) == 0) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int MTGSets::findBlock(string s)
|
|
{
|
|
if (!s.size()) return -1;
|
|
|
|
string comp = s;
|
|
std::transform(comp.begin(), comp.end(), comp.begin(), ::tolower);
|
|
for (int i = 0; i < (int) blocks.size(); i++)
|
|
{
|
|
string b = blocks[i];
|
|
std::transform(b.begin(), b.end(), b.begin(), ::tolower);
|
|
if (b.compare(comp) == 0) return i;
|
|
}
|
|
|
|
blocks.push_back(s);
|
|
return ((int) blocks.size()) - 1;
|
|
}
|
|
|
|
int MTGSets::operator[](string id)
|
|
{
|
|
return findSet(id);
|
|
}
|
|
|
|
string MTGSets::operator[](int id)
|
|
{
|
|
if (id < 0 || id >= (int) setinfo.size()) return "";
|
|
|
|
MTGSetInfo * si = setinfo[id];
|
|
if (!si) return "";
|
|
|
|
return si->id;
|
|
}
|
|
|
|
int MTGSets::getSetNum(MTGSetInfo*i)
|
|
{
|
|
int it;
|
|
for (it = 0; it < size(); it++)
|
|
{
|
|
if (setinfo[it] == i) return it;
|
|
}
|
|
return -1;
|
|
}
|
|
int MTGSets::size()
|
|
{
|
|
return (int) setinfo.size();
|
|
}
|
|
|
|
//MTGSetInfo
|
|
MTGSetInfo::~MTGSetInfo()
|
|
{
|
|
SAFE_DELETE(mPack);
|
|
}
|
|
|
|
MTGSetInfo::MTGSetInfo(const string& _id)
|
|
{
|
|
string whitespaces(" \t\f\v\n\r");
|
|
id = _id;
|
|
block = -1;
|
|
year = -1;
|
|
total = -1;
|
|
|
|
for (int i = 0; i < MTGSetInfo::MAX_COUNT; i++)
|
|
counts[i] = 0;
|
|
|
|
char myFilename[4096];
|
|
sprintf(myFilename, "sets/%s/booster.txt", id.c_str());
|
|
mPack = NEW MTGPack(myFilename);
|
|
if (!mPack->isValid())
|
|
{
|
|
SAFE_DELETE(mPack);
|
|
}
|
|
bZipped = false;
|
|
bThemeZipped = false;
|
|
}
|
|
|
|
void MTGSetInfo::count(MTGCard*c)
|
|
{
|
|
if (!c) return;
|
|
|
|
switch (c->getRarity())
|
|
{
|
|
case Constants::RARITY_M:
|
|
counts[MTGSetInfo::MYTHIC]++;
|
|
break;
|
|
case Constants::RARITY_R:
|
|
counts[MTGSetInfo::RARE]++;
|
|
break;
|
|
case Constants::RARITY_U:
|
|
counts[MTGSetInfo::UNCOMMON]++;
|
|
break;
|
|
case Constants::RARITY_C:
|
|
counts[MTGSetInfo::COMMON]++;
|
|
break;
|
|
default:
|
|
case Constants::RARITY_L:
|
|
counts[MTGSetInfo::LAND]++;
|
|
break;
|
|
}
|
|
|
|
counts[MTGSetInfo::TOTAL_CARDS]++;
|
|
}
|
|
|
|
int MTGSetInfo::totalCards()
|
|
{
|
|
return counts[MTGSetInfo::TOTAL_CARDS];
|
|
}
|
|
|
|
string MTGSetInfo::getName()
|
|
{
|
|
if (name.size()) return name; //Pretty name is translated when rendering.
|
|
return id; //Ugly name as well.
|
|
}
|
|
|
|
string MTGSetInfo::getDate()
|
|
{
|
|
if (date.size()) return date; //Return the set release date.
|
|
return "..."; //Fallback if no date has been specified.
|
|
}
|
|
|
|
string MTGSetInfo::getOrderIndex()
|
|
{
|
|
if (orderindex.size()) return orderindex; //Order Index for sorting sets.
|
|
return getName(); // Fallback to name if Order Index is empty.
|
|
}
|
|
|
|
string MTGSetInfo::getBlock()
|
|
{
|
|
if (block < 0 || block >= (int) setlist.blocks.size()) return "None";
|
|
|
|
return setlist.blocks[block];
|
|
}
|
|
|
|
void MTGSetInfo::processConfLine(string line)
|
|
{
|
|
size_t i = line.find_first_of("=");
|
|
if (i == string::npos) return;
|
|
|
|
string key = line.substr(0, i);
|
|
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
|
|
string value = line.substr(i + 1);
|
|
|
|
if (key.compare("name") == 0)
|
|
name = value;
|
|
else if (key.compare("author") == 0)
|
|
author = value;
|
|
else if (key.compare("block") == 0)
|
|
block = setlist.findBlock(value.c_str());
|
|
else if (key.compare("year") == 0){
|
|
date = value; // Added to read the full release date of sets.
|
|
year = atoi(value.substr(0,4).c_str());
|
|
} else if (key.compare("total") == 0)
|
|
total = atoi(value.c_str());
|
|
else if (key.compare("orderindex") == 0)
|
|
orderindex = value; // Added new tag for different sorting of sets.
|
|
}
|