/* The Action Stack contains all information for Game Events that can be interrupted (Interruptible) */ #include "PrecompiledHeader.h" #include "ActionStack.h" #include "CardGui.h" #include "Damage.h" #include "GameObserver.h" #include "ManaCost.h" #include "MTGAbility.h" #include "Subtypes.h" #include "TargetChooser.h" #include "Translate.h" #include "WResourceManager.h" #include "ModRules.h" #include "AllAbilities.h" #include "CardSelector.h" #include namespace { float kGamepadIconSize = 0.5f; std::string kInterruptMessageString("Interrupt?"); std::string kInterruptString(": Interrupt"); std::string kNoString(": No"); std::string kNoToAllString(": No To All"); static const float kIconVerticalOffset = 24; } /* NextGamePhase requested by user */ int NextGamePhase::resolve() { observer->userRequestNextGamePhase(false, false); return 1; } const string NextGamePhase::getDisplayName() const { std::ostringstream stream; stream << "NextGamePhase. (Current phase is: " << observer->getCurrentGamePhaseName() << ")"; return stream.str(); } void NextGamePhase::Render() { WFont * mFont = observer->getResourceManager()->GetWFont(Fonts::MAIN_FONT); mFont->SetBase(0); mFont->SetScale(1.3f); char buffer[200]; int playerId = 1; if (observer->currentActionPlayer == observer->players[1]) playerId = 2; sprintf(buffer, "%s %i : %s", _("Player").c_str(), playerId, observer->getNextGamePhaseName().c_str()); mFont->DrawString(buffer, x + 15, y+10, JGETEXT_LEFT); mFont->SetScale(DEFAULT_MAIN_FONT_SCALE); } NextGamePhase::NextGamePhase(GameObserver* observer, int id) : Interruptible(observer, id) { mHeight = 40; type = ACTION_NEXTGAMEPHASE; } ostream& NextGamePhase::toString(ostream& out) const { out << "NextGamePhase ::: "; return out; } const string Interruptible::getDisplayName() const { return typeid(*this).name(); } float Interruptible::GetVerticalTextOffset() const { static const float kTextVerticalOffset = (mHeight - observer->getResourceManager()->GetWFont(Fonts::MAIN_FONT)->GetHeight()) / 2; return kTextVerticalOffset; } void Interruptible::Render(MTGCardInstance * source, JQuad * targetQuad, string alt1, string alt2, string action, bool bigQuad) { WFont * mFont = observer->getResourceManager()->GetWFont(Fonts::MAIN_FONT); mFont->SetColor(ARGB(255,255,255,255)); mFont->SetScale(DEFAULT_MAIN_FONT_SCALE); JRenderer * renderer = JRenderer::GetInstance(); if (!targetQuad) { /*if(source->controller()->isHuman() && source->controller()->opponent()->isAI() && !alt2.size() && _(action).c_str() == source->name) mFont->DrawString("You play ", x + 35, y-15 + GetVerticalTextOffset(), JGETEXT_LEFT); else if(source->controller()->isAI() && source->controller()->opponent()->isHuman() && !alt2.size() && _(action).c_str() == source->name) mFont->DrawString("Opponent plays ", x + 35, y-15 + GetVerticalTextOffset(), JGETEXT_LEFT);*/ mFont->DrawString(_(action).c_str(), x + 35, y + GetVerticalTextOffset(), JGETEXT_LEFT); } else { renderer->FillRect(x-2,y-16 + GetVerticalTextOffset(), 73, 43, ARGB(235,10,10,10)); /*if(source->controller()->isHuman() && source->controller()->opponent()->isAI()) renderer->DrawRect(x-2,y-16 + GetVerticalTextOffset(), 73, 43, ARGB(245,0,255,0)); else renderer->DrawRect(x-2,y-16 + GetVerticalTextOffset(), 73, 43, ARGB(245,255,0,0));*/ mFont->DrawString(">", x + 32, y + GetVerticalTextOffset(), JGETEXT_LEFT); mFont->DrawString(_(action).c_str(), x + 75, y + GetVerticalTextOffset(), JGETEXT_LEFT); } JQuadPtr quad = observer->getResourceManager()->RetrieveCard(source, CACHE_THUMB); if (!quad.get()) quad = CardGui::AlternateThumbQuad(source); if (quad.get()) { quad->SetColor(ARGB(255,255,255,255)); float scale = mHeight / quad->mHeight; renderer->RenderQuad(quad.get(), x + (quad->mWidth * scale / 2), y + (quad->mHeight * scale / 2), 0, scale, scale); } else if (alt1.size()) { mFont->DrawString(_(alt1).c_str(), x, y + GetVerticalTextOffset()); } if (bigQuad) { Pos pos = Pos(CardGui::BigWidth / 2, CardGui::BigHeight / 2 - 10, 1.0, 0.0, 220); CardGui::DrawCard(source, pos, observer->getCardSelector()->GetDrawMode()); } if (targetQuad) { float backupX = targetQuad->mHotSpotX; float backupY = targetQuad->mHotSpotY; targetQuad->SetColor(ARGB(255,255,255,255)); targetQuad->SetHotSpot(targetQuad->mWidth / 2, targetQuad->mHeight / 2); float scale = mHeight / targetQuad->mHeight; renderer->RenderQuad(targetQuad, x + 55, y + ((mHeight - targetQuad->mHeight) / 2) + targetQuad->mHotSpotY, 0, scale, scale); targetQuad->SetHotSpot(backupX, backupY); } else if (alt2.size()) { mFont->DrawString(_(alt2).c_str(), x + 35, y+15 + GetVerticalTextOffset()); } } /* Ability */ int StackAbility::resolve() { return (ability->resolve()); } void StackAbility::Render() { string action = ability->getMenuText(); MTGCardInstance * source = ability->source; string alt1 = source->getName(); Targetable * _target = ability->target; if (ability->getActionTc()) { Targetable * t = ability->getActionTc()->getNextTarget(); if (t) _target = t; } Damageable * target = NULL; if (_target != ability->source && (dynamic_cast(_target) || dynamic_cast(_target))) { target = (Damageable *) _target; } JQuadPtr quad; string alt2 = ""; if (target) { quad = target->getIcon(); if (target->type_as_damageable == Damageable::DAMAGEABLE_MTGCARDINSTANCE) { alt2 = ((MTGCardInstance *) target)->name; } } Interruptible::Render(source, quad.get(), alt1, alt2, action); } StackAbility::StackAbility(GameObserver* observer, int id, MTGAbility * _ability) : Interruptible(observer, id), ability(_ability) { type = ACTION_ABILITY; } ostream& StackAbility::toString(ostream& out) const { out << "StackAbility ::: ability : " << ability; return out; } const string StackAbility::getDisplayName() const { std::ostringstream stream; if(ability->source) stream << "StackAbility. (Source: " << ability->source->getDisplayName() << ")"; else stream << "StackAbility. (Source: " << ability->getMenuText() << ")"; return stream.str(); } /* Spell Cast */ Spell::Spell(GameObserver* observer, MTGCardInstance * _source) : Interruptible(observer, 0) { source = _source; mHeight = 40; type = ACTION_SPELL; cost = NEW ManaCost(); cost->extraCosts = NULL; tc = NULL; from = _source->getCurrentZone(); payResult = ManaCost::MANA_UNPAID; source->castMethod = Constants::NOT_CAST; } Spell::Spell(GameObserver* observer, int id, MTGCardInstance * _source, TargetChooser * tc, ManaCost * _cost, int payResult) : Interruptible(observer, id), tc(tc), cost(_cost), payResult(payResult) { if (!cost) { cost = NEW ManaCost(); cost->extraCosts = NULL; } source = _source; mHeight = 40; type = ACTION_SPELL; from = _source->getCurrentZone(); _source->backupTargets.clear(); if (tc) { Targetable* t = NULL; for(size_t i = 0;i < tc->getNbTargets();i++) { t = tc->getNextTarget(t); _source->backupTargets.push_back(t); } } // fill information on how the card came into this zone. Right now the quickest way is to do it here, based on how the mana was paid... switch(payResult) { case ManaCost::MANA_UNPAID: source->castMethod = Constants::NOT_CAST; break; case ManaCost::MANA_PAID: case ManaCost::MANA_PAID_WITH_KICKER: source->castMethod = Constants::CAST_NORMALLY; break; default: source->castMethod = Constants::CAST_ALTERNATE; break; } } int Spell::computeX(MTGCardInstance * card) { ManaCost * c = cost->Diff(card->getManaCost()); int x = c->getCost(Constants::NB_Colors); delete c; return x; } bool Spell::FullfilledAlternateCost(const int &costType) { bool hasFullfilledAlternateCost = false; switch (costType) { case ManaCost::MANA_UNPAID: hasFullfilledAlternateCost = (payResult == ManaCost::MANA_UNPAID); break; case ManaCost::MANA_PAID: hasFullfilledAlternateCost = (payResult == ManaCost::MANA_PAID); break; case ManaCost::MANA_PAID_WITH_KICKER: hasFullfilledAlternateCost = (payResult == ManaCost::MANA_PAID_WITH_KICKER); break; case ManaCost::MANA_PAID_WITH_ALTERNATIVE: hasFullfilledAlternateCost = (payResult == ManaCost::MANA_PAID_WITH_ALTERNATIVE); break; case ManaCost::MANA_PAID_WITH_BUYBACK: hasFullfilledAlternateCost = (payResult == ManaCost::MANA_PAID_WITH_BUYBACK); break; case ManaCost::MANA_PAID_WITH_FLASHBACK: hasFullfilledAlternateCost = (payResult == ManaCost::MANA_PAID_WITH_FLASHBACK); break; case ManaCost::MANA_PAID_WITH_RETRACE: hasFullfilledAlternateCost = (payResult == ManaCost::MANA_PAID_WITH_RETRACE); break; case ManaCost::MANA_PAID_WITH_SUSPEND: hasFullfilledAlternateCost = (payResult == ManaCost::MANA_PAID_WITH_SUSPEND); break; case ManaCost::MANA_PAID_WITH_OVERLOAD: hasFullfilledAlternateCost = (payResult == ManaCost::MANA_PAID_WITH_OVERLOAD); break; case ManaCost::MANA_PAID_WITH_BESTOW: hasFullfilledAlternateCost = (payResult == ManaCost::MANA_PAID_WITH_BESTOW); break; } return hasFullfilledAlternateCost; } const string Spell::getDisplayName() const { return source->getName(); } Spell::~Spell() { SAFE_DELETE(cost); SAFE_DELETE(tc); } int Spell::resolve() { MTGCardInstance * oldStored = source->storedCard; Player * playerT = source->playerTarget; if (!source->hasType(Subtypes::TYPE_INSTANT) && !source->hasType(Subtypes::TYPE_SORCERY) && source->name.size()) { Player * p = source->controller(); int castMethod = source->castMethod; vectorbackupTgt = source->backupTargets; if(from != source->currentZone) { from = source->currentZone;//this happens when casting spells that belong to another player or casting a copy of someone elses spell. } source = p->game->putInZone(source, from, p->game->battlefield); // We need to get the information about the cast method on both the card in the stack AND the card in play, //so we copy it from the previous card (in the stack) to the new one (in play). source->castMethod = castMethod; source->backupTargets = backupTgt; from = p->game->battlefield; } source->playerTarget = playerT; source->storedCard = oldStored; //Play SFX if (options[Options::SFXVOLUME].number > 0) { if(observer->getResourceManager()) observer->getResourceManager()->PlaySample(source->getSample()); } if(this->cost) { source->getManaCost()->setManaUsedToCast(NEW ManaCost(this->cost)); } AbilityFactory af(observer); af.addAbilities(observer->mLayers->actionLayer()->getMaxId(), this); return 1; } MTGCardInstance * Spell::getNextCardTarget(MTGCardInstance * previous) { if (!tc) return NULL; return tc->getNextCardTarget(previous); } Player * Spell::getNextPlayerTarget(Player * previous) { if (!tc) return NULL; return tc->getNextPlayerTarget(previous); } Damageable * Spell::getNextDamageableTarget(Damageable * previous) { if (!tc) return NULL; return tc->getNextDamageableTarget(previous); } Interruptible * Spell::getNextInterruptible(Interruptible * previous, int type) { if (!tc) return NULL; return tc->getNextInterruptible(previous, type); } Spell * Spell::getNextSpellTarget(Spell * previous) { if (!tc) return NULL; return tc->getNextSpellTarget(previous); } Damage * Spell::getNextDamageTarget(Damage * previous) { if (!tc) return NULL; return tc->getNextDamageTarget(previous); } Targetable * Spell::getNextTarget(Targetable * previous) { if (!tc) return NULL; return tc->getNextTarget(previous); } int Spell::getNbTargets() { if (!tc) return 0; return (int) (tc->getNbTargets()); } void Spell::Render() { string action = source->getName(); string alt1 = ""; string alt2 = ""; Damageable * target = getNextDamageableTarget(); JQuadPtr quad; if (target) { quad = target->getIcon(); if (target->type_as_damageable == Damageable::DAMAGEABLE_MTGCARDINSTANCE) { alt2 = ((MTGCardInstance *) target)->name; } } Interruptible::Render(source, quad.get(), alt1, alt2, action, true); } ostream& Spell::toString(ostream& out) const { out << "Spell ::: cost : " << cost; return out; } /* Put a card in graveyard */ PutInGraveyard::PutInGraveyard(GameObserver* observer, int id, MTGCardInstance * _card) : Interruptible(observer, id) { card = _card; removeFromGame = 0; type = ACTION_PUTINGRAVEYARD; } int PutInGraveyard::resolve() { MTGGameZone * zone = card->getCurrentZone(); if (card->basicAbilities[(int)Constants::EXILEDEATH]) { card->owner->game->putInZone(card, zone, card->owner->game->exile); return 1; } if (zone == observer->players[0]->game->inPlay || zone == observer->players[1]->game->inPlay) { card->owner->game->putInZone(card, zone, card->owner->game->graveyard); return 1; } return 0; } void PutInGraveyard::Render() { WFont * mFont = observer->getResourceManager()->GetWFont(Fonts::MAIN_FONT); mFont->SetBase(0); mFont->SetScale(DEFAULT_MAIN_FONT_SCALE); if (!removeFromGame) { mFont->DrawString(_("goes to graveyard").c_str(), x + 30, y, JGETEXT_LEFT); } else { mFont->DrawString(_("is exiled").c_str(), x + 30, y, JGETEXT_LEFT); } JRenderer * renderer = JRenderer::GetInstance(); JQuadPtr quad = observer->getResourceManager()->RetrieveCard(card, CACHE_THUMB); if (quad.get()) { quad->SetColor(ARGB(255,255,255,255)); float scale = 30 / quad->mHeight; renderer->RenderQuad(quad.get(), x, y, 0, scale, scale); } else { mFont->DrawString(_(card->name).c_str(), x, y - 15); } } ostream& PutInGraveyard::toString(ostream& out) const { out << "PutInGraveyard ::: removeFromGame : " << removeFromGame; return out; } /* Draw a Card */ DrawAction::DrawAction(GameObserver* observer, int id, Player * _player, int _nbcards) : Interruptible(observer, id), nbcards(_nbcards), player(_player) { } int DrawAction::resolve() { for (int i = 0; i < nbcards; i++) { player->game->drawFromLibrary(); } return 1; } void DrawAction::Render() { WFont * mFont = observer->getResourceManager()->GetWFont(Fonts::MAIN_FONT); mFont->SetBase(0); mFont->SetScale(DEFAULT_MAIN_FONT_SCALE); char buffer[200]; int playerId = 1; if (player == observer->players[1]) playerId = 2; sprintf(buffer, _("Player %i draws %i card").c_str(), playerId, nbcards); mFont->DrawString(buffer, x + 35, y + GetVerticalTextOffset(), JGETEXT_LEFT); } ostream& DrawAction::toString(ostream& out) const { out << "DrawAction ::: nbcards : " << nbcards << " ; player : " << player; return out; } ////// LifeAction::LifeAction(GameObserver* observer, int id, Damageable * _target, int amount) : Interruptible(observer, id), amount(amount),target(_target) { } int LifeAction::resolve() { target->life += amount; return 1; } void LifeAction::Render() { WFont * mFont = observer->getResourceManager()->GetWFont(Fonts::MAIN_FONT); mFont->SetBase(0); mFont->SetScale(DEFAULT_MAIN_FONT_SCALE); char buffer[200]; if(amount > 0) sprintf(buffer, _("Player gains %i life").c_str(), amount); else if(amount < 0) sprintf(buffer, _("Player loses %i life").c_str(), amount); else sprintf(buffer, _("Nothing happened").c_str(), amount); mFont->DrawString(buffer, x + 20, y, JGETEXT_LEFT); } ostream& LifeAction::toString(ostream& out) const { out << "LifeAction ::: amount : " << amount << " ; target : " << target; return out; } /* The Action Stack itself */ int ActionStack::addPutInGraveyard(MTGCardInstance * card) { PutInGraveyard * death = NEW PutInGraveyard(observer, mObjects.size(), card); addAction(death); return 1; } int ActionStack::addAbility(MTGAbility * ability) { StackAbility * stackAbility = NEW StackAbility(observer, mObjects.size(), ability); int result = addAction(stackAbility); if (!observer->players[0]->isAI() && ability->source->controller() == observer->players[0] && 0 == options[Options::INTERRUPTMYABILITIES].number) interruptDecision[0] = DONT_INTERRUPT; return result; } int ActionStack::addDraw(Player * player, int nb_cards) { DrawAction * draw = NEW DrawAction(observer, mObjects.size(), player, nb_cards); addAction(draw); return 1; } int ActionStack::addLife(Damageable * _target, int amount) { LifeAction * life = NEW LifeAction(observer, mObjects.size(), _target, amount); addAction(life); return 1; } int ActionStack::addDamage(MTGCardInstance * _source, Damageable * _target, int _damage) { Damage * damage = NEW Damage(observer, _source, _target, _damage); addAction(damage); _source->thatmuch = _damage; _target->thatmuch = _damage; return 1; } int ActionStack::AddNextGamePhase() { if (getNext(NULL, NOT_RESOLVED)) return 0; NextGamePhase * next = NEW NextGamePhase(observer, mObjects.size()); addAction(next); int playerId = (observer->currentActionPlayer == observer->players[1]) ? 1 : 0; interruptDecision[playerId] = DONT_INTERRUPT; return 1; } int ActionStack::AddNextCombatStep() { if (getNext(NULL, NOT_RESOLVED)) return 0; NextGamePhase * next = NEW NextGamePhase(observer, mObjects.size()); addAction(next); return 1; } int ActionStack::setIsInterrupting(Player * player, bool log) { askIfWishesToInterrupt = NULL; if (!gModRules.game.canInterrupt()) { cancelInterruptOffer(DONT_INTERRUPT, log); return 0; } //Is it a valid interruption request, or is uninterruptible stuff going on in the game? if (observer->getCurrentTargetChooser()) { DebugTrace("ActionStack: WARNING - We were asked to interrupt, During Targetchoosing" << endl << "source: " << (observer->getCurrentTargetChooser()->source ? observer->getCurrentTargetChooser()->source->name : "None" ) << endl ); return 0; } int playerId = (player == observer->players[1]) ? 1 : 0; interruptDecision[playerId] = INTERRUPT; observer->isInterrupting = player; if(log) observer->logAction(player, "yes"); return 1; } int ActionStack::addAction(Interruptible * action) { for (int i = 0; i < 2; i++) { interruptDecision[i] = NOT_DECIDED; } Add(action); lastActionController = observer->currentlyActing(); DebugTrace("Action added to stack: " << action->getDisplayName()); return 1; } Spell * ActionStack::addSpell(MTGCardInstance * _source, TargetChooser * tc, ManaCost * mana, int payResult, int storm) { DebugTrace("ACTIONSTACK Add spell"); if (storm > 0) { mana = NULL; } Spell * spell = NEW Spell(observer, mObjects.size(), _source, tc, mana, payResult); addAction(spell); if (!observer->players[0]->isAI() && _source->controller() == observer->players[0] && 0 == options[Options::INTERRUPTMYSPELLS].number) interruptDecision[0] = DONT_INTERRUPT; return spell; } Interruptible * ActionStack::getAt(int id) { if (id < 0) id = mObjects.size() + id; if (id > (int)(mObjects.size()) - 1 || id < 0) return NULL; return (Interruptible *) mObjects[id]; } ActionStack::ActionStack(GameObserver* game) : GuiLayer(game), currentTutorial(0) { for (int i = 0; i < 2; i++) interruptDecision[i] = NOT_DECIDED; askIfWishesToInterrupt = NULL; timer = -1; currentState = -1; mode = ACTIONSTACK_STANDARD; checked = 0; lastActionController = NULL; if(!observer->getResourceManager()) return; for (int i = 0; i < 8; ++i) { std::ostringstream stream; stream << "iconspsp" << i; pspIcons[i] = observer->getResourceManager()->RetrieveQuad("iconspsp.png", (float) i * 32, 0, 32, 32, stream.str(), RETRIEVE_MANAGE); pspIcons[i]->SetHotSpot(16, 16); } } int ActionStack::has(MTGAbility * ability) { for (size_t i = 0; i < mObjects.size(); i++) { if (((Interruptible *) mObjects[i])->type == ACTION_ABILITY) { StackAbility * action = ((StackAbility *) mObjects[i]); if (action->state == NOT_RESOLVED && action->ability == ability) return 1; } } return 0; } int ActionStack::has(Interruptible * action) { for (size_t i = 0; i < mObjects.size(); i++) { if (mObjects[i] == action) return 1; } return 0; } int ActionStack::resolve() { Interruptible * action = getLatest(NOT_RESOLVED); if (!action) return 0; DebugTrace("Resolving Action on stack: " << action->getDisplayName()); if (action->resolve()) { action->state = RESOLVED_OK; } else { action->state = RESOLVED_NOK; } if (action->type == ACTION_DAMAGE) ((Damage *) action)->target->afterDamage(); if (!getNext(NULL, NOT_RESOLVED)) { for (int i = 0; i < 2; i++) { if (interruptDecision[i] != 2) interruptDecision[i] = NOT_DECIDED; } } else { for (int i = 0; i < 2; i++) { if (interruptDecision[i] != DONT_INTERRUPT_ALL) interruptDecision[i] = NOT_DECIDED; } } lastActionController = NULL; return 1; } Interruptible * ActionStack::getPrevious(Interruptible * next, int type, int state, int display) { int n = getPreviousIndex(next, type, state, display); if (n == -1) return NULL; return ((Interruptible *) mObjects[n]); } int ActionStack::getPreviousIndex(Interruptible * next, int type, int state, int display) { int found = 0; if (!next) found = 1; for (int i = (int)(mObjects.size()) - 1; i >= 0; i--) { Interruptible * current = (Interruptible *) mObjects[i]; if (found && (type == 0 || current->type == type) && (state == 0 || current->state == state) && (display == -1 || current->display == display)) { return i; } if (current == next) found = 1; } if (!found) return getPreviousIndex(NULL, type, state, display); return -1; } int ActionStack::count(int type, int state, int display) { int result = 0; for (size_t i = 0; i < mObjects.size(); i++) { Interruptible * current = (Interruptible *) mObjects[i]; if ((type == 0 || current->type == type) && (state == 0 || current->state == state) && (display == -1 || current->display == display)) { result++; } } return result; } Interruptible * ActionStack::getActionElementFromCard(MTGCardInstance * card) { if(!card) return 0; for (size_t i = 0; i < mObjects.size(); i++) { Interruptible * current = (Interruptible *) mObjects[i]; if (current->source == card) { return current; } } return NULL; } Interruptible * ActionStack::getNext(Interruptible * previous, int type, int state, int display) { int n = getNextIndex(previous, type, state, display); if (n == -1) return NULL; return ((Interruptible *) mObjects[n]); } int ActionStack::getNextIndex(Interruptible * previous, int type, int state, int display) { int found = 0; if (!previous) found = 1; for (size_t i = 0; i < mObjects.size(); i++) { Interruptible * current = (Interruptible *) mObjects[i]; if (found && (type == 0 || current->type == type) && (state == 0 || current->state == state) && (display == -1 || current->display == display)) { return i; } if (current == previous) found = 1; } if (!found) return getNextIndex(NULL, type, state, display); return -1; } Interruptible * ActionStack::getLatest(int state) { for (int i = (int)(mObjects.size()) - 1; i >= 0; i--) { Interruptible * action = ((Interruptible *) mObjects[i]); if (action->state == state) return action; } return NULL; } int ActionStack::receiveEventPlus(WEvent * event) { int result = 0; for (size_t i = 0; i < mObjects.size(); ++i) { Interruptible * current = (Interruptible *) mObjects[i]; result += current->receiveEvent(event); } return result; } void ActionStack::Update(float dt) { //This is a hack to avoid updating the stack while tuto messages are being shown //Ideally, the tuto messages should be moved to a layer above this one //No need for Tuto when no human in game if (getCurrentTutorial() && (observer->players[0]->isHuman() || observer->players[1]->isHuman() ) ) return; askIfWishesToInterrupt = NULL; //modal = 0; TargetChooser * tc = observer->getCurrentTargetChooser(); int newState = observer->getCurrentGamePhase(); currentState = newState; if (!tc) checked = 0; //Select Stack's display mode if (mode == ACTIONSTACK_STANDARD && tc && !checked) { checked = 1; for (size_t i = 0; i < mObjects.size(); i++) { Interruptible * current = (Interruptible *) mObjects[i]; if (tc->canTarget(current)) { if (mCurr < (int) mObjects.size() && mObjects[mCurr]) mObjects[mCurr]->Leaving(JGE_BTN_UP); current->display = 1; mCurr = i; mObjects[mCurr]->Entering(); mode = ACTIONSTACK_TARGET; modal = 1; } else { current->display = 0; } } if (mode != ACTIONSTACK_TARGET) { } } else if (mode == ACTIONSTACK_TARGET && !tc) { mode = ACTIONSTACK_STANDARD; checked = 0; } if (mode == ACTIONSTACK_STANDARD) { modal = 0; if (getLatest(NOT_RESOLVED) && !tc) { Interruptible * currentSpell = (Interruptible *)getLatest(NOT_RESOLVED); MTGCardInstance * card = currentSpell->source; if(card && card->has(Constants::SPLITSECOND)) { resolve(); } else { int currentPlayerId = 0; int otherPlayerId = 1; if (observer->currentlyActing() != observer->players[0]) { currentPlayerId = 1; otherPlayerId = 0; } if (interruptDecision[currentPlayerId] == NOT_DECIDED) { askIfWishesToInterrupt = observer->players[currentPlayerId]; observer->isInterrupting = observer->players[currentPlayerId]; modal = 1; } else if (interruptDecision[currentPlayerId] == INTERRUPT) { observer->isInterrupting = observer->players[currentPlayerId]; } else { if (interruptDecision[otherPlayerId] == NOT_DECIDED) { askIfWishesToInterrupt = observer->players[otherPlayerId]; observer->isInterrupting = observer->players[otherPlayerId]; modal = 1; } else if (interruptDecision[otherPlayerId] == INTERRUPT) { observer->isInterrupting = observer->players[otherPlayerId]; } else { resolve(); } } } } } else if (mode == ACTIONSTACK_TARGET) { GuiLayer::Update(dt); } if (askIfWishesToInterrupt) { // WALDORF - added code to use a game option setting to determine how // long the Interrupt timer should be. If it is set to zero (0), the // game will wait for ever for the user to make a selection. if (options[Options::INTERRUPT_SECONDS].number > 0) { int extraTime = 0; //extraTime is a multiplier, it counts the number of unresolved stack actions //then is used to extend the time you have to interupt. //this prevents you from "running out of time" while deciding. //before this int was added, it was possible to run out of time if you had 10 stack actions //and set the timer to 4 secs. BUG FIX //http://code.google.com/p/wagic/issues/detail?id=464 extraTime = count(0, NOT_RESOLVED, 0); if (extraTime == 0) extraTime = 1;//we never want this int to be 0. if (timer < 0) timer = static_cast(options[Options::INTERRUPT_SECONDS].number * extraTime); timer -= dt; if (timer < 0) cancelInterruptOffer(); } } } void ActionStack::cancelInterruptOffer(InterruptDecision cancelMode, bool log) { int playerId = (observer->isInterrupting == observer->players[1]) ? 1 : 0; interruptDecision[playerId] = cancelMode; askIfWishesToInterrupt = NULL; observer->isInterrupting = NULL; timer = -1; if(log) { stringstream stream; stream << "no " << cancelMode; observer->logAction(playerId, stream.str()); } } void ActionStack::endOfInterruption(bool log) { int playerId = (observer->isInterrupting == observer->players[1]) ? 1 : 0; interruptDecision[playerId] = NOT_DECIDED; observer->isInterrupting = NULL; if(log) observer->logAction(playerId, "endinterruption"); } JButton ActionStack::handleInterruptRequest( JButton inputKey, int& x, int& y ) { if ( gModRules.game.canInterrupt() && y >= 10 && y < (kIconVerticalOffset + 16)) { if (x >= interruptBtnXOffset && x < noBtnXOffset ) return JGE_BTN_SEC; if (x >= noBtnXOffset && x < noToAllBtnXOffset) return JGE_BTN_OK; if (x >= noToAllBtnXOffset && x < interruptDialogWidth) return JGE_BTN_PRI; } return inputKey; } bool ActionStack::CheckUserInput(JButton inputKey) { JButton key = inputKey; JButton trigger = (options[Options::REVERSETRIGGERS].number ? JGE_BTN_NEXT : JGE_BTN_PREV); if (mode == ACTIONSTACK_STANDARD) { if (askIfWishesToInterrupt) { int x,y; if(observer->getInput()->GetLeftClickCoordinates(x, y)) { key = handleInterruptRequest(inputKey, x, y); } if (JGE_BTN_SEC == key && gModRules.game.canInterrupt()) { setIsInterrupting(askIfWishesToInterrupt); return true; } else if ((JGE_BTN_OK == key) || (trigger == key)) { cancelInterruptOffer(); return true; } else if ((JGE_BTN_PRI == key)) { cancelInterruptOffer(DONT_INTERRUPT_ALL); return true; } return true; } else if (observer->isInterrupting) { if (JGE_BTN_SEC == key) { if(observer->mExtraPayment) { observer->mExtraPayment->action->CheckUserInput(JGE_BTN_SEC); observer->mExtraPayment = NULL; } endOfInterruption(); return true; } } } else if (mode == ACTIONSTACK_TARGET) { if (modal) { if (JGE_BTN_UP == key) { if (mObjects[mCurr]) { int n = getPreviousIndex(((Interruptible *) mObjects[mCurr]), 0, 0, 1); if (n != -1 && n != mCurr && mObjects[mCurr]->Leaving(JGE_BTN_UP)) { mCurr = n; mObjects[mCurr]->Entering(); DebugTrace("ACTIONSTACK UP TO mCurr = " << mCurr); } } return true; } else if (JGE_BTN_DOWN == key) { if( mObjects[mCurr]) { int n = getNextIndex(((Interruptible *) mObjects[mCurr]), 0, 0, 1); if (n!= -1 && n != mCurr && mObjects[mCurr]->Leaving(JGE_BTN_DOWN)) { mCurr = n; mObjects[mCurr]->Entering(); DebugTrace("ACTIONSTACK DOWN TO mCurr " << mCurr); } } return true; } else if (JGE_BTN_OK == key) { DebugTrace("ACTIONSTACK CLICKED mCurr = " << mCurr); observer->stackObjectClicked(((Interruptible *) mObjects[mCurr])); return true; } return true; //Steal the input to other layers if we're visible } if (JGE_BTN_CANCEL == key) { if (modal) modal = 0; else modal = 1; return true; } } return false; } //Cleans history of last turn int ActionStack::garbageCollect() { std::vector::iterator iter = mObjects.begin(); while (iter != mObjects.end()) { Interruptible * current = ((Interruptible *) *iter); if (current->state != NOT_RESOLVED) { AManaProducer * amp = dynamic_cast(current); if(amp) { manaObjects.erase(iter); } iter = mObjects.erase(iter); SAFE_DELETE(current); } else ++iter; } return 1; } // Fizzle action and put it in targetZone void ActionStack::Fizzle(Interruptible * action, FizzleMode fizzleMode) { if (!action) { DebugTrace("ACTIONSTACK ==ERROR==: action is NULL in ActionStack::Fizzle"); return; } if (action->type == ACTION_SPELL) { Spell * spell = (Spell *) action; switch (fizzleMode) { case PUT_IN_GRAVEARD: spell->source->controller()->game->putInGraveyard(spell->source); break; case PUT_IN_HAND: spell->source->controller()->game->putInHand(spell->source); break; case PUT_IN_EXILE: spell->source->controller()->game->putInExile(spell->source); break; case PUT_IN_LIBRARY_TOP: spell->source->controller()->game->putInLibrary(spell->source); break; } } action->state = RESOLVED_NOK; } void ActionStack::Render() { //This is a hack to avoid rendering the stack above the tuto messages //Ideally, the tuto messages should be moved to a layer above this one if (getCurrentTutorial()) return; static const float kSpacer = 8; static const float x0 = 250; static const float y0 = 0; float width = 200; float height = 25; float currenty = y0 + 5; if (mode == ACTIONSTACK_STANDARD) { if (!askIfWishesToInterrupt || !askIfWishesToInterrupt->displayStack()) return; /*observer->mExtraPayment = NULL*/;//end any payment request from extra cost as we open the stack to display items. if(observer->mExtraPayment) { observer->mExtraPayment->action->CheckUserInput(JGE_BTN_SEC); observer->mExtraPayment = NULL; } for (size_t i = 0; i < mObjects.size(); i++) { Interruptible * current = (Interruptible *) mObjects[i]; if (current->state == NOT_RESOLVED) height += current->mHeight; } WFont * mFont = observer->getResourceManager()->GetWFont(Fonts::MAIN_FONT); mFont->SetBase(0); mFont->SetScale(DEFAULT_MAIN_FONT_SCALE); mFont->SetColor(ARGB(255,255,255,255)); JRenderer * renderer = JRenderer::GetInstance(); //stack shadow //renderer->FillRoundRect(x0 - 7, y0+2, width + 17, height + 2, 9.0f, ARGB(128,0,0,0)); //stack fill renderer->FillRect(x0 - 7, y0+2, width + 17, height + 14, ARGB(225,5,5,5)); //stack highlight renderer->FillRect(x0 - 6, y0+3, width + 15, 30, ARGB(255,89,89,89)); //another border renderer->DrawRect(x0 - 6, y0+33, width + 15, height - 18, ARGB(255,89,89,89)); //stack border renderer->DrawRect(x0 - 7, y0+2, width + 17, height + 14, ARGB(255,240,240,240)); std::ostringstream stream; // WALDORF - changed "interrupt ?" to "Interrupt?". Don't display count down // seconds if the user disables auto progressing interrupts by setting the seconds // value to zero in Options. // Mootpoint 01/12/2011: draw the interrupt text first, at the top. Offset the rest of the // unresolved stack effects down so that they don't collide with the interrupt text. if (options[Options::INTERRUPT_SECONDS].number == 0) stream << _(kInterruptMessageString); else stream << _(kInterruptMessageString) << " " << static_cast(timer); mFont->DrawString(stream.str(), x0 + 5, currenty - 2); // static const float kIconVerticalOffset = 24; static const float kIconHorizontalOffset = 10; static const float kBeforeIconSpace = 12; //Render "interrupt?" text + possible actions { float currentx = x0 + 10; interruptBtnXOffset = static_cast(currentx); if (gModRules.game.canInterrupt()) { renderer->RenderQuad(pspIcons[7].get(), currentx, kIconVerticalOffset - 2, 0, kGamepadIconSize, kGamepadIconSize); currentx+= kIconHorizontalOffset; mFont->DrawString(_(kInterruptString), currentx, kIconVerticalOffset - 8); currentx+= mFont->GetStringWidth(_(kInterruptString).c_str()) + kBeforeIconSpace; } noBtnXOffset = static_cast(currentx); renderer->RenderQuad(pspIcons[4].get(), currentx, kIconVerticalOffset - 2, 0, kGamepadIconSize, kGamepadIconSize); currentx+= kIconHorizontalOffset; mFont->DrawString(_(kNoString), currentx, kIconVerticalOffset - 8); currentx+= mFont->GetStringWidth(_(kNoString).c_str()) + kBeforeIconSpace; noToAllBtnXOffset = static_cast(currentx); if (mObjects.size() > 1) { renderer->RenderQuad(pspIcons[6].get(), currentx, kIconVerticalOffset - 2, 0, kGamepadIconSize, kGamepadIconSize); currentx+= kIconHorizontalOffset; mFont->DrawString(_(kNoToAllString), currentx, kIconVerticalOffset - 8); currentx+= mFont->GetStringWidth(_(kNoToAllString).c_str()) + kBeforeIconSpace; } interruptDialogWidth = static_cast(currentx); } currenty += kIconVerticalOffset + kSpacer; for (size_t i = 0; i < mObjects.size(); i++) { Interruptible * current = (Interruptible *) mObjects[i]; if (current && current->state == NOT_RESOLVED) { current->x = x0; current->y = currenty; current->Render(); currenty += current->mHeight; } } } else if (mode == ACTIONSTACK_TARGET && modal) { for (size_t i = 0; i < mObjects.size(); i++) { Interruptible * current = (Interruptible *) mObjects[i]; if (current->display) height += current->mHeight; } WFont * mFont = observer->getResourceManager()->GetWFont(Fonts::MAIN_FONT); mFont->SetScale(DEFAULT_MAIN_FONT_SCALE); mFont->SetColor(ARGB(255,255,255,255)); JRenderer * renderer = JRenderer::GetInstance(); renderer->FillRect(x0, y0, width, height, ARGB(200,0,0,0)); renderer->DrawRect(x0 - 1, y0 - 1, width + 2, height + 2, ARGB(255,255,255,255)); for (size_t i = 0; i < mObjects.size(); i++) { Interruptible * current = (Interruptible *) mObjects[i]; if (mObjects[i] != NULL && current->display) { ((Interruptible *) mObjects[i])->x = x0 + 5; if (i != mObjects.size() - 1) { ((Interruptible *) mObjects[i])->y = currenty; currenty += ((Interruptible *) mObjects[i])->mHeight; } else { ((Interruptible *) mObjects[i])->y = currenty + 40; currenty += ((Interruptible *) mObjects[i])->mHeight + 40; } mObjects[i]->Render(); } } } } #if defined (WIN32) || defined (LINUX) || defined (IOS) void Interruptible::Dump() { string stype, sstate, sdisplay = ""; switch (type) { case ACTION_SPELL: stype = "spell"; break; case ACTION_DAMAGE: stype = "damage"; break; case ACTION_DAMAGES: stype = "damages"; break; case ACTION_NEXTGAMEPHASE: stype = "next phase"; break; case ACTION_DRAW: stype = "draw"; break; case ACTION_PUTINGRAVEYARD: stype = "put in graveyard"; break; case ACTION_ABILITY: stype = "ability"; break; default: stype = "unknown"; break; } switch(state) { case NOT_RESOLVED: sstate = "not resolved"; break; case RESOLVED_OK: sstate = "resolved"; break; case RESOLVED_NOK: sstate = "fizzled"; break; default: sstate = "unknown"; break; } DebugTrace("type: " << stype << " " << type << " - state: " << sstate << " " << state << " - display: " << display); } void ActionStack::Dump() { DebugTrace("=====\nDumping Action Stack====="); for (size_t i = 0; i < mObjects.size(); i++) { Interruptible * current = (Interruptible *)mObjects[i]; current->Dump(); } } #endif