Files
wagic/projects/mtg/src/ActionLayer.cpp
2013-11-20 11:13:35 +01:00

551 lines
16 KiB
C++

#include "PrecompiledHeader.h"
#include "ActionLayer.h"
#include "GameObserver.h"
#include "Targetable.h"
#include "WEvent.h"
#include "AllAbilities.h"
#include "MTGRules.h"
MTGAbility* ActionLayer::getAbility(int type)
{
for (size_t i = 1; i < mObjects.size(); i++)
{
MTGAbility * a = ((MTGAbility *) mObjects[i]);
if (a->aType == type)
{
return a;
}
}
return NULL;
}
int ActionLayer::removeFromGame(ActionElement * e)
{
mReactions.erase(e);
int i = getIndexOf(e);
if (i == -1)
return 0;
if (isWaitingForAnswer() == e)
setCurrentWaitingAction(NULL);
assert(e);
e->destroy();
i = getIndexOf(e); //the destroy event might have changed the contents of mObjects, so we get the index again
if (i == -1)
return 0; //Should not happen, it means we deleted thesame object twice?
AbilityFactory af(observer);
MTGAbility * a = dynamic_cast<MTGAbility*>(e);
if (a != NULL)
{
MTGAbility * toCheck = af.getCoreAbility(a);
AManaProducer * manaObject = dynamic_cast<AManaProducer*>(toCheck);
if(manaObject)
{
for (size_t i = 0; i < manaObjects.size(); i++)
if (manaObjects[i] == e)
{
manaObjects.erase(manaObjects.begin() + i);
}
}
}
mObjects.erase(mObjects.begin() + i);
return 1;
}
bool ActionLayer::moveToGarbage(ActionElement * e)
{
if (removeFromGame(e))
{
garbage.push_back(e);
return true;
}
return false;
}
void ActionLayer::cleanGarbage()
{
for (size_t i = 0; i < garbage.size(); ++i)
{
SAFE_DELETE(garbage[i]);
}
garbage.clear();
}
int ActionLayer::reactToClick(ActionElement * ability, MTGCardInstance * card)
{
int result = ability->reactToClick(card);
if (result)
stuffHappened = 1;
return result;
}
int ActionLayer::reactToTargetClick(ActionElement* ability, Targetable * card)
{
int result = ability->reactToTargetClick(card);
if (result)
stuffHappened = 1;
return result;
}
bool ActionLayer::CheckUserInput(JButton key)
{
if (observer->mExtraPayment && key == JGE_BTN_SEC)
{
for (size_t i = 0; i < mObjects.size(); i++)
{
if (mObjects[i] != NULL)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
currentAction->CheckUserInput(key);
//check first with a mock up to see if any abilities will care about the extra payment
//being cancelled. currently only menuability and paidability will care.
}
}
observer->mExtraPayment = NULL;
return 1;
}
if (menuObject)
{
return false;
}
for (size_t i = 0; i < mObjects.size(); i++)
{
if (mObjects[i] != NULL)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
if (currentAction->CheckUserInput(key))
return true;
}
}
return false;
}
void ActionLayer::Update(float dt)
{
stuffHappened = 0;
if (menuObject)
{
abilitiesMenu->Update(dt);
return;
}
modal = 0;
for (int i = (int)(mObjects.size()) - 1; i >= 0; i--)
{
//a dirty hack, there might be cases when the mObject array gets reshaped if an ability removes some of its children abilites
if ((int) mObjects.size() <= i)
{
i = (int) (mObjects.size()) - 1;
if (i<0)
break;
}
if (mObjects[i] != NULL)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
if (currentAction->testDestroy())
observer->removeObserver(currentAction);
}
}
GamePhase newPhase = observer->getCurrentGamePhase();
for (size_t i = 0; i < mObjects.size(); i++)
{
if (mObjects[i] != NULL)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
currentAction->newPhase = newPhase;
currentAction->Update(dt);
currentAction->currentPhase = newPhase;
}
}
if (cantCancel)
{
ActionElement * ae = isWaitingForAnswer();
if(ae && ae->getActionTc())
{
if (!ae->getActionTc()->validTargetsExist())
{
cantCancel = 0;
cancelCurrentAction();
return;
}
int countTargets = ae->getActionTc()->countValidTargets();
int maxTargets = ae->getActionTc()->maxtargets;
if (countTargets < maxTargets)
{
/*
@movedto(this|mygraveyard) from(mybattlefield):moveto(mybattlefield)
target(<2>creature[elf]|opponentgraveyard)
and there were 3 in the grave, you have the valid amount needed, this function should not trigger
...however if you had only 1 in the grave, then the max targets is reset to the maximum you CAN
use this effect on...in line with "up to" wording found on the cards with such abilities.
without this, the game locks into a freeze state while you try to select the targets and dont have enough to
fill the maxtargets list.
*/
if(int(ae->getActionTc()->getNbTargets()) == countTargets-1)
ae->getActionTc()->done = true;
}
}
}
}
void ActionLayer::Render()
{
if (menuObject)
{
abilitiesMenu->Render();
return;
}
for (size_t i = 0; i < mObjects.size(); i++)
{
if (mObjects[i] != NULL)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
currentAction->Render();
}
}
}
void ActionLayer::setCurrentWaitingAction(ActionElement * ae)
{
assert(!ae || !currentWaitingAction);//this assert causes crashes when may abilities overlap each other on ai. this conidiation is preexsiting.
currentWaitingAction = ae;
if (!ae)
cantCancel = 0;
}
TargetChooser * ActionLayer::getCurrentTargetChooser()
{
if (currentWaitingAction && currentWaitingAction->waitingForAnswer)
return currentWaitingAction->getActionTc();
return NULL;
}
int ActionLayer::cancelCurrentAction()
{
ActionElement * ae = isWaitingForAnswer();
if (!ae)
return 0;
if (cantCancel && ae->getActionTc()->validTargetsExist())
return 0;
ae->waitingForAnswer = 0; //TODO MOVE THIS IN ActionElement
setCurrentWaitingAction(NULL);
return 1;
}
ActionElement * ActionLayer::isWaitingForAnswer()
{
if (currentWaitingAction && currentWaitingAction->waitingForAnswer)
return currentWaitingAction;
return NULL;
}
int ActionLayer::stillInUse(MTGCardInstance * card)
{
for (size_t i = 0; i < mObjects.size(); i++)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
if (currentAction->stillInUse(card))
return 1;
}
return 0;
}
int ActionLayer::receiveEventPlus(WEvent * event)
{
int result = 0;
for (size_t i = 0; i < mObjects.size(); i++)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
result += currentAction->receiveEvent(event);
}
return result;
}
int ActionLayer::isReactingToTargetClick(Targetable * card)
{
int result = 0;
if (isWaitingForAnswer())
return -1;
for (size_t i = 0; i < mObjects.size(); i++)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
result += currentAction->isReactingToTargetClick(card);
}
return result;
}
int ActionLayer::reactToTargetClick(Targetable * card)
{
int result = 0;
ActionElement * ae = isWaitingForAnswer();
if (ae)
return reactToTargetClick(ae, card);
for (size_t i = 0; i < mObjects.size(); i++)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
result += currentAction->reactToTargetClick(card);
}
return result;
}
bool ActionLayer::getMenuIdFromCardAbility(MTGCardInstance *card, MTGAbility *ability, int& menuId)
{
int ctr = 0;
for (size_t i = 0; i < mObjects.size(); i++)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
if (currentAction->isReactingToClick(card))
{
if(currentAction == ability) {
// code corresponding to that is in setMenuObject
menuId = ctr;
ctr++;
}
}
}
if(ctr == 0 || ctr == 1)
{
return false;
}
else
return true;
}
//TODO Simplify with only object !!!
int ActionLayer::isReactingToClick(MTGCardInstance * card)
{
int result = 0;
if (isWaitingForAnswer())
return -1;
for (size_t i = 0; i < mObjects.size(); i++)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
if (currentAction->isReactingToClick(card))
{
++result;
mReactions.insert(currentAction);
}
}
return result;
}
int ActionLayer::reactToClick(MTGCardInstance * card)
{
int result = 0;
ActionElement * ae = isWaitingForAnswer();
if (ae)
return reactToClick(ae, card);
std::set<ActionElement*>::const_iterator iter = mReactions.begin();
std::set<ActionElement*>::const_iterator end = mReactions.end();
for (; iter !=end; ++iter)
{
result += reactToClick(*iter, card);
if (result)
break;
}
#ifdef WIN32
// if we hit this, then something strange has happened with the click logic - reactToClick()
// should never be called if isReactingToClick() previously didn't have an object return true
assert(!mReactions.empty());
#endif
mReactions.clear();
return result;
}
void ActionLayer::setMenuObject(Targetable * object, bool must)
{
if (!object)
{
DebugTrace("FATAL: ActionLayer::setMenuObject");
return;
}
menuObject = object;
SAFE_DELETE(abilitiesMenu);
abilitiesTriggered = NULL;
abilitiesMenu = NEW SimpleMenu(observer->getInput(), observer->getResourceManager(), 10, this, Fonts::MAIN_FONT, 100, 100, object->getDisplayName().c_str());
abilitiesTriggered = NEW SimpleMenu(observer->getInput(), observer->getResourceManager(), 10, this, Fonts::MAIN_FONT, 100, 100, object->getDisplayName().c_str());
currentActionCard = (MTGCardInstance*)object;
for (size_t i = 0; i < mObjects.size(); i++)
{
ActionElement * currentAction = (ActionElement *) mObjects[i];
if (currentAction->isReactingToTargetClick(object))
{
if(dynamic_cast<MTGAbility*>(currentAction)->getCost()||dynamic_cast<PermanentAbility*>(currentAction))
{
abilitiesMenu->Add(i, currentAction->getMenuText());
}
else
{
//the only time this condiation is hit is when we are about to display a menu of abilities
//which were triggered through a triggered ability or abilities such as multiple target(
//and may abilities appearing on cards ie: auto=may draw:1
//this prevents abilities activated otherwise from displaying on the same menu as "triggered" and
//"put in play" abilities. an activated ability of a card should never share a menu with
//a triggered or may ability as it leads to exploits.
//only exception is perminent abilities such as "cast card normally" which can share the menu with autohand=
abilitiesTriggered->Add(i, currentAction->getMenuText());
}
}
}
if(abilitiesTriggered->mCount)
{
SAFE_DELETE(abilitiesMenu);
abilitiesMenu = abilitiesTriggered;
}
else
{
SAFE_DELETE(abilitiesTriggered);
}
if (!must)
abilitiesMenu->Add(kCancelMenuID, "Cancel");
else
cantCancel = 1;
modal = 1;
}
void ActionLayer::setCustomMenuObject(Targetable * object, bool must,vector<MTGAbility*>abilities,string customName)
{
if (!object)
{
DebugTrace("FATAL: ActionLayer::setCustomMenuObject");
return;
}
menuObject = object;
SAFE_DELETE(abilitiesMenu);
abilitiesMenu = NEW SimpleMenu(observer->getInput(), observer->getResourceManager(), 10, this, Fonts::MAIN_FONT, 100, 100, customName.size()?customName.c_str():object->getDisplayName().c_str());
currentActionCard = NULL;
abilitiesMenu->isMultipleChoice = false;
if(abilities.size())
{
abilitiesMenu->isMultipleChoice = true;
for(int w = 0; w < int(abilities.size());w++)
{
ActionElement* currentAction = (ActionElement*)abilities[w];
currentActionCard = (MTGCardInstance*)abilities[0]->target;
abilitiesMenu->Add(mObjects.size()-1, currentAction->getMenuText(),"",false);
}
}
if (!must)
abilitiesMenu->Add(kCancelMenuID, "Cancel");
else
cantCancel = 1;
modal = 1;
}
void ActionLayer::doReactTo(int menuIndex)
{
if (menuObject)
{
int controlid = abilitiesMenu->mObjects[menuIndex]->GetId();
DebugTrace("ActionLayer::doReactTo " << controlid);
ButtonPressed(0, controlid);
}
}
void ActionLayer::ButtonPressed(int, int controlid)
{
stringstream stream;
for(size_t i = 0; i < abilitiesMenu->mObjects.size(); i++)
{ // this computes the reverse from the doReactTo method
if(abilitiesMenu->mObjects[i]->GetId() == controlid)
{
stream << "choice " << i;
observer->logAction(observer->currentActionPlayer, stream.str());
break;
}
}
if(this->abilitiesMenu && this->abilitiesMenu->isMultipleChoice)
{
return ButtonPressedOnMultipleChoice();
}
if (controlid >= 0 && controlid < static_cast<int>(mObjects.size()))
{
ActionElement * currentAction = (ActionElement *) mObjects[controlid];
currentAction->reactToTargetClick(menuObject);
menuObject = 0;
currentActionCard = NULL;
}
else if (controlid == kCancelMenuID)
{
observer->mLayers->stackLayer()->endOfInterruption(false);
menuObject = 0;
currentActionCard = NULL;
}
else
{
// fallthrough case. We have an id we don't recognize - do nothing, don't clear the menu!
//assert(false);
}
}
void ActionLayer::ButtonPressedOnMultipleChoice(int choice)
{
int currentMenuObject = -1;
for(int i = int(mObjects.size()-1);i > 0;i--)
{
//the currently displayed menu is not always the currently listenning action object
//find the menu which is displayed.
MenuAbility * ma = dynamic_cast<MenuAbility *>(mObjects[i]);//find the active menu
if(ma && ma->triggered)
{
currentMenuObject = i;
break;
}
}
if (currentMenuObject >= 0 && currentMenuObject < static_cast<int>(mObjects.size()))
{
ActionElement * currentAction = (ActionElement *) mObjects[currentMenuObject];
currentAction->reactToChoiceClick(menuObject,choice > -1?choice:this->abilitiesMenu->getmCurr(),currentMenuObject);
MenuAbility * ma = dynamic_cast<MenuAbility *>(mObjects[currentMenuObject]);
if(ma)
ma->removeMenu = true;//we clicked something, close menu now.
}
else if (currentMenuObject == kCancelMenuID)
{
observer->mLayers->stackLayer()->endOfInterruption(false);
}
menuObject = 0;
currentActionCard = NULL;
}
ActionLayer::ActionLayer(GameObserver *observer)
: GuiLayer(observer)
{
menuObject = NULL;
abilitiesMenu = NULL;
abilitiesTriggered = NULL;
stuffHappened = 0;
currentWaitingAction = NULL;
cantCancel = 0;
}
ActionLayer::~ActionLayer()
{
while(mObjects.size())
moveToGarbage((ActionElement *) mObjects[mObjects.size() - 1]);
SAFE_DELETE(abilitiesMenu);
cleanGarbage();
}