Added/fixed primitives, updated the "missing_cards_by_sets" folder, fixed several crash on Commander Format, fixed a possible crash using "and!()!" with "imprint", added a new macros "_REBOUND_" and "_GOAD_" related to rebound and goad abilities and refactored all cards using them, implemented a new keywords "haunt", "hasprey", "preyname" and "isprey" related the haunting ability and improved all cards using it, Added "commander" and "\*" to HINT castpriority for AI decks in order to allow the user to give a cast priority to commanders.

This commit is contained in:
Vittorio Alfieri
2021-09-02 19:32:45 +02:00
parent 54d0c32035
commit 001cea95bd
51 changed files with 634 additions and 800 deletions
+3 -2
View File
@@ -206,7 +206,7 @@ int AIPlayer::clickSingleTarget(TargetChooser *, vector<Targetable*>& potentialT
AIPlayer * AIPlayerFactory::createAIPlayer(GameObserver *observer, MTGAllCards * collection, Player * opponent, int deckid)
{
char deckFile[512];
string avatarFilename; // default imagename
string avatarFilename = ""; // default imagename
char deckFileSmall[512];
if (deckid == GameStateDuel::MENUITEM_EVIL_TWIN)
@@ -228,7 +228,8 @@ AIPlayer * AIPlayerFactory::createAIPlayer(GameObserver *observer, MTGAllCards *
}
sprintf(deckFile, "ai/baka/deck%i.txt", deckid);
DeckMetaData *aiMeta = observer->getDeckManager()->getDeckMetaDataByFilename( deckFile, true);
avatarFilename = aiMeta->getAvatarFilename();
if(aiMeta)
avatarFilename = aiMeta->getAvatarFilename();
sprintf(deckFileSmall, "ai_baka_deck%i", deckid);
}
+4 -1
View File
@@ -2748,7 +2748,10 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty
}
CardDescriptor cd;
cd.init();
cd.setType(type);
if(!strcmp(type,"commander")) //Added to allow the casting priority for commanders
cd.basicAbilities[Constants::ISCOMMANDER] = 1;
else if(strcmp(type,"*")) //Added to allow the wildcard in casting priority
cd.setType(type);
card = NULL;
payAlternative = NONE;
gotPayments = vector<MTGAbility*>();
+56 -1
View File
@@ -2254,6 +2254,7 @@ int AAImprint::resolve()
{
andAbilityClone->addToGame();
}
SAFE_DELETE(andAbility); //moved here because in destructor it can cause a crash.
}
return 1;
}
@@ -2272,7 +2273,61 @@ AAImprint * AAImprint::clone() const
AAImprint::~AAImprint()
{
SAFE_DELETE(andAbility);
}
//AAHaunt
AAHaunt::AAHaunt(GameObserver* observer, int _id, MTGCardInstance * _source, MTGCardInstance * _target, ManaCost * _cost) :
ActivatedAbility(observer, _id, _source, _cost, 0)
{
target = _target;
andAbility = NULL;
}
int AAHaunt::resolve()
{
MTGCardInstance * _target = (MTGCardInstance *) target;
if (_target && _target->hasType(Subtypes::TYPE_CREATURE))
{
if(_target->mutation && _target->parentCards.size() > 0) return 0; // Mutated down cards cannot be haunted, they will follow the fate of top-card
while(_target->next)
_target = _target->next;
_target->basicAbilities[Constants::ISPREY] = 1;
source->hauntedCard = _target;
if(andAbility)
{
MTGAbility * andAbilityClone = andAbility->clone();
andAbilityClone->target = _target;
if(andAbility->oneShot)
{
andAbilityClone->resolve();
SAFE_DELETE(andAbilityClone);
}
else
{
andAbilityClone->addToGame();
}
SAFE_DELETE(andAbility); //moved here because in destructor it can cause a crash.
}
return 1;
}
return 0;
}
const string AAHaunt::getMenuText()
{
return "Haunt";
}
AAHaunt * AAHaunt::clone() const
{
return NEW AAHaunt(*this);
}
AAHaunt::~AAHaunt()
{
}
//AAConjure
+8
View File
@@ -668,6 +668,14 @@ void GameObserver::gameStateBasedEffects()
card->removeColor(i);
}
}
//clear prey
if(card && isInExile(card) && card->hauntedCard)
{
if(!isInPlay(card->hauntedCard))
{
card->hauntedCard = 0;
}
}
//reset alternate paid
if(card && (isInGrave(card)||isInHand(card)||isInExile(card)))
{
+21
View File
@@ -3372,6 +3372,25 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG
}
}
//haunt a creature
found = s.find("haunt");
if (found != string::npos)
{
if (s.find("haunted") == string::npos)
{
MTGAbility * a = NEW AAHaunt(observer, id, card, target);
a->oneShot = 1;
//andability
if(storedAndAbility.size())
{
string stored = storedAndAbility;
storedAndAbility.clear();
((AAHaunt*)a)->andAbility = parseMagicLine(stored, id, spell, card);
}
return a;
}
}
//Conjure a card
found = s.find("conjure");
if (found != string::npos)
@@ -5306,6 +5325,8 @@ int AbilityFactory::abilityEfficiency(MTGAbility * a, Player * p, int mode, Targ
return BAKA_EFFECT_GOOD;
if (dynamic_cast<AAImprint *> (a))
return BAKA_EFFECT_GOOD;
if (dynamic_cast<AAHaunt *> (a))
return BAKA_EFFECT_GOOD;
if (dynamic_cast<ABestow *> (a))
return BAKA_EFFECT_GOOD;
if (dynamic_cast<AExert *> (a))
+1
View File
@@ -280,6 +280,7 @@ void MTGCardInstance::initMTGCI()
countTrini = 0;
anymanareplacement = false;
imprintedCards.clear();
hauntedCard = NULL;
attackCost = 0;
attackCostBackup = 0;
attackPlaneswalkerCost = 0;
+2 -1
View File
@@ -228,7 +228,8 @@ const char* Constants::MTGBasicAbilities[] = {
"wascommander", //It was the current commander (e.g. after it flipped or morphed)
"showopponenthand", //opponent plays with his hand revealed.
"showcontrollerhand", //controller plays with his hand revealed.
"hasreplicate" //Kicker cost is a replicate cost (eg. "Vacuumelt")
"hasreplicate", //Kicker cost is a replicate cost (eg. "Vacuumelt")
"isprey" //Creature has been haunted by some other card.
};
map<string,int> Constants::MTGBasicAbilitiesMap;
+20 -7
View File
@@ -569,6 +569,10 @@ MTGCardInstance * MTGPlayerCards::putInZone(MTGCardInstance * card, MTGGameZone
}
}
// Save the haunted status... (solving the bug on comparison cards with haunted status before zone changing events)
if(card->has(Constants::ISPREY) && doCopy && !asCopy && !inplaytoinplay)
copy->basicAbilities[Constants::ISPREY] = 1;
//Commander is going back to Command Zone, so we recalculate costs according to how many times it has been casted from there.
if((to == g->players[0]->game->commandzone || to == g->players[1]->game->commandzone) && copy->numofcastfromcommandzone > 0){
copy->getManaCost()->add(Constants::MTG_COLOR_ARTIFACT,2*copy->numofcastfromcommandzone);
@@ -740,8 +744,12 @@ MTGCardInstance * MTGPlayerCards::putInZone(MTGCardInstance * card, MTGGameZone
g->receiveEvent(e);
}
// Reset the haunted status... (if the creature is moving from battlefield is no longer a prey)
if(doCopy && !inplaytoinplay && copy->has(Constants::ISPREY))
copy->basicAbilities[Constants::ISPREY] = 0;
// Erasing counters from copy after the event has been triggered (no counter can survive to a zone changing except the perpetual ones)
if(doCopy && copy->counters && copy->counters->mCount > 0){
if(doCopy && !inplaytoinplay && copy->counters && copy->counters->mCount > 0){
for (unsigned int i = 0; i < copy->counters->counters.size(); i++){
Counter * counter = copy->counters->counters[i];
for(int j = counter->nb; j > 0; j--){
@@ -1469,15 +1477,15 @@ MTGGameZone * MTGGameZone::intToZone(int zoneId, Player * p, Player * p2)
MTGGameZone * MTGGameZone::intToZone(GameObserver *g, int zoneId, MTGCardInstance * source, MTGCardInstance * target)
{
Player *p, *p2;
Player *p = NULL;
Player *p2 = NULL;
if (!source)
if (!source && g) //patchwork fix when g is NULL.
p = g->currentlyActing();
else
p = source->controller();
if (!target)
if (!target && source) //patchwork fix when source is NULL.
{
//TODO source may be NULL, need to handle the case when it is NULL. method declaration has NULL being default value of source and target.
if(source->target)
{
//bug case, this is a patchwork fix for now
@@ -1492,10 +1500,15 @@ MTGGameZone * MTGGameZone::intToZone(GameObserver *g, int zoneId, MTGCardInstanc
target = source;
}
}
else
else if (target)
p2 = target->controller();
if(!p) { //patchwork fix when p is NULL.
if(!p2)
return NULL;
else
p = p2;
}
MTGGameZone * result = intToZone(zoneId, p, p2);
if (result) return result;
switch (zoneId)
+11 -1
View File
@@ -913,6 +913,7 @@ int MTGAlternativeCostRule::reactToClick(MTGCardInstance * card)
return 0;
ManaCost * alternateCost = card->getManaCost()->getAlternative();
if(!alternateCost) return 0;
card->paymenttype = MTGAbility::ALTERNATIVE_COST;
if(alternateCost->extraCosts)
for(unsigned int i = 0; i < alternateCost->extraCosts->costs.size();i++)
@@ -1139,6 +1140,7 @@ int MTGBuyBackRule::reactToClick(MTGCardInstance * card)
return 0;
ManaCost * buybackCost = card->getManaCost()->getBuyback();
if(!buybackCost) return 0;
if(buybackCost->extraCosts)
for(unsigned int i = 0; i < buybackCost->extraCosts->costs.size();i++)
{
@@ -1190,6 +1192,7 @@ int MTGFlashBackRule::isReactingToClick(MTGCardInstance * card, ManaCost * mana)
int MTGFlashBackRule::reactToClick(MTGCardInstance * card)
{
ManaCost * flashbackCost = card->getManaCost()->getFlashback();
if(!flashbackCost) return 0;
if(flashbackCost->extraCosts)
for(unsigned int i = 0; i < flashbackCost->extraCosts->costs.size();i++)
{
@@ -1239,6 +1242,7 @@ int MTGTempFlashBackRule::isReactingToClick(MTGCardInstance * card, ManaCost * m
int MTGTempFlashBackRule::reactToClick(MTGCardInstance * card)
{
ManaCost * flashbackCost = card->getManaCost();
if(!flashbackCost) return 0;
if(flashbackCost->extraCosts)
for(unsigned int i = 0; i < flashbackCost->extraCosts->costs.size();i++)
{
@@ -1306,6 +1310,7 @@ int MTGRetraceRule::reactToClick(MTGCardInstance * card)
return 0;
ManaCost * retraceCost = card->getManaCost()->getRetrace();
if(!retraceCost) return 0;
if(retraceCost->extraCosts)
for(unsigned int i = 0; i < retraceCost->extraCosts->costs.size();i++)
{
@@ -1386,6 +1391,7 @@ int MTGSuspendRule::reactToClick(MTGCardInstance * card)
Player *player = game->currentlyActing();
ManaCost * playerMana = player->getManaPool();
ManaCost * alternateCost = card->getManaCost()->getSuspend();
if(!alternateCost) return 0;
//this handles extra cost payments at the moment a card is played.
if (playerMana->canAfford(alternateCost,card->has(Constants::ANYTYPEOFMANA)))
{
@@ -1512,6 +1518,7 @@ int MTGMorphCostRule::reactToClick(MTGCardInstance * card)
ManaCost * cost = card->getManaCost();
ManaCost * playerMana = player->getManaPool();
ManaCost * morph = card->getManaCost()->getMorph();
if(!morph) return 0;
if(morph->extraCosts){
for(unsigned int i = 0; i < morph->extraCosts->costs.size();i++)
morph->extraCosts->costs[i]->setSource(card);
@@ -1647,6 +1654,7 @@ int MTGPayZeroRule::reactToClick(MTGCardInstance * card)
ManaCost * cost = NEW ManaCost(ManaCost::parseManaCost("{0}",NULL,NULL));
ManaCost * newCost = card->computeNewCost(card,cost,cost);
if(!newCost) return 0;
if(newCost->extraCosts)
for(unsigned int i = 0; i < newCost->extraCosts->costs.size();i++)
{
@@ -1693,6 +1701,7 @@ int MTGOverloadRule::isReactingToClick(MTGCardInstance * card, ManaCost * mana)
return 0;
}
ManaCost * newCost = card->getManaCost()->getAlternative();
if(!newCost) return 0;
if(newCost->extraCosts)
for(unsigned int i = 0; i < newCost->extraCosts->costs.size();i++)
{
@@ -1707,6 +1716,7 @@ int MTGOverloadRule::reactToClick(MTGCardInstance * card)
if (!isReactingToClick(card))
return 0;
ManaCost * newCost = card->getManaCost()->getAlternative();
if(!newCost) return 0;
if(newCost->extraCosts)
for(unsigned int i = 0; i < newCost->extraCosts->costs.size();i++)
{
@@ -1768,7 +1778,7 @@ int MTGBestowRule::reactToClick(MTGCardInstance * card)
//this new method below in all alternative cost type causes a memleak, however, you cant safedelete the cost here as it cause a crash
//TODO::::we need to get to the source of this leak and fix it.
ManaCost * newCost = card->getManaCost()->getBestow();
if(!newCost) return 0;
if (newCost->extraCosts)
for (unsigned int i = 0; i < newCost->extraCosts->costs.size(); i++)
{
+10
View File
@@ -1052,6 +1052,16 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta
cd->nameComparisonMode = COMPARISON_EQUAL;
}
if (attribute.find("preyname") != string::npos && card->hauntedCard)
{
attributefound = 1;
cd->compareName = card->hauntedCard->getName();
if (minus)
cd->nameComparisonMode = COMPARISON_UNEQUAL;
else
cd->nameComparisonMode = COMPARISON_EQUAL;
}
if (!attributefound)
{
//Abilities
+4
View File
@@ -1315,6 +1315,10 @@ void WParsedInt::extendedParse(string s, Spell * spell, MTGCardInstance * card)
{
intValue = (s == "plastshlturn")?card->controller()->lastShuffleTurn:card->controller()->opponent()->lastShuffleTurn;
}
else if (s == "hasprey")
{
intValue = (card->hauntedCard)?1:0;
}
else if(!intValue)//found nothing, try parsing a atoi
{
intValue = atoi(s.c_str());