1669 lines
54 KiB
C++
1669 lines
54 KiB
C++
/*
|
|
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 <typeinfo>
|
|
|
|
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, int aType, vector<JQuadPtr> mytargetsQuad)
|
|
{
|
|
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();
|
|
bool hiddenview = aType == MTGAbility::HIDDENVIEW?true:false;
|
|
|
|
if (!targetQuad && !mytargetsQuad.size())
|
|
{
|
|
/*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
|
|
{
|
|
/*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));*/
|
|
float xnadj = 0;
|
|
int count = 1;
|
|
if(mytargetsQuad.size())
|
|
{
|
|
count = mytargetsQuad.size();
|
|
for(unsigned int k = 0; k < mytargetsQuad.size(); k++)
|
|
{
|
|
if(k > 10)
|
|
break;
|
|
xnadj+=4;
|
|
}
|
|
}
|
|
|
|
ostringstream aa;
|
|
aa << action << " " << "(" << count << ")";
|
|
|
|
if(count > 1)
|
|
xnadj -= 4;
|
|
|
|
if(!hiddenview)
|
|
{
|
|
mFont->DrawString(">", x + 32, y + GetVerticalTextOffset(), JGETEXT_LEFT);
|
|
if(count > 1)
|
|
{
|
|
mFont->DrawString(_(aa.str()).c_str(), x + 75 + xnadj, y + GetVerticalTextOffset(), JGETEXT_LEFT);
|
|
}
|
|
else
|
|
mFont->DrawString(_(action).c_str(), x + 75 + xnadj, y + GetVerticalTextOffset(), JGETEXT_LEFT);
|
|
}
|
|
else
|
|
mFont->DrawString(_(action).c_str(), x + 35, y + GetVerticalTextOffset(), JGETEXT_LEFT);
|
|
|
|
}
|
|
|
|
JQuadPtr quad = observer->getResourceManager()->RetrieveCard(source, CACHE_THUMB);
|
|
JQuadPtr fakeborder = observer->getResourceManager()->GetQuad("white");
|
|
if (!quad.get())
|
|
quad = CardGui::AlternateThumbQuad(source);
|
|
if (quad.get())
|
|
{
|
|
quad->SetColor(ARGB(255,255,255,255));
|
|
float scale = mHeight / quad->mHeight;
|
|
if (fakeborder.get())
|
|
{
|
|
fakeborder->SetColor(ARGB(255,15,15,15));
|
|
renderer->RenderQuad(fakeborder.get(), x + (quad->mWidth * scale / 2), y + (quad->mHeight * scale / 2), 0, (29 * actZ + 1) / 16, 42 * actZ / 16);
|
|
}
|
|
|
|
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(observer->gameType() == GAME_TYPE_MOMIR && aType == MTGAbility::FORCED_TOKEN_CREATOR)
|
|
{
|
|
Pos pos = Pos(CardGui::BigWidth / 2, CardGui::BigHeight / 2 - 10, 0.80f, 0.0, 220);
|
|
pos.actY = 142;//adjust y a little bit
|
|
CardGui::DrawCard(source, pos, observer->getCardSelector()->GetDrawMode());
|
|
}
|
|
else if (observer->gameType() != GAME_TYPE_MOMIR)
|
|
{
|
|
Pos pos = Pos(CardGui::BigWidth / 2, CardGui::BigHeight / 2 - 10, 0.80f, 0.0, 220);
|
|
pos.actY = 142;//adjust y a little bit
|
|
CardGui::DrawCard(source, pos, observer->getCardSelector()->GetDrawMode());
|
|
}
|
|
|
|
}
|
|
|
|
if(mytargetsQuad.size() && !hiddenview)
|
|
{
|
|
float xadj = 0;
|
|
for(unsigned int k = 0; k < mytargetsQuad.size(); k++)
|
|
{
|
|
if(k > 10)
|
|
break;
|
|
|
|
JQuadPtr multiQ = mytargetsQuad[k];
|
|
if(multiQ.get())
|
|
{
|
|
float backupX = multiQ->mHotSpotX;
|
|
float backupY = multiQ->mHotSpotY;
|
|
multiQ->SetColor(ARGB(255,255,255,255));
|
|
multiQ->SetHotSpot(multiQ->mWidth / 2, multiQ->mHeight / 2);
|
|
float scale = mHeight / multiQ->mHeight;
|
|
if (fakeborder.get())
|
|
{
|
|
fakeborder->SetColor(ARGB(255,15,15,15));
|
|
renderer->RenderQuad(fakeborder.get(), x + 55 + xadj, y + ((mHeight - multiQ->mHeight) / 2) + multiQ->mHotSpotY, 0, (29 * actZ + 1) / 16, 42 * actZ / 16);
|
|
}
|
|
renderer->RenderQuad(multiQ.get(), x + 55 + xadj, y + ((mHeight - multiQ->mHeight) / 2) + multiQ->mHotSpotY, 0, scale, scale);
|
|
multiQ->SetHotSpot(backupX, backupY);
|
|
xadj+=4;
|
|
}
|
|
}
|
|
}
|
|
else if(!hiddenview)
|
|
{
|
|
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();
|
|
vector<JQuadPtr> mytargetQuads;
|
|
vector<MTGCardInstance*> myClones;
|
|
|
|
int fmLibrary = 0;
|
|
int force = 0;
|
|
|
|
Targetable * _target = ability->target;
|
|
if (ability->getActionTc())
|
|
{
|
|
Targetable * t = ability->getActionTc()->getNextTarget();
|
|
if (t)
|
|
_target = t;
|
|
|
|
|
|
//test vector quads
|
|
if(ability->getActionTc()->getTargetsFrom().size())
|
|
{
|
|
for(size_t i = 0; i < ability->getActionTc()->getTargetsFrom().size(); i++)
|
|
{
|
|
Targetable * tt = ability->getActionTc()->getTargetsFrom()[i];
|
|
if(tt)
|
|
{
|
|
MTGCardInstance* card = dynamic_cast<MTGCardInstance *>(tt);
|
|
if(!card && dynamic_cast<Spell *>(tt)) card = dynamic_cast<Spell *>(tt)->source; //Fixed crash on targeting a spell on stack by correctly casting variable tt.
|
|
if(((Damageable *)(tt))->type_as_damageable == Damageable::DAMAGEABLE_MTGCARDINSTANCE)
|
|
{
|
|
if(card)
|
|
myClones.push_back(card); //fill vector
|
|
if(source->has(Constants::HIDDENFACE) && card && !observer->isInLibrary(card))
|
|
mytargetQuads.push_back(card->getIcon()); //Fixed crash on targeting a spell on stack by correctly casting variable tt.
|
|
else if (!source->has(Constants::HIDDENFACE) && card)
|
|
mytargetQuads.push_back(card->getIcon()); //Fixed crash on targeting a spell on stack by correctly casting variable tt.
|
|
else
|
|
fmLibrary++;
|
|
}
|
|
else if(card)
|
|
mytargetQuads.push_back(card->getIcon()); //Fixed crash on targeting a spell on stack by correctly casting variable tt.
|
|
}
|
|
}
|
|
}
|
|
//end
|
|
}
|
|
Damageable * target = NULL;
|
|
if (_target != ability->source && (dynamic_cast<MTGCardInstance *>(_target) || dynamic_cast<Player *>(_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;
|
|
}
|
|
}
|
|
|
|
//setborder test
|
|
if(myClones.size())
|
|
{
|
|
source->forcedBorderB = 1;
|
|
for(unsigned int kk = 0; kk < myClones.size(); kk++)
|
|
{
|
|
if(myClones[kk])
|
|
{
|
|
myClones[kk]->forcedBorderA = 1;
|
|
//JRenderer::GetInstance()->DrawLine(myClones[kk]->view->actX,myClones[kk]->view->actY,source->view->actX,source->view->actY,0.5f,ARGB(120, 255, 0, 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
if(source->has(Constants::HIDDENFACE) && fmLibrary)
|
|
force = MTGAbility::HIDDENVIEW;
|
|
|
|
if(observer->gameType() == GAME_TYPE_MOMIR)
|
|
Interruptible::Render(source, quad.get(), alt1, alt2, action, true, ability->aType, mytargetQuads);
|
|
else
|
|
Interruptible::Render(source, quad.get(), alt1, alt2, action, false, force, mytargetQuads);
|
|
}
|
|
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;
|
|
vector<Targetable*>backupTgt = 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()->getManaUsedToCast())
|
|
{
|
|
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->basicAbilities[(int)Constants::GAINEDEXILEDEATH] || (card->basicAbilities[(int)Constants::HASDISTURB] && card->alternateCostPaid[ManaCost::MANA_PAID_WITH_RETRACE] == 1))
|
|
{
|
|
card->basicAbilities[(int)Constants::GAINEDEXILEDEATH] = 0;
|
|
card->controller()->game->putInZone(card, zone, card->owner->game->exile);
|
|
return 1;
|
|
}
|
|
if (card->basicAbilities[(int)Constants::DOUBLEFACEDEATH] || card->basicAbilities[(int)Constants::GAINEDDOUBLEFACEDEATH])
|
|
{
|
|
card->basicAbilities[(int)Constants::GAINEDDOUBLEFACEDEATH] = 0;
|
|
card->controller()->game->putInZone(card, zone, card->owner->game->temp);
|
|
return 1;
|
|
}
|
|
if (card->basicAbilities[(int)Constants::HANDDEATH] || card->basicAbilities[(int)Constants::GAINEDHANDDEATH])
|
|
{
|
|
card->basicAbilities[(int)Constants::GAINEDHANDDEATH] = 0;
|
|
card->controller()->game->putInZone(card, zone, card->owner->game->hand);
|
|
return 1;
|
|
}
|
|
if (card->basicAbilities[(int)Constants::INPLAYDEATH] || card->basicAbilities[(int)Constants::INPLAYTAPDEATH])
|
|
{
|
|
bool toTap = card->basicAbilities[(int)Constants::INPLAYTAPDEATH];
|
|
bool addCounter = card->basicAbilities[(int)Constants::COUNTERDEATH];
|
|
card = card->controller()->game->putInZone(card, zone, card->owner->game->graveyard);
|
|
card = card->controller()->game->putInZone(card, card->owner->game->graveyard, card->owner->game->battlefield);
|
|
if(toTap)
|
|
card->tap(true);
|
|
if(addCounter)
|
|
card->counters->addCounter(1, 1, false);
|
|
return 1;
|
|
}
|
|
if (zone == observer->players[0]->game->inPlay || zone == observer->players[1]->game->inPlay)
|
|
{
|
|
card->controller()->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)
|
|
{
|
|
if((observer->gameType() == GAME_TYPE_MOMIR && ability->aType == MTGAbility::FORCED_TOKEN_CREATOR)||
|
|
(dynamic_cast<GenericTargetAbility *>(ability) && ability->canBeInterrupted && !observer->OpenedDisplay && !observer->players[0]->game->reveal->cards.size()))//test interrupt...
|
|
interruptDecision[0] = NOT_DECIDED;
|
|
else
|
|
interruptDecision[0] = DONT_INTERRUPT;
|
|
}
|
|
if (observer->OpenedDisplay && observer->players[0]->game->reveal->cards.size())
|
|
{
|
|
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, bool forcedinterrupt)
|
|
{
|
|
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)
|
|
{
|
|
if(forcedinterrupt)
|
|
interruptDecision[0] = INTERRUPT;
|
|
else
|
|
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 (mObjects[i] && (((Interruptible *) mObjects[i])->type == ACTION_ABILITY)) // Fixed a random crash occurring when mObjects[i] is null...
|
|
{
|
|
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;
|
|
|
|
if (observer->mLayers->actionLayer()->menuObject)// || observer->LPWeffect) //test fix for hang for both legendary with action/reveal
|
|
if(observer->players[0]->isHuman() || observer->players[1]->isHuman())
|
|
return;//dont do any of this if a menuobject exist.
|
|
|
|
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<float>(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<JGuiObject *>::iterator iter = mObjects.begin();
|
|
|
|
while (iter != mObjects.end())
|
|
{
|
|
Interruptible * current = ((Interruptible *) *iter);
|
|
if (current->state != NOT_RESOLVED)
|
|
{
|
|
AManaProducer * amp = dynamic_cast<AManaProducer*>(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, MTGCardInstance * fizzler, FizzleMode fizzleMode)
|
|
{
|
|
if (!action)
|
|
{
|
|
DebugTrace("ACTIONSTACK ==ERROR==: action is NULL in ActionStack::Fizzle");
|
|
return;
|
|
}
|
|
if (action->type == ACTION_SPELL)
|
|
{
|
|
Spell * spell = (Spell *) action;
|
|
MTGCardInstance * _target = NULL;
|
|
unsigned int position = 0;
|
|
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:
|
|
case PUT_IN_EXILE_IMPRINT:
|
|
_target = spell->source->controller()->game->putInExile(spell->source);
|
|
if (_target && fizzler && fizzleMode == PUT_IN_EXILE_IMPRINT){
|
|
fizzler->imprintedCards.push_back(_target);
|
|
if (fizzler->imprintedCards.size()){
|
|
if (fizzler->imprintedCards.back()->getName().size()){
|
|
fizzler->currentimprintName = fizzler->imprintedCards.back()->getName();
|
|
fizzler->imprintedNames.push_back(fizzler->imprintedCards.back()->getName());
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case PUT_IN_LIBRARY_TOP:
|
|
case PUT_IN_LIBRARY_SECOND:
|
|
case PUT_IN_LIBRARY_BOTTOM:
|
|
_target = spell->source->controller()->game->putInLibrary(spell->source);
|
|
if (_target && fizzleMode == PUT_IN_LIBRARY_BOTTOM){
|
|
MTGLibrary * library = _target->owner->game->library;
|
|
vector<MTGCardInstance *>oldOrder = library->cards;
|
|
vector<MTGCardInstance *>newOrder;
|
|
newOrder.push_back(_target);
|
|
for(unsigned int k = 0 ;k < oldOrder.size(); ++k)
|
|
{
|
|
MTGCardInstance * rearranged = oldOrder[k];
|
|
if(rearranged != _target)
|
|
newOrder.push_back(rearranged);
|
|
}
|
|
library->cards = newOrder;
|
|
} else if (_target && fizzleMode == PUT_IN_LIBRARY_SECOND){
|
|
position = 2;
|
|
MTGLibrary * library = _target->owner->game->library;
|
|
vector<MTGCardInstance *>oldOrder = library->cards;
|
|
vector<MTGCardInstance *>newOrder;
|
|
if(position > oldOrder.size())
|
|
position = oldOrder.size(); //Avoid to exceed the library dimension.
|
|
for(unsigned int k = 0; k < oldOrder.size() - position; ++k)
|
|
{
|
|
MTGCardInstance * rearranged = oldOrder[k];
|
|
if(rearranged != _target)
|
|
newOrder.push_back(rearranged);
|
|
}
|
|
newOrder.push_back(_target);
|
|
for(unsigned int k = oldOrder.size() - position ; k < oldOrder.size(); ++k)
|
|
{
|
|
MTGCardInstance * rearranged = oldOrder[k];
|
|
if(rearranged != _target)
|
|
newOrder.push_back(rearranged);
|
|
}
|
|
library->cards = newOrder;
|
|
}
|
|
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));
|
|
//top stack fill
|
|
renderer->FillRect(x0 - 6, y0+37, width + 15, 40.5f, ARGB(20,135,206,235));
|
|
//stack highlight
|
|
renderer->FillRect(x0 - 6, y0+3, width + 15, 31.f, ARGB(255,89,89,89));
|
|
//another border
|
|
renderer->DrawRect(x0 - 6, y0+34.5f, width + 15, height - 19.5f, 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<int>(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<int>(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<int>(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<int>(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<int>(currentx);
|
|
}
|
|
|
|
currenty += kIconVerticalOffset + kSpacer;
|
|
|
|
float totalmHeight = 0.f;
|
|
for (size_t i = 0; i < mObjects.size(); i++)
|
|
{
|
|
Interruptible * current = (Interruptible *) mObjects[i];
|
|
if (current && current->state == NOT_RESOLVED)
|
|
totalmHeight += current->mHeight;
|
|
}
|
|
int sC = 0;//stack Count
|
|
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;*/
|
|
sC+=1;
|
|
float cH = current->mHeight*sC;
|
|
current->x = x0;
|
|
current->y = (5+kIconVerticalOffset + kSpacer) + (totalmHeight - cH);
|
|
//render the stack object
|
|
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;
|
|
}
|
|
current->mHasFocus = false;//fix stack display
|
|
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
|