From 0b9ff076e6b8d7d4514c54fdfa1c5662f39691fa Mon Sep 17 00:00:00 2001 From: "wagic.the.homebrew" Date: Tue, 3 May 2011 11:59:27 +0000 Subject: [PATCH] Please update your rules folder - "Manapool empties at the end of each step" becomes an ability, and was moved into the external rules file. "removemana(*) to remove all, removemana(*{G}) to remove all green, removemana(*{G}{B}{R}) to remove all green black red, removemana({G}{G}{B}{U}) (no "*") to remove a specific value. - Added a possibility to make abilities non interruptible. With little work, this could be added to the parser if needed. Please use with care, let's discuss what is an acceptable usage of this now functionality, if needed. --- projects/mtg/bin/Res/rules/mtg.txt | 20 ++++++- projects/mtg/bin/Res/rules/testsuite.txt | 20 ++++++- projects/mtg/include/AllAbilities.h | 14 +++++ projects/mtg/include/MTGAbility.h | 1 + projects/mtg/include/ManaCost.h | 2 + projects/mtg/include/Rules.h | 3 + projects/mtg/src/AllAbilities.cpp | 75 ++++++++++++++++++++++++ projects/mtg/src/GameObserver.cpp | 16 +---- projects/mtg/src/GameStateDuel.cpp | 3 + projects/mtg/src/MTGAbility.cpp | 25 +++++--- projects/mtg/src/ManaCost.cpp | 39 ++++++++---- projects/mtg/src/Rules.cpp | 18 +++++- projects/mtg/src/TestSuiteAI.cpp | 11 +++- 13 files changed, 206 insertions(+), 41 deletions(-) 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++) {