diff --git a/projects/mtg/bin/Res/rules/mtg.txt b/projects/mtg/bin/Res/rules/mtg.txt index bcb84f441..efb760e7b 100644 --- a/projects/mtg/bin/Res/rules/mtg.txt +++ b/projects/mtg/bin/Res/rules/mtg.txt @@ -8,6 +8,20 @@ auto=shuffle auto=draw:7 auto=@each my draw:draw:1 auto=maxPlay(land)1 -[Player1] -#This is a trick, we put this in player 1's rules so that they most likely won't see that this can be interrupted. Kind of a hack until we can get "noninterruptible" events -auto=@each cleanup:all(*|Battlefield) resetDamage \ No newline at end of file + +#Mana Empties from manapool at the end of each phase +auto=@each untap:removeMana(*) +auto=@each upkeep:removeMana(*) +auto=@each draw:removeMana(*) +auto=@each firstmain:removeMana(*) +auto=@each combatbegins:removeMana(*) +auto=@each attackers:removeMana(*) +auto=@each blockers:removeMana(*) +auto=@each combatdamage:removeMana(*) +auto=@each combatEnds:removeMana(*) +auto=@each secondmain:removeMana(*) +auto=@each end:removeMana(*) +auto=@each cleanup:removeMana(*) + +#reset Creature damage at the cleanup phase +auto=@each cleanup:all(*|myBattlefield) resetDamage \ No newline at end of file diff --git a/projects/mtg/bin/Res/rules/testsuite.txt b/projects/mtg/bin/Res/rules/testsuite.txt index ef9cd793a..044e5f712 100644 --- a/projects/mtg/bin/Res/rules/testsuite.txt +++ b/projects/mtg/bin/Res/rules/testsuite.txt @@ -6,6 +6,20 @@ mode=mtg life:20 auto=@each my draw:draw:1 auto=maxPlay(land)1 -[Player1] -#This is a trick, we put this in player 1's rules so that they most likely won't see that this can be interrupted. Kind of a hack until we can get "noninterruptible" events -auto=@each cleanup:all(*|Battlefield) resetDamage \ No newline at end of file + +#Mana Empties from manapool at the end of each phase +auto=@each untap:removeMana(*) +auto=@each upkeep:removeMana(*) +auto=@each draw:removeMana(*) +auto=@each firstmain:removeMana(*) +auto=@each combatbegins:removeMana(*) +auto=@each attackers:removeMana(*) +auto=@each blockers:removeMana(*) +auto=@each combatdamage:removeMana(*) +auto=@each combatEnds:removeMana(*) +auto=@each secondmain:removeMana(*) +auto=@each end:removeMana(*) +auto=@each cleanup:removeMana(*) + +#reset Creature damage at the cleanup phase +auto=@each cleanup:all(*|myBattlefield) resetDamage \ No newline at end of file diff --git a/projects/mtg/include/AllAbilities.h b/projects/mtg/include/AllAbilities.h index b15d2ffac..e711deeb0 100644 --- a/projects/mtg/include/AllAbilities.h +++ b/projects/mtg/include/AllAbilities.h @@ -4762,6 +4762,20 @@ public: AAShuffle * clone() const; }; +//Remove Mana From ManaPool +class AARemoveMana: public ActivatedAbilityTP +{ +public: + ManaCost * mManaDesc; + bool mRemoveAll; + + AARemoveMana(int _id, MTGCardInstance * card, Targetable * _target, string ManaDesc, int who = TargetChooser::UNSET); + int resolve(); + const char * getMenuText(); + AARemoveMana * clone() const; + ~AARemoveMana(); + +}; //Random Discard class AARandomDiscarder: public ActivatedAbilityTP diff --git a/projects/mtg/include/MTGAbility.h b/projects/mtg/include/MTGAbility.h index 490eb4840..3f6cdff10 100644 --- a/projects/mtg/include/MTGAbility.h +++ b/projects/mtg/include/MTGAbility.h @@ -109,6 +109,7 @@ public: int allowedToAltCast(MTGCardInstance* card, Player* player); int oneShot; int forceDestroy; + bool canBeInterrupted; ManaCost* cost; ManaCost* alternative; ManaCost* BuyBack; diff --git a/projects/mtg/include/ManaCost.h b/projects/mtg/include/ManaCost.h index 14f23be10..56ce2eb55 100644 --- a/projects/mtg/include/ManaCost.h +++ b/projects/mtg/include/ManaCost.h @@ -52,6 +52,7 @@ public: string alternativeName; static ManaCost * parseManaCost(string value, ManaCost * _manacost = NULL, MTGCardInstance * c = NULL); virtual void init(); + virtual void reinit(); void x(); int hasX(); ManaCost(int _cost[], int nb_elems = 1); @@ -85,6 +86,7 @@ public: void randomDiffHybrids(ManaCost * _cost, int diff[]); int add(ManaCost * _cost); int remove(ManaCost * _cost); + int removeAll(int color); int pay (ManaCost * _cost); //return 1 if _cost can be paid with current data, 0 otherwise diff --git a/projects/mtg/include/Rules.h b/projects/mtg/include/Rules.h index 6b4bead31..f463182ee 100644 --- a/projects/mtg/include/Rules.h +++ b/projects/mtg/include/Rules.h @@ -59,6 +59,7 @@ protected: Player * initPlayer(int playerId); MTGDeck * buildDeck(int playerId); int strToGameMode(string s); + bool postUpdateInitDone; public: enum { @@ -86,6 +87,8 @@ public: bool canChooseDeck(); //True if the players get to select their decks, false if the decks are automatically generated by the mode void addExtraRules(); void initGame(); + //second part of the initialization, needs to happen after the first update call + void postUpdateInit(); void cleanup(); vector extraRules; RulesState initState; diff --git a/projects/mtg/src/AllAbilities.cpp b/projects/mtg/src/AllAbilities.cpp index c8486e3f9..80df19d08 100644 --- a/projects/mtg/src/AllAbilities.cpp +++ b/projects/mtg/src/AllAbilities.cpp @@ -1761,6 +1761,81 @@ AAShuffle * AAShuffle::clone() const return a; } +// Remove Mana From ManaPool +AARemoveMana::AARemoveMana(int _id, MTGCardInstance * card, Targetable * _target, string manaDesc, int who) : + ActivatedAbilityTP(_id, card, _target, NULL, who) +{ + if (!manaDesc.size()) + { + DebugTrace("ALL_ABILITIES: AARemoveMana ctor error"); + return; + } + mRemoveAll = (manaDesc[0] == '*'); + if (mRemoveAll) + manaDesc = manaDesc.substr(1); + + mManaDesc = (manaDesc.size()) ? ManaCost::parseManaCost(manaDesc) : NULL; + +} + +int AARemoveMana::resolve() +{ + Targetable * _target = getTarget(); + Player * player; + if (_target) + { + if (_target->typeAsTarget() == TARGET_CARD) + { + player = ((MTGCardInstance *) _target)->controller(); + } + else + { + player = (Player *) _target; + } + ManaPool * manaPool = player->getManaPool(); + if (mRemoveAll) + { + if (mManaDesc) // Remove all mana Matching a description + { + for (unsigned int i = 0; i < Constants::MTG_NB_COLORS; i++) + { + if (mManaDesc->hasColor(i)) + manaPool->removeAll(i); + } + } + else //Remove all mana + { + manaPool->init(); + } + } + else //remove a "standard" mana Description + { + ((ManaCost *)manaPool)->remove(mManaDesc); //why do I have to cast here? + } + } + return 1; +} + +const char * AARemoveMana::getMenuText() +{ + if (mRemoveAll && !mManaDesc) + return "Empty Manapool"; + return "Remove Mana"; +} + +AARemoveMana * AARemoveMana::clone() const +{ + AARemoveMana * a = NEW AARemoveMana(*this); + a->mManaDesc = mManaDesc ? NEW ManaCost(mManaDesc) : NULL; + a->isClone = 1; + return a; +} + +AARemoveMana::~AARemoveMana() +{ + SAFE_DELETE(mManaDesc); +} + //Tapper AATapper::AATapper(int id, MTGCardInstance * card, MTGCardInstance * _target, ManaCost * _cost) : ActivatedAbility(id, card, _cost, 0) diff --git a/projects/mtg/src/GameObserver.cpp b/projects/mtg/src/GameObserver.cpp index cbc94f9f7..978f7fdeb 100644 --- a/projects/mtg/src/GameObserver.cpp +++ b/projects/mtg/src/GameObserver.cpp @@ -122,9 +122,6 @@ void GameObserver::nextGamePhase() return nextGamePhase(); } - for (int i = 0; i < 2; ++i) - players[i]->getManaPool()->init(); - if (currentGamePhase == Constants::MTG_PHASE_AFTER_EOT) { //Auto Hand cleaning, in case the player didn't do it himself @@ -371,16 +368,7 @@ void GameObserver::gameStateBasedEffects() //check land playability at start; as we want this effect to happen reguardless of unresolved //effects or menus actions for (int i = 0; i < 2; i++) - { - if(players[i]->poisonCount > 0) - { - players[i]->isPoisoned = true; - } - else - { - players[i]->isPoisoned = false; - } - } + players[i]->isPoisoned = (players[i]->poisonCount > 0); if (mLayers->stackLayer()->count(0, NOT_RESOLVED) != 0) return; if (mLayers->actionLayer()->menuObject) @@ -939,7 +927,7 @@ int GameObserver::cardClick(MTGCardInstance * card, Targetable * object) { //card played as normal, alternative cost, buyback, flashback, retrace. - //the varible "paymenttype = int" only serves one purpose, to tell this bug fix what menu item you clicked on... + //the variable "paymenttype = int" only serves one purpose, to tell this bug fix what menu item you clicked on... // all alternative cost or play methods suffered from the fix because if the card contained "target=" // it would automatically force the play method to putinplayrule...even charge you the original mana cost. diff --git a/projects/mtg/src/GameStateDuel.cpp b/projects/mtg/src/GameStateDuel.cpp index f90e702f9..54a0b0abd 100644 --- a/projects/mtg/src/GameStateDuel.cpp +++ b/projects/mtg/src/GameStateDuel.cpp @@ -441,6 +441,9 @@ void GameStateDuel::Update(float dt) GameApp::playMusic(musictrack); } game->Update(dt); + //run a "post update" init call in the rules. This is for things such as Manapool, which gets emptied in the update + // That's mostly because of a legacy bug, where we use the update sequence for some things when we should use events (such as phase changes) + mParent->rules->postUpdateInit(); if (game->gameOver) { if (game->players[1]->playMode != Player::MODE_TEST_SUITE) credits->compute(game->players[0], game->players[1], mParent); diff --git a/projects/mtg/src/MTGAbility.cpp b/projects/mtg/src/MTGAbility.cpp index a3d0997f9..d593f2ae4 100644 --- a/projects/mtg/src/MTGAbility.cpp +++ b/projects/mtg/src/MTGAbility.cpp @@ -1804,6 +1804,16 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG return a; } + //Remove Mana from ManaPool + vector splitRemove = parseBetween(s, "removemana(", ")"); + if (splitRemove.size()) + { + Targetable * t = spell? spell->getNextTarget() : NULL; + MTGAbility *a = NEW AARemoveMana(id, card, t, splitRemove[1], who); + a->oneShot = 1; + return a; + } + //Cast/Play Restrictions for (size_t i = 0; i < kMaxCastKeywordsCount; ++i) { @@ -2400,7 +2410,6 @@ int AbilityFactory::computeX(Spell * spell, MTGCardInstance * card) int AbilityFactory::getAbilities(vector * v, Spell * spell, MTGCardInstance * card, int id, MTGGameZone * dest) { - if (!card && spell) card = spell->source; if (!card) @@ -2468,12 +2477,7 @@ int AbilityFactory::getAbilities(vector * v, Spell * spell, MTGCar string cre = "Creature"; card->setType(cre.c_str()); card->basicAbilities.reset(); - card->getManaCost()->remove(0,100); - card->getManaCost()->remove(1,100); - card->getManaCost()->remove(2,100); - card->getManaCost()->remove(3,100); - card->getManaCost()->remove(4,100); - card->getManaCost()->remove(5,100); + card->getManaCost()->reinit(); } else if(card && !card->morphed && card->turningOver) { @@ -3241,6 +3245,7 @@ MTGAbility::MTGAbility(int id, MTGCardInstance * card) : cost = NULL; forceDestroy = 0; oneShot = 0; + canBeInterrupted = true; } MTGAbility::MTGAbility(int id, MTGCardInstance * _source, Targetable * _target) : @@ -3253,6 +3258,7 @@ MTGAbility::MTGAbility(int id, MTGCardInstance * _source, Targetable * _target) cost = NULL; forceDestroy = 0; oneShot = 0; + canBeInterrupted = true; } int MTGAbility::stillInUse(MTGCardInstance * card) @@ -3307,7 +3313,10 @@ GameObserver * g=g->GetInstance(); int MTGAbility::fireAbility() { - game->mLayers->stackLayer()->addAbility(this); + if (canBeInterrupted) + game->mLayers->stackLayer()->addAbility(this); + else + resolve(); return 1; } diff --git a/projects/mtg/src/ManaCost.cpp b/projects/mtg/src/ManaCost.cpp index 96a3a3ca5..29a263284 100644 --- a/projects/mtg/src/ManaCost.cpp +++ b/projects/mtg/src/ManaCost.cpp @@ -365,6 +365,23 @@ void ManaCost::init() suspend = NULL; } +void ManaCost::reinit() +{ + int i; + for (i = 0; i <= Constants::MTG_NB_COLORS; i++) + { + cost[i] = 0; + } + SAFE_DELETE(extraCosts); + SAFE_DELETE(kicker); + SAFE_DELETE(alternative); + SAFE_DELETE(BuyBack); + SAFE_DELETE(FlashBack); + SAFE_DELETE(Retrace); + SAFE_DELETE(morph); + SAFE_DELETE(suspend); +} + void ManaCost::copy(ManaCost * _manaCost) { if (!_manaCost) @@ -475,11 +492,9 @@ int ManaCost::getConvertedCost() int ManaCost::remove(int color, int value) { - cost[color] -= value; - if (cost[color] < 0) - { - cost[color] = 0; - } + assert (value >= 0); + int toRemove = min(cost[color], value); + cost[color] -= toRemove; return 1; } @@ -511,15 +526,19 @@ int ManaCost::remove(ManaCost * _cost) return 0; for (unsigned int i = 0; i < Constants::MTG_NB_COLORS; i++) { - for(int c = 0;c < _cost->getCost(i);c++) - { - if(cost[i])//remove 1 at a time to avoid dipping into negitive cost. - cost[i] -= 1; - } + int toRemove = min(cost[i], _cost->getCost(i)); //we don't want to be negative + cost[i] -= toRemove; + assert(cost[i] >= 0); } return 1; } +int ManaCost::removeAll(int color) +{ + cost[color] = 0; + return 1; +} + int ManaCost::addHybrid(int c1, int v1, int c2, int v2) { hybrids.push_back(ManaCostHybrid(c1, v1, c2, v2)); diff --git a/projects/mtg/src/Rules.cpp b/projects/mtg/src/Rules.cpp index 0c8c62911..1ce83e774 100644 --- a/projects/mtg/src/Rules.cpp +++ b/projects/mtg/src/Rules.cpp @@ -255,6 +255,8 @@ void Rules::addExtraRules() if (a) { + //We make those non interruptible, so that they don't appear on the player's stack + a->canBeInterrupted = false; if (a->oneShot) { if (((p->isAI() && p->playMode @@ -463,7 +465,6 @@ void Rules::initGame() p->poisonCount = initState.playerData[i].poisonCount; p->damageCount = initState.playerData[i].damageCount; p->preventable = initState.playerData[i].preventable; - p->getManaPool()->copy(initState.playerData[i].manapool); if (initState.playerData[i].avatar.size()) { p->loadAvatar(initState.playerData[i].avatar); @@ -504,9 +505,23 @@ void Rules::initGame() } } addExtraRules(); + + postUpdateInitDone = false; DebugTrace("RULES Init Game Done !\n"); } +//This function has all iitialization that can't be done in the "real" init function, +// because the first update call messes things up. +//It's a hack, ideally, the first update call shouldn't mess the init parameters... +void Rules::postUpdateInit() +{ + if (postUpdateInitDone) + return; + for (int i = 0; i < 2; ++ i) + GameObserver::GetInstance()->players[i]->getManaPool()->copy(initState.playerData[i].manapool); + postUpdateInitDone = true; +} + void RulesPlayerZone::cleanup() { cards.clear(); @@ -546,6 +561,7 @@ Rules::Rules(string _bg) unlockOption = INVALID_OPTION; hidden = false; filename = ""; + postUpdateInitDone = false; } bool Rules::canChooseDeck() diff --git a/projects/mtg/src/TestSuiteAI.cpp b/projects/mtg/src/TestSuiteAI.cpp index f912710b1..31985a136 100644 --- a/projects/mtg/src/TestSuiteAI.cpp +++ b/projects/mtg/src/TestSuiteAI.cpp @@ -78,6 +78,14 @@ int TestSuiteAI::Act(float dt) { GameObserver * g = GameObserver::GetInstance(); g->gameOver = NULL; // Prevent draw rule from losing the game + + //Last bits of initialization require to be done here, after the first "update" call of the game + if (suite->currentAction == 0) + { + for (int i = 0; i < 2; ++ i) + g->players[i]->getManaPool()->copy(suite->initState.playerData[i].manapool); + } + if (playMode == MODE_AI && suite->aiMaxCalls) { suite->aiMaxCalls--; @@ -87,6 +95,7 @@ int TestSuiteAI::Act(float dt) if (playMode == MODE_HUMAN) { g->mLayers->CheckUserInput(0); + suite->currentAction++; //hack to avoid repeating the initialization of manapool return 1; } @@ -96,7 +105,6 @@ int TestSuiteAI::Act(float dt) string action = suite->getNextAction(); g->mLayers->stackLayer()->Dump(); - // DamageResolverLayer * drl = g->mLayers->combatLayer(); DebugTrace("TESTSUITE command: " << action); if (g->mLayers->stackLayer()->askIfWishesToInterrupt == this) @@ -386,7 +394,6 @@ void TestSuite::initGame() AIPlayer * p = (AIPlayer *) (g->players[i]); p->forceBestAbilityUse = forceAbility; p->life = initState.playerData[i].life; - p->getManaPool()->copy(initState.playerData[i].manapool); MTGGameZone * playerZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay }; for (int j = 0; j < 4; j++) {