2 major bug fixes and 1 minor fix
first somehow accidentally line 3616 mtgability.cpp removefromgame was adding an observer instead of removing it, this explains "abilities sometimes acting strangely or not being removed"...I'm surprised it didn't create memleaks or extremely visible side-effects...
2nd fixed a bug where triggered abilities would share a menu with activated abilities of a card when ever you had enough mana floating to pay an activated ability before the trigger resolved.
adjust the way ai calculates if it should use cards like wrath of god, though it is still open to using it at a random chance, i noticed that the method we use might not be the best.
an ability with an eff of 1 for example actually has a 10% chance of being choosen....
lets say rng rolls 3402
when you % this it simply takes the 2 last numbers making this roll 2...meaning that unless we assign no "random chance to do blah" the actual chance of ai using a stupid ability is 10%...I'm leaving that logic how it is tho I "unfactored"(?) it to make it easier to track the numbers, also added a debug trace to help see how often we hit "lottery chance" ...
fixed a minor crash from multiability trying to fetch menutext when no abilities existed in the vector anymore.
this patch introduces a new subkeyword for "may " which is syntax pay(manacost)
auto=may pay({w}) untap
this is to allow the card group that was coded using the activated ability loophole i described at the start. it works the same way as it did with the loophole only it is actually something we want to happen instead of a flaw in the engine...you float the mana same as before and when the may line is triggered it will check if payment can be made with exist mana if so then it displays the menutext for the ability, if that ability is choosen it then charges you the mana directly before activation.
this patch also include flip( ability, tho not intended originally for this version, I had previously had it finished and was polishing it right before I noticed the bugs above. since this keyword is not intended to add cards for this version, I wont go into massive details about it at this time.
This commit is contained in:
@@ -24,6 +24,7 @@ public:
|
||||
vector<ActionElement *> garbage;
|
||||
Targetable * menuObject;
|
||||
SimpleMenu * abilitiesMenu;
|
||||
SimpleMenu * abilitiesTriggered;
|
||||
MTGCardInstance * currentActionCard;
|
||||
int stuffHappened;
|
||||
virtual void Render();
|
||||
|
||||
@@ -880,6 +880,7 @@ public:
|
||||
bool must;
|
||||
Player * previousInterrupter;
|
||||
MTGAbility * mClone;
|
||||
ManaCost * optionalCost;
|
||||
|
||||
MayAbility(GameObserver* observer, int _id, MTGAbility * _ability, MTGCardInstance * _source, bool must = false);
|
||||
|
||||
@@ -3054,6 +3055,18 @@ public:
|
||||
const char * getMenuText();
|
||||
AAMorph * clone() const;
|
||||
};
|
||||
/* flip*/
|
||||
class AAFlip: public InstantAbility
|
||||
{
|
||||
public:
|
||||
vector<MTGAbility *> currentAbilities;
|
||||
string flipStats;
|
||||
AAFlip(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target,string flipStats);
|
||||
int resolve();
|
||||
int testDestroy();
|
||||
const char * getMenuText();
|
||||
AAFlip * clone() const;
|
||||
};
|
||||
/* dynamic ability build*/
|
||||
class AADynamic: public ActivatedAbility
|
||||
{
|
||||
|
||||
@@ -43,8 +43,6 @@ class CardPrimitive
|
||||
#endif
|
||||
{
|
||||
private:
|
||||
string text;
|
||||
vector<string> formattedText;
|
||||
CastRestrictions * restrictions;
|
||||
|
||||
protected:
|
||||
@@ -52,6 +50,8 @@ protected:
|
||||
ManaCost manaCost;
|
||||
|
||||
public:
|
||||
vector<string> formattedText;
|
||||
string text;
|
||||
string name;
|
||||
int init();
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ public:
|
||||
bool morphed;
|
||||
bool turningOver;
|
||||
bool isMorphed;
|
||||
bool isFlipped;
|
||||
bool isPhased;
|
||||
int phasedTurn;
|
||||
bool graveEffects;
|
||||
|
||||
@@ -1619,7 +1619,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty
|
||||
{
|
||||
TargetChooserFactory tcf(observer);
|
||||
TargetChooser * tc = tcf.createTargetChooser(card);
|
||||
int shouldPlayPercentage = 10;
|
||||
int shouldPlayPercentage = 0;
|
||||
if (tc)
|
||||
{
|
||||
int hasTarget = chooseTarget(tc,NULL,NULL,true);
|
||||
@@ -1662,8 +1662,18 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty
|
||||
}
|
||||
else if (BAKA_EFFECT_DONTKNOW == shouldPlay)
|
||||
{
|
||||
shouldPlayPercentage = 80;
|
||||
//previously shouldPlayPercentage = 80;, I found this a little to high
|
||||
//for cards which AI had no idea how to use.
|
||||
shouldPlayPercentage = 60;
|
||||
}
|
||||
else
|
||||
{
|
||||
// shouldPlay == baka_effect_bad giving it a 1 for odd ball lottery chance.
|
||||
shouldPlayPercentage = 1;
|
||||
}
|
||||
DebugTrace("Should I play " << (card ? card->name : "Nothing" ) << "?" << endl
|
||||
<<"shouldPlayPercentage = "<< shouldPlayPercentage);
|
||||
|
||||
}
|
||||
//Reduce the chances of playing a spell with X cost if available mana is low
|
||||
if (hasX)
|
||||
@@ -1694,8 +1704,14 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty
|
||||
if(!canPlay)
|
||||
continue;
|
||||
}
|
||||
if (randomGenerator.random() % 100 > shouldPlayPercentage)
|
||||
int randomChance = randomGenerator.random();
|
||||
int chance = randomChance % 100;
|
||||
if (chance > shouldPlayPercentage)
|
||||
continue;
|
||||
if(shouldPlayPercentage < 10)
|
||||
{
|
||||
DebugTrace("shouldPlayPercentage was less than 10 this was a lottery roll on RNG");
|
||||
}
|
||||
nextCardToPlay = card;
|
||||
maxCost = currentCost;
|
||||
if (hasX)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Targetable.h"
|
||||
#include "WEvent.h"
|
||||
#include "AllAbilities.h"
|
||||
#include "MTGRules.h"
|
||||
|
||||
MTGAbility* ActionLayer::getAbility(int type)
|
||||
{
|
||||
@@ -372,17 +373,42 @@ void ActionLayer::setMenuObject(Targetable * object, bool must)
|
||||
menuObject = object;
|
||||
|
||||
SAFE_DELETE(abilitiesMenu);
|
||||
abilitiesTriggered = NULL;
|
||||
|
||||
abilitiesMenu = NEW SimpleMenu(observer->getInput(), 10, this, Fonts::MAIN_FONT, 100, 100, object->getDisplayName().c_str());
|
||||
abilitiesTriggered = NEW SimpleMenu(observer->getInput(), 10, this, Fonts::MAIN_FONT, 100, 100, object->getDisplayName().c_str());
|
||||
currentActionCard = NULL;
|
||||
for (size_t i = 0; i < mObjects.size(); i++)
|
||||
{
|
||||
ActionElement * currentAction = (ActionElement *) mObjects[i];
|
||||
if (currentAction->isReactingToTargetClick(object))
|
||||
{
|
||||
abilitiesMenu->Add(i, currentAction->getMenuText());
|
||||
if(dynamic_cast<MTGAbility*>(currentAction)->getCost()||dynamic_cast<PermanentAbility*>(currentAction))
|
||||
{
|
||||
abilitiesMenu->Add(i, currentAction->getMenuText());
|
||||
}
|
||||
else
|
||||
{
|
||||
//the only time this condiation is hit is when we are about to display a menu of abilities
|
||||
//which were triggered through a triggered ability or abilities such as multiple target(
|
||||
//and may abilities appearing on cards ie: auto=may draw:1
|
||||
//this prevents abilities activated otherwise from displaying on the same menu as "triggered" and
|
||||
//"put in play" abilities. an activated ability of a card should never share a menu with
|
||||
//a triggered or may ability as it leads to exploits.
|
||||
//only exception is perminent abilities such as "cast card normally" which can share the menu with autohand=
|
||||
abilitiesTriggered->Add(i, currentAction->getMenuText());
|
||||
}
|
||||
}
|
||||
}
|
||||
if(abilitiesTriggered->mCount)
|
||||
{
|
||||
SAFE_DELETE(abilitiesMenu);
|
||||
abilitiesMenu = abilitiesTriggered;
|
||||
}
|
||||
else
|
||||
{
|
||||
SAFE_DELETE(abilitiesTriggered);
|
||||
}
|
||||
if (!must)
|
||||
abilitiesMenu->Add(kCancelMenuID, "Cancel");
|
||||
else
|
||||
@@ -503,6 +529,7 @@ ActionLayer::ActionLayer(GameObserver *observer)
|
||||
{
|
||||
menuObject = NULL;
|
||||
abilitiesMenu = NULL;
|
||||
abilitiesTriggered = NULL;
|
||||
stuffHappened = 0;
|
||||
currentWaitingAction = NULL;
|
||||
cantCancel = 0;
|
||||
|
||||
@@ -1009,6 +1009,108 @@ AAMorph * AAMorph::clone() const
|
||||
a->forceDestroy = 1;
|
||||
return a;
|
||||
}
|
||||
// flip a card
|
||||
AAFlip::AAFlip(GameObserver* observer, int id, MTGCardInstance * card, MTGCardInstance * _target,string flipStats) :
|
||||
InstantAbility(observer, id, card, _target),flipStats(flipStats)
|
||||
{
|
||||
target = _target;
|
||||
}
|
||||
|
||||
int AAFlip::resolve()
|
||||
{
|
||||
MTGCardInstance * Flipper = (MTGCardInstance*)source;
|
||||
if(Flipper->isFlipped)
|
||||
{
|
||||
game->removeObserver(this);
|
||||
return 0;
|
||||
}
|
||||
MTGCardInstance * _target = (MTGCardInstance *) target;
|
||||
if (_target)
|
||||
{
|
||||
while (_target->next)
|
||||
_target = _target->next;
|
||||
|
||||
AbilityFactory af(game);
|
||||
_target->isFlipped = true;
|
||||
GameObserver * game = _target->getObserver();
|
||||
if(flipStats.size())
|
||||
{
|
||||
MTGCard * fcard = MTGCollection()->getCardByName(flipStats);
|
||||
MTGCardInstance * myFlip = NEW MTGCardInstance(fcard, _target->controller()->game);
|
||||
_target->name = myFlip->name;
|
||||
_target->colors = myFlip->colors;
|
||||
_target->power = (myFlip->power + _target->power) - _target->origpower;
|
||||
_target->addToToughness(myFlip->toughness - _target->origtoughness);
|
||||
_target->types = myFlip->types;
|
||||
_target->text = myFlip->text;
|
||||
_target->formattedText = myFlip->formattedText;
|
||||
ActionLayer * al = game->mLayers->actionLayer();
|
||||
for (int k = (int)(al->mObjects.size()) - 1; k > 0; k--)
|
||||
{
|
||||
MTGAbility * a = dynamic_cast<MTGAbility*>(game->mLayers->actionLayer()->mObjects[k]);
|
||||
if(a && a->source == _target)
|
||||
{
|
||||
a->forceDestroy = 1;
|
||||
a->destroy();
|
||||
a->removeFromGame();
|
||||
al->removeFromGame(a);
|
||||
}
|
||||
}
|
||||
_target->magicText = myFlip->magicText;
|
||||
af.getAbilities(¤tAbilities, NULL, _target);
|
||||
for (size_t i = 0; i < currentAbilities.size(); ++i)
|
||||
{
|
||||
MTGAbility * a = currentAbilities[i];
|
||||
a->source = (MTGCardInstance *) _target;
|
||||
if (a)
|
||||
{
|
||||
if (a->oneShot)
|
||||
{
|
||||
a->resolve();
|
||||
delete (a);
|
||||
}
|
||||
else
|
||||
{
|
||||
a->addToGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
SAFE_DELETE(myFlip);
|
||||
}
|
||||
_target->mPropertiesChangedSinceLastUpdate = true;
|
||||
currentAbilities.clear();
|
||||
testDestroy();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AAFlip::testDestroy()
|
||||
{
|
||||
MTGCardInstance * _target = (MTGCardInstance *) target;
|
||||
if(target)
|
||||
{
|
||||
if(_target->isFlipped)
|
||||
{
|
||||
this->forceDestroy = 1;
|
||||
_target->getObserver()->removeObserver(this);
|
||||
_target->isFlipped = false;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char * AAFlip::getMenuText()
|
||||
{
|
||||
return "Flip";
|
||||
}
|
||||
|
||||
AAFlip * AAFlip::clone() const
|
||||
{
|
||||
AAFlip * a = NEW AAFlip(*this);
|
||||
a->forceDestroy = 1;
|
||||
return a;
|
||||
}
|
||||
// AADYNAMIC: dynamic ability builder
|
||||
AADynamic::AADynamic(GameObserver* observer, int id, MTGCardInstance * card, Damageable * _target,int type,int effect,int who,int amountsource,MTGAbility * storedAbility, ManaCost * _cost) :
|
||||
ActivatedAbility(observer, id, card, _cost, 0),type(type),effect(effect),who(who),amountsource(amountsource),storedAbility(storedAbility)
|
||||
@@ -2212,6 +2314,7 @@ MayAbility::MayAbility(GameObserver* observer, int _id, MTGAbility * _ability, M
|
||||
{
|
||||
triggered = 0;
|
||||
mClone = NULL;
|
||||
optionalCost = NULL;
|
||||
}
|
||||
|
||||
void MayAbility::Update(float dt)
|
||||
@@ -2220,6 +2323,8 @@ void MayAbility::Update(float dt)
|
||||
if (!triggered && !game->getCurrentTargetChooser())
|
||||
{
|
||||
triggered = 1;
|
||||
if(optionalCost && !source->controller()->getManaPool()->canAfford(optionalCost))
|
||||
return;
|
||||
if (TargetAbility * ta = dynamic_cast<TargetAbility *>(ability))
|
||||
{
|
||||
if (!ta->getActionTc()->validTargetsExist())
|
||||
@@ -2254,13 +2359,22 @@ int MayAbility::testDestroy()
|
||||
int MayAbility::isReactingToTargetClick(Targetable * card)
|
||||
{
|
||||
if (card == source)
|
||||
return 1;
|
||||
{
|
||||
if(!optionalCost || source->controller()->getManaPool()->canAfford(optionalCost))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MayAbility::reactToTargetClick(Targetable * object)
|
||||
{
|
||||
mClone = ability->clone();
|
||||
if(optionalCost)
|
||||
{
|
||||
source->controller()->getManaPool()->pay(optionalCost);
|
||||
optionalCost->setExtraCostsAction(this, source);
|
||||
optionalCost->doPayExtra();
|
||||
}
|
||||
mClone->addToGame();
|
||||
mClone->forceDestroy = 1;
|
||||
return mClone->reactToTargetClick(object);
|
||||
@@ -2270,12 +2384,14 @@ MayAbility * MayAbility::clone() const
|
||||
{
|
||||
MayAbility * a = NEW MayAbility(*this);
|
||||
a->ability = ability->clone();
|
||||
a->optionalCost = this->optionalCost;
|
||||
return a;
|
||||
}
|
||||
|
||||
MayAbility::~MayAbility()
|
||||
{
|
||||
SAFE_DELETE(ability);
|
||||
SAFE_DELETE(optionalCost);
|
||||
}
|
||||
|
||||
//Menu building ability Abilities
|
||||
@@ -2466,7 +2582,7 @@ int MultiAbility::destroy()
|
||||
|
||||
const char * MultiAbility::getMenuText()
|
||||
{
|
||||
if (abilities.size())
|
||||
if (abilities.size() && abilities[0])
|
||||
return abilities[0]->getMenuText();
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -140,7 +140,11 @@ void CardGui::Render()
|
||||
|
||||
bool alternate = true;
|
||||
JQuadPtr quad = game? game->getResourceManager()->RetrieveCard(card, CACHE_THUMB):WResourceManager::Instance()->RetrieveCard(card, CACHE_THUMB);
|
||||
|
||||
if(card && card->name != card->model->data->name)
|
||||
{
|
||||
MTGCard * fcard = MTGCollection()->getCardByName(card->name);
|
||||
quad = game->getResourceManager()->RetrieveCard(fcard, CACHE_THUMB);
|
||||
}
|
||||
if (quad.get())
|
||||
alternate = false;
|
||||
else
|
||||
@@ -227,7 +231,6 @@ void CardGui::Render()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (quad)
|
||||
{
|
||||
quad->SetColor(ARGB(static_cast<unsigned char>(actA),255,255,255));
|
||||
@@ -1031,6 +1034,12 @@ void CardGui::RenderBig(MTGCard* card, const Pos& pos)
|
||||
float x = pos.actX;
|
||||
|
||||
JQuadPtr quad = WResourceManager::Instance()->RetrieveCard(card);
|
||||
MTGCardInstance * kcard = dynamic_cast<MTGCardInstance*>(card);
|
||||
if(kcard && kcard->name != kcard->model->data->name)
|
||||
{
|
||||
MTGCard * fcard = MTGCollection()->getCardByName(kcard->name);
|
||||
quad = WResourceManager::Instance()->RetrieveCard(fcard);
|
||||
}
|
||||
if (quad.get())
|
||||
{
|
||||
if (quad->mHeight < quad->mWidth)
|
||||
@@ -1043,7 +1052,6 @@ void CardGui::RenderBig(MTGCard* card, const Pos& pos)
|
||||
RenderCountersBig(card, pos);
|
||||
return;
|
||||
}
|
||||
|
||||
//DebugTrace("Unable to fetch image: " << card->getImageName());
|
||||
|
||||
// If we come here, we do not have the picture.
|
||||
|
||||
@@ -1120,12 +1120,24 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG
|
||||
//When...comes into play, choose one...
|
||||
const string mayKeywords[] = {"may ", "choice "};
|
||||
const bool mayMust[] = { false, true };
|
||||
ManaCost * mayCost = NULL;
|
||||
for (size_t i =0; i < sizeof(mayMust)/sizeof(mayMust[0]); ++i)
|
||||
{
|
||||
if (sWithoutTc.find(mayKeywords[i]) == 0)
|
||||
{
|
||||
string s1 = sWithoutTc.substr(mayKeywords[i].length());
|
||||
MTGAbility * a1 = parseMagicLine(s1, id, spell, card);
|
||||
MTGAbility * a1 = NULL;
|
||||
//may pay a cost for this ability
|
||||
vector<string> splitMayPay = parseBetween(s1, "pay(", ")", true);
|
||||
if(splitMayPay.size())
|
||||
{
|
||||
a1 = parseMagicLine(splitMayPay[2], id, spell, card);
|
||||
mayCost = ManaCost::parseManaCost(splitMayPay[1], NULL, card);
|
||||
}
|
||||
else
|
||||
{
|
||||
a1 = parseMagicLine(s1, id, spell, card);
|
||||
}
|
||||
if (!a1)
|
||||
return NULL;
|
||||
|
||||
@@ -1133,7 +1145,10 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG
|
||||
a1 = NEW GenericTargetAbility(observer, newName,castRestriction,id, card, tc, a1);
|
||||
else
|
||||
a1 = NEW GenericActivatedAbility(observer, newName,castRestriction,id, card, a1, NULL);
|
||||
return NEW MayAbility(observer, id, a1, card,mayMust[i]);
|
||||
MayAbility * mainAbility = NEW MayAbility(observer, id, a1, card,mayMust[i]);
|
||||
if(mayCost)
|
||||
mainAbility->optionalCost = mayCost;
|
||||
return mainAbility;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1147,7 +1162,7 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG
|
||||
|
||||
return NEW GenericInstantAbility(observer, 1, card, (Damageable *) target, a1);
|
||||
}
|
||||
|
||||
|
||||
//Upkeep Cost
|
||||
found = s.find("upcostmulti");
|
||||
if (found != string::npos)
|
||||
@@ -2194,6 +2209,21 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG
|
||||
|
||||
}
|
||||
|
||||
//flip
|
||||
vector<string> splitFlipStat = parseBetween(s, "flip(", ")", true);
|
||||
if(splitFlipStat.size())
|
||||
{
|
||||
string flipStats = "";
|
||||
if(splitFlipStat[1].size())
|
||||
{
|
||||
/*vector<string>FlipStats = split(splitFlipStat[1],'%');*/
|
||||
flipStats = splitFlipStat[1];
|
||||
}
|
||||
MTGAbility * a = NEW AAFlip(observer, id, card, target,flipStats);
|
||||
a->oneShot = 1;
|
||||
return a;
|
||||
}
|
||||
|
||||
//Change Power/Toughness
|
||||
WParsedPT * wppt = NEW WParsedPT(s, spell, card);
|
||||
bool nonstatic = false;
|
||||
@@ -2529,9 +2559,20 @@ int AbilityFactory::abilityEfficiency(MTGAbility * a, Player * p, int mode, Targ
|
||||
int myCards = countCards(abi->getActionTc(), p);
|
||||
int theirCards = countCards(abi->getActionTc(), p->opponent());
|
||||
int efficiency = abilityEfficiency(abi->ability, p, mode, tc);
|
||||
if ( ((myCards < theirCards) && efficiency == BAKA_EFFECT_GOOD) || ((myCards > theirCards) && efficiency == BAKA_EFFECT_BAD) )
|
||||
return efficiency;
|
||||
return -efficiency;
|
||||
if (efficiency == BAKA_EFFECT_GOOD)
|
||||
{
|
||||
myCards < theirCards? efficiency = BAKA_EFFECT_BAD : efficiency = BAKA_EFFECT_GOOD;
|
||||
}
|
||||
else if (efficiency == BAKA_EFFECT_BAD)
|
||||
{
|
||||
myCards >= theirCards? efficiency = BAKA_EFFECT_BAD : efficiency = BAKA_EFFECT_GOOD;
|
||||
}
|
||||
return efficiency;
|
||||
/*this method below leads to too many undesired effects, basically it doesn't work how the original coder thought it would.
|
||||
leaving it for reference to avoid it reaccuring during a refactor.
|
||||
if ( ((myCards <= theirCards) && efficiency == BAKA_EFFECT_GOOD) || ((myCards >= theirCards) && efficiency == BAKA_EFFECT_BAD) )
|
||||
return efficiency;
|
||||
return -efficiency; */
|
||||
}
|
||||
if (AAsLongAs * abi = dynamic_cast<AAsLongAs *>(a))
|
||||
return abilityEfficiency(abi->ability, p, mode, tc);
|
||||
@@ -3572,7 +3613,7 @@ int MTGAbility::addToGame()
|
||||
|
||||
int MTGAbility::removeFromGame()
|
||||
{
|
||||
game->addObserver(this);
|
||||
game->removeObserver(this);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ void MTGCardInstance::initMTGCI()
|
||||
morphed = false;
|
||||
turningOver = false;
|
||||
isMorphed = false;
|
||||
isFlipped = false;
|
||||
isPhased = false;
|
||||
phasedTurn = -1;
|
||||
didattacked = 0;
|
||||
|
||||
Reference in New Issue
Block a user