- some dangerous casts Player/MTGCardInstance fixed
- removed typeAsTarget function and replaced with dynamic casting - The test suite passes, but it is possible that I busted some of AI's features :(
This commit is contained in:
@@ -98,6 +98,7 @@ class AIPlayerBaka: public AIPlayer{
|
|||||||
|
|
||||||
//used by MomirPlayer, hence protected instead of private
|
//used by MomirPlayer, hence protected instead of private
|
||||||
virtual int getEfficiency(OrderedAIAction * action);
|
virtual int getEfficiency(OrderedAIAction * action);
|
||||||
|
virtual int getEfficiency(MTGAbility * ability);
|
||||||
virtual bool payTheManaCost(ManaCost * cost, MTGCardInstance * card = NULL,vector<MTGAbility*> gotPayment = vector<MTGAbility*>());
|
virtual bool payTheManaCost(ManaCost * cost, MTGCardInstance * card = NULL,vector<MTGAbility*> gotPayment = vector<MTGAbility*>());
|
||||||
virtual int getCreaturesInfo(Player * player, int neededInfo = INFO_NBCREATURES , int untapMode = 0, int canAttack = 0);
|
virtual int getCreaturesInfo(Player * player, int neededInfo = INFO_NBCREATURES , int untapMode = 0, int canAttack = 0);
|
||||||
virtual ManaCost * getPotentialMana(MTGCardInstance * card = NULL);
|
virtual ManaCost * getPotentialMana(MTGCardInstance * card = NULL);
|
||||||
|
|||||||
@@ -78,11 +78,6 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
int typeAsTarget()
|
|
||||||
{
|
|
||||||
return TARGET_STACKACTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
Interruptible(GameObserver* observer, int inID = 0, bool hasFocus = false)
|
Interruptible(GameObserver* observer, int inID = 0, bool hasFocus = false)
|
||||||
: PlayGuiObject(40, x, y, inID, hasFocus), Targetable(observer), state(NOT_RESOLVED), display(0), source(NULL)
|
: PlayGuiObject(40, x, y, inID, hasFocus), Targetable(observer), state(NOT_RESOLVED), display(0), source(NULL)
|
||||||
{
|
{
|
||||||
@@ -139,7 +134,7 @@ public:
|
|||||||
Interruptible * getNextInterruptible(Interruptible * previous, int type);
|
Interruptible * getNextInterruptible(Interruptible * previous, int type);
|
||||||
Spell * getNextSpellTarget(Spell * previous = 0);
|
Spell * getNextSpellTarget(Spell * previous = 0);
|
||||||
Damage * getNextDamageTarget(Damage * previous = 0);
|
Damage * getNextDamageTarget(Damage * previous = 0);
|
||||||
Targetable * getNextTarget(Targetable * previous = 0, int type = -1);
|
Targetable * getNextTarget(Targetable * previous = 0);
|
||||||
int getNbTargets();
|
int getNbTargets();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ public:
|
|||||||
MTGCardInstance * changeController(Player * newcontroller);
|
MTGCardInstance * changeController(Player * newcontroller);
|
||||||
Player * owner;
|
Player * owner;
|
||||||
Counters * counters;
|
Counters * counters;
|
||||||
int typeAsTarget(){return TARGET_CARD;}
|
|
||||||
const string getDisplayName() const;
|
const string getDisplayName() const;
|
||||||
MTGCardInstance * target;
|
MTGCardInstance * target;
|
||||||
vector<Targetable *> backupTargets;
|
vector<Targetable *> backupTargets;
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ class MTGVampireRule: public PermanentAbility
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MTGVampireRule(GameObserver* observer, int _id);
|
MTGVampireRule(GameObserver* observer, int _id);
|
||||||
map<MTGCardInstance*,vector<MTGCardInstance*> > victems;
|
map<MTGCardInstance*,vector<MTGCardInstance*> > victims;
|
||||||
int receiveEvent(WEvent * event);
|
int receiveEvent(WEvent * event);
|
||||||
virtual ostream& toString(ostream& out) const;
|
virtual ostream& toString(ostream& out) const;
|
||||||
virtual MTGVampireRule * clone() const;
|
virtual MTGVampireRule * clone() const;
|
||||||
|
|||||||
@@ -48,10 +48,6 @@ public:
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
const string getDisplayName() const;
|
const string getDisplayName() const;
|
||||||
int typeAsTarget()
|
|
||||||
{
|
|
||||||
return TARGET_PLAYER;
|
|
||||||
}
|
|
||||||
|
|
||||||
int afterDamage();
|
int afterDamage();
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
#ifndef _TARGETABLE_H_
|
#ifndef _TARGETABLE_H_
|
||||||
#define _TARGETABLE_H_
|
#define _TARGETABLE_H_
|
||||||
|
|
||||||
#define TARGET_CARD 1
|
|
||||||
#define TARGET_PLAYER 2
|
|
||||||
#define TARGET_STACKACTION 3
|
|
||||||
|
|
||||||
class Targetable
|
class Targetable
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
GameObserver* observer;
|
GameObserver* observer;
|
||||||
public:
|
public:
|
||||||
Targetable(GameObserver* observer) : observer(observer) {};
|
Targetable(GameObserver* observer) : observer(observer) {};
|
||||||
virtual int typeAsTarget() = 0;
|
|
||||||
virtual const string getDisplayName() const = 0;
|
virtual const string getDisplayName() const = 0;
|
||||||
inline GameObserver* getObserver() { return observer; };
|
inline GameObserver* getObserver() { return observer; };
|
||||||
virtual void setObserver(GameObserver*g) { observer = g; };
|
virtual void setObserver(GameObserver*g) { observer = g; };
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ using std::vector;
|
|||||||
|
|
||||||
class TargetsList
|
class TargetsList
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
size_t iterateTarget(Targetable * previous);
|
||||||
public:
|
public:
|
||||||
TargetsList();
|
TargetsList();
|
||||||
TargetsList(Targetable * _targets[], int nbtargets);
|
TargetsList(Targetable * _targets[], int nbtargets);
|
||||||
@@ -28,7 +30,7 @@ public:
|
|||||||
Interruptible * getNextInterruptible(Interruptible * previous, int type);
|
Interruptible * getNextInterruptible(Interruptible * previous, int type);
|
||||||
Spell * getNextSpellTarget(Spell * previous = 0);
|
Spell * getNextSpellTarget(Spell * previous = 0);
|
||||||
Damage * getNextDamageTarget(Damage * previous = 0);
|
Damage * getNextDamageTarget(Damage * previous = 0);
|
||||||
Targetable * getNextTarget(Targetable * previous = 0, int type = -1);
|
Targetable * getNextTarget(Targetable * previous = 0);
|
||||||
void initTargets()
|
void initTargets()
|
||||||
{
|
{
|
||||||
targets.clear();
|
targets.clear();
|
||||||
|
|||||||
@@ -98,24 +98,17 @@ int AIAction::clickMultiAct(vector<Targetable*>& actionTargets)
|
|||||||
}
|
}
|
||||||
ite++;
|
ite++;
|
||||||
}
|
}
|
||||||
owner->getRandomGenerator()->random_shuffle(actionTargets.begin(), actionTargets.end());
|
|
||||||
//shuffle to make it less predictable, otherwise ai will always seem to target from right to left. making it very obvious.
|
//shuffle to make it less predictable, otherwise ai will always seem to target from right to left. making it very obvious.
|
||||||
for(int k = 0;k < int(actionTargets.size());k++)
|
owner->getRandomGenerator()->random_shuffle(actionTargets.begin(), actionTargets.end());
|
||||||
|
|
||||||
|
for(int k = 0 ;k < int(actionTargets.size()) && k < tc->maxtargets; k++)
|
||||||
{
|
{
|
||||||
int type = actionTargets[k]->typeAsTarget();
|
if (MTGCardInstance * card = dynamic_cast<MTGCardInstance *>(actionTargets[k]))
|
||||||
switch (type)
|
|
||||||
{
|
{
|
||||||
case TARGET_CARD:
|
if(k+1 == int(actionTargets.size()))
|
||||||
{
|
tc->done = true;
|
||||||
if(k < tc->maxtargets)
|
g->cardClick(card);
|
||||||
{
|
|
||||||
MTGCardInstance * card = ((MTGCardInstance *) actionTargets[k]);
|
|
||||||
if(k+1 == int(actionTargets.size()))
|
|
||||||
tc->done = true;
|
|
||||||
g->cardClick(card);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tc->attemptsToFill++;
|
tc->attemptsToFill++;
|
||||||
@@ -158,16 +151,15 @@ int AIPlayer::clickMultiTarget(TargetChooser * tc, vector<Targetable*>& potentia
|
|||||||
vector<Targetable*>::iterator ite = potentialTargets.begin();
|
vector<Targetable*>::iterator ite = potentialTargets.begin();
|
||||||
while(ite != potentialTargets.end())
|
while(ite != potentialTargets.end())
|
||||||
{
|
{
|
||||||
MTGCardInstance * card = ((MTGCardInstance *) (*ite));
|
MTGCardInstance * card = dynamic_cast<MTGCardInstance *>(*ite);
|
||||||
Player * pTarget = (Player*)(*ite);
|
if(card && card == tc->source)//if the source is part of the targetting deal with it first. second click is "confirming click".
|
||||||
if(card && card == (MTGCardInstance*)tc->source)//if the source is part of the targetting deal with it first. second click is "confirming click".
|
|
||||||
{
|
{
|
||||||
clickstream.push(NEW AIAction(this, card));
|
clickstream.push(NEW AIAction(this, card));
|
||||||
DebugTrace("Ai clicked source as a target: " << (card ? card->name : "None" ) << endl );
|
DebugTrace("Ai clicked source as a target: " << (card ? card->name : "None" ) << endl );
|
||||||
ite = potentialTargets.erase(ite);
|
ite = potentialTargets.erase(ite);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(pTarget && pTarget->typeAsTarget() == TARGET_PLAYER)
|
else if(Player * pTarget = dynamic_cast<Player *>(*ite))
|
||||||
{
|
{
|
||||||
clickstream.push(NEW AIAction(this, pTarget));
|
clickstream.push(NEW AIAction(this, pTarget));
|
||||||
DebugTrace("Ai clicked Player as a target");
|
DebugTrace("Ai clicked Player as a target");
|
||||||
@@ -192,26 +184,18 @@ int AIPlayer::clickMultiTarget(TargetChooser * tc, vector<Targetable*>& potentia
|
|||||||
|
|
||||||
int AIPlayer::clickSingleTarget(TargetChooser * tc, vector<Targetable*>& potentialTargets, MTGCardInstance * chosenCard)
|
int AIPlayer::clickSingleTarget(TargetChooser * tc, vector<Targetable*>& potentialTargets, MTGCardInstance * chosenCard)
|
||||||
{
|
{
|
||||||
int i = randomGenerator.random() % potentialTargets.size();
|
int i = randomGenerator.random() % potentialTargets.size();
|
||||||
int type = potentialTargets[i]->typeAsTarget();
|
|
||||||
switch (type)
|
if(MTGCardInstance * card = dynamic_cast<MTGCardInstance *>(potentialTargets[i]))
|
||||||
{
|
{
|
||||||
case TARGET_CARD:
|
if (!chosenCard)
|
||||||
{
|
clickstream.push(NEW AIAction(this, card));
|
||||||
if(!chosenCard)
|
}
|
||||||
{
|
else if(Player * player = dynamic_cast<Player *>(potentialTargets[i]))
|
||||||
MTGCardInstance * card = ((MTGCardInstance *) potentialTargets[i]);
|
{
|
||||||
clickstream.push(NEW AIAction(this, card));
|
clickstream.push(NEW AIAction(this, player));
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
case TARGET_PLAYER:
|
|
||||||
{
|
|
||||||
Player * player = ((Player *) potentialTargets[i]);
|
|
||||||
clickstream.push(NEW AIAction(this, player));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ Player * OrderedAIAction::getPlayerTarget()
|
|||||||
if (playerAbilityTarget)
|
if (playerAbilityTarget)
|
||||||
return (Player *)playerAbilityTarget;
|
return (Player *)playerAbilityTarget;
|
||||||
|
|
||||||
if (target && target->typeAsTarget() == TARGET_PLAYER)
|
|
||||||
return (Player *)target;
|
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,9 +405,9 @@ int OrderedAIAction::getEfficiency()
|
|||||||
AbilityFactory af(g);
|
AbilityFactory af(g);
|
||||||
int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY);
|
int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY);
|
||||||
|
|
||||||
if(_t->typeAsTarget() == TARGET_CARD)
|
if(MTGCardInstance * cTarget = dynamic_cast<MTGCardInstance *>(_t))
|
||||||
{
|
{
|
||||||
if((suggestion == BAKA_EFFECT_BAD && ((MTGCardInstance*)_t)->controller() == p) || (suggestion == BAKA_EFFECT_GOOD && ((MTGCardInstance*)_t)->controller() != p))
|
if((suggestion == BAKA_EFFECT_BAD && (cTarget)->controller() == p) || (suggestion == BAKA_EFFECT_GOOD && (cTarget)->controller() != p))
|
||||||
efficiency = 0;
|
efficiency = 0;
|
||||||
}
|
}
|
||||||
else if ((suggestion == BAKA_EFFECT_BAD && _t == p) || (suggestion == BAKA_EFFECT_GOOD && _t != p))
|
else if ((suggestion == BAKA_EFFECT_BAD && _t == p) || (suggestion == BAKA_EFFECT_GOOD && _t != p))
|
||||||
@@ -549,16 +546,16 @@ int OrderedAIAction::getEfficiency()
|
|||||||
}
|
}
|
||||||
else if (AAProliferate * aap = dynamic_cast<AAProliferate *>(a))
|
else if (AAProliferate * aap = dynamic_cast<AAProliferate *>(a))
|
||||||
{
|
{
|
||||||
if (aap && target && target->typeAsTarget() == TARGET_PLAYER && (Player*)target != p)
|
if (playerAbilityTarget && playerAbilityTarget != p)
|
||||||
{
|
{
|
||||||
efficiency = 60;//ai determines if the counters are good or bad on menu check.
|
efficiency = 60;//ai determines if the counters are good or bad on menu check.
|
||||||
}
|
}
|
||||||
else if (aap)
|
else
|
||||||
efficiency = 90;
|
efficiency = 90;
|
||||||
}
|
}
|
||||||
else if (AAAlterPoison * aaap = dynamic_cast<AAAlterPoison *>(a))
|
else if (AAAlterPoison * aaap = dynamic_cast<AAAlterPoison *>(a))
|
||||||
{
|
{
|
||||||
if (aaap && target && target->typeAsTarget() == TARGET_PLAYER && (Player*)target != p)
|
if (playerAbilityTarget && playerAbilityTarget != p)
|
||||||
{
|
{
|
||||||
efficiency = 90;
|
efficiency = 90;
|
||||||
}
|
}
|
||||||
@@ -566,7 +563,7 @@ int OrderedAIAction::getEfficiency()
|
|||||||
else if (ATokenCreator * atc = dynamic_cast<ATokenCreator *>(a))
|
else if (ATokenCreator * atc = dynamic_cast<ATokenCreator *>(a))
|
||||||
{
|
{
|
||||||
efficiency = 80;
|
efficiency = 80;
|
||||||
if(atc && atc->name.length() && atc->sabilities.length() && atc->types.size() && p->game->inPlay->findByName(atc->name))
|
if(atc->name.length() && atc->sabilities.length() && atc->types.size() && p->game->inPlay->findByName(atc->name))
|
||||||
{
|
{
|
||||||
list<int>::iterator it;
|
list<int>::iterator it;
|
||||||
for (it = atc->types.begin(); it != atc->types.end(); it++)
|
for (it = atc->types.begin(); it != atc->types.end(); it++)
|
||||||
@@ -1196,23 +1193,22 @@ int AIPlayerBaka::createAbilityTargets(MTGAbility * a, MTGCardInstance * c, Rank
|
|||||||
a->getActionTc()->done = true;
|
a->getActionTc()->done = true;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int targetThis = 0;
|
|
||||||
while(potentialTargets.size())
|
while(potentialTargets.size())
|
||||||
{
|
{
|
||||||
OrderedAIAction * check = NULL;
|
OrderedAIAction * check = NULL;
|
||||||
|
|
||||||
MTGCardInstance * targeting = dynamic_cast<MTGCardInstance*>(potentialTargets[0]);
|
MTGCardInstance * cTargeting = dynamic_cast<MTGCardInstance*>(potentialTargets[0]);
|
||||||
if(targeting && targeting->typeAsTarget() == TARGET_CARD)
|
if(cTargeting)
|
||||||
check = NEW OrderedAIAction(this, a,c,targeting);
|
check = NEW OrderedAIAction(this, a,c,cTargeting);
|
||||||
|
|
||||||
Player * ptargeting = dynamic_cast<Player*>(potentialTargets[0]);
|
Player * pTargeting = dynamic_cast<Player*>(potentialTargets[0]);
|
||||||
if(ptargeting && ptargeting->typeAsTarget() == TARGET_PLAYER)
|
if(pTargeting)
|
||||||
check = NEW OrderedAIAction(this, a,ptargeting,c);
|
check = NEW OrderedAIAction(this, a,pTargeting,c);
|
||||||
|
|
||||||
targetThis = getEfficiency(check);
|
int targetThis = getEfficiency(check);
|
||||||
if(targetThis && ptargeting && ptargeting->typeAsTarget() == TARGET_PLAYER)
|
if(targetThis && pTargeting)
|
||||||
{
|
{
|
||||||
OrderedAIAction aiAction(this, a,ptargeting,c);
|
OrderedAIAction aiAction(this, a,pTargeting,c);
|
||||||
ranking[aiAction] = 1;
|
ranking[aiAction] = 1;
|
||||||
}
|
}
|
||||||
if(targetThis)
|
if(targetThis)
|
||||||
@@ -1223,7 +1219,9 @@ int AIPlayerBaka::createAbilityTargets(MTGAbility * a, MTGCardInstance * c, Rank
|
|||||||
if(!realTargets.size() || (int(realTargets.size()) < a->getActionTc()->maxtargets && a->getActionTc()->targetMin))
|
if(!realTargets.size() || (int(realTargets.size()) < a->getActionTc()->maxtargets && a->getActionTc()->targetMin))
|
||||||
return 0;
|
return 0;
|
||||||
OrderedAIAction aiAction(this, a, c,realTargets);
|
OrderedAIAction aiAction(this, a, c,realTargets);
|
||||||
|
|
||||||
aiAction.target = dynamic_cast<MTGCardInstance*>(realTargets[0]);
|
aiAction.target = dynamic_cast<MTGCardInstance*>(realTargets[0]);
|
||||||
|
aiAction.playerAbilityTarget = dynamic_cast<Player*>(realTargets[0]);
|
||||||
ranking[aiAction] = 1;
|
ranking[aiAction] = 1;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
@@ -1508,6 +1506,29 @@ int AIPlayerBaka::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCard
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Returns -1 if error, a number between 0 and 100 otherwise
|
||||||
|
int AIPlayerBaka::getEfficiency(MTGAbility * ability)
|
||||||
|
{
|
||||||
|
if (!ability)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
OrderedAIAction * check = NULL;
|
||||||
|
|
||||||
|
if(MTGCardInstance * cTarget = dynamic_cast<MTGCardInstance *>(ability->target))
|
||||||
|
check = NEW OrderedAIAction(this, ability, ability->source, cTarget);
|
||||||
|
else if(Player * pTarget = dynamic_cast<Player *>(ability->target))
|
||||||
|
check = NEW OrderedAIAction(this, ability, pTarget, ability->source);
|
||||||
|
else
|
||||||
|
check = NEW OrderedAIAction(this, ability, ability->source);
|
||||||
|
|
||||||
|
if (!check)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int result = getEfficiency(check);
|
||||||
|
SAFE_DELETE(check);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
int AIPlayerBaka::selectMenuOption()
|
int AIPlayerBaka::selectMenuOption()
|
||||||
{
|
{
|
||||||
ActionLayer * object = observer->mLayers->actionLayer();
|
ActionLayer * object = observer->mLayers->actionLayer();
|
||||||
@@ -1515,10 +1536,10 @@ int AIPlayerBaka::selectMenuOption()
|
|||||||
if (object->menuObject)
|
if (object->menuObject)
|
||||||
{
|
{
|
||||||
int checkedLast = 0;
|
int checkedLast = 0;
|
||||||
int checked = 0;
|
|
||||||
MenuAbility * currentMenu = NULL;
|
|
||||||
if(object->abilitiesMenu->isMultipleChoice && object->currentActionCard)
|
if(object->abilitiesMenu->isMultipleChoice && object->currentActionCard)
|
||||||
{
|
{
|
||||||
|
MenuAbility * currentMenu = NULL;
|
||||||
for(size_t m = object->mObjects.size()-1;m > 0;m--)
|
for(size_t m = object->mObjects.size()-1;m > 0;m--)
|
||||||
{
|
{
|
||||||
MenuAbility * ability = dynamic_cast<MenuAbility *>(object->mObjects[m]);
|
MenuAbility * ability = dynamic_cast<MenuAbility *>(object->mObjects[m]);
|
||||||
@@ -1531,60 +1552,28 @@ int AIPlayerBaka::selectMenuOption()
|
|||||||
if(currentMenu)
|
if(currentMenu)
|
||||||
for(unsigned int mk = 0;mk < currentMenu->abilities.size();mk++)
|
for(unsigned int mk = 0;mk < currentMenu->abilities.size();mk++)
|
||||||
{
|
{
|
||||||
MTGAbility * checkEff = NULL;
|
int checked = getEfficiency(currentMenu->abilities[mk]);
|
||||||
OrderedAIAction * check = NULL;
|
|
||||||
checkEff = currentMenu->abilities[mk];
|
|
||||||
if(checkEff)
|
|
||||||
{
|
|
||||||
if(checkEff->target && checkEff->target->typeAsTarget() == TARGET_CARD)
|
|
||||||
check = NEW OrderedAIAction(this, checkEff,checkEff->source,(MTGCardInstance*)checkEff->target);
|
|
||||||
else if(checkEff->target && checkEff->target->typeAsTarget() == TARGET_PLAYER)
|
|
||||||
check = NEW OrderedAIAction(this, checkEff,(Player*)checkEff->target,checkEff->source);
|
|
||||||
else
|
|
||||||
check = NEW OrderedAIAction(this, checkEff,checkEff->source);
|
|
||||||
}
|
|
||||||
if(check)
|
|
||||||
{
|
|
||||||
checked = getEfficiency(check);
|
|
||||||
SAFE_DELETE(check);
|
|
||||||
}
|
|
||||||
if(checked > 60 && checked > checkedLast)
|
if(checked > 60 && checked > checkedLast)
|
||||||
{
|
{
|
||||||
doThis = mk;
|
doThis = mk;
|
||||||
checkedLast = checked;
|
checkedLast = checked;
|
||||||
}
|
}
|
||||||
checked = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for(unsigned int k = 0;k < object->abilitiesMenu->mObjects.size();k++)
|
for(unsigned int k = 0;k < object->abilitiesMenu->mObjects.size();k++)
|
||||||
{
|
{
|
||||||
MTGAbility * checkEff = NULL;
|
if(object->abilitiesMenu->mObjects[k]->GetId() <= 0)
|
||||||
OrderedAIAction * check = NULL;
|
continue;
|
||||||
if(object->abilitiesMenu->mObjects[k]->GetId() >= 0)
|
|
||||||
checkEff = (MTGAbility *)object->mObjects[object->abilitiesMenu->mObjects[k]->GetId()];
|
MTGAbility * checkEff = (MTGAbility *)object->mObjects[object->abilitiesMenu->mObjects[k]->GetId()];
|
||||||
if(checkEff)
|
int checked = getEfficiency(checkEff);
|
||||||
{
|
|
||||||
Targetable * checkTarget = checkEff->target;
|
|
||||||
if(checkTarget && checkTarget->typeAsTarget() == TARGET_CARD)
|
|
||||||
check = NEW OrderedAIAction(this, checkEff,checkEff->source,(MTGCardInstance*)checkTarget);
|
|
||||||
else if(checkTarget && checkTarget->typeAsTarget() == TARGET_PLAYER)
|
|
||||||
check = NEW OrderedAIAction(this, checkEff,(Player*)checkTarget,checkEff->source);
|
|
||||||
else
|
|
||||||
check = NEW OrderedAIAction(this, checkEff,checkEff->source);
|
|
||||||
}
|
|
||||||
if(check)
|
|
||||||
{
|
|
||||||
checked = getEfficiency(check);
|
|
||||||
SAFE_DELETE(check);
|
|
||||||
}
|
|
||||||
if(checked > 60 && checked > checkedLast)
|
if(checked > 60 && checked > checkedLast)
|
||||||
{
|
{
|
||||||
doThis = k;
|
doThis = k;
|
||||||
checkedLast = checked;
|
checkedLast = checked;
|
||||||
}
|
}
|
||||||
checked = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,14 +39,14 @@ int ActionElement::getActivity()
|
|||||||
|
|
||||||
int ActionElement::isReactingToTargetClick(Targetable * object)
|
int ActionElement::isReactingToTargetClick(Targetable * object)
|
||||||
{
|
{
|
||||||
if (object && object->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * cObject = dynamic_cast<MTGCardInstance *>(object))
|
||||||
return isReactingToClick((MTGCardInstance *) object);
|
return isReactingToClick(cObject);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ActionElement::reactToTargetClick(Targetable * object)
|
int ActionElement::reactToTargetClick(Targetable * object)
|
||||||
{
|
{
|
||||||
if (object->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * cObject = dynamic_cast<MTGCardInstance *>(object))
|
||||||
return reactToClick((MTGCardInstance *) object);
|
return reactToClick(cObject);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,8 +148,7 @@ void StackAbility::Render()
|
|||||||
_target = t;
|
_target = t;
|
||||||
}
|
}
|
||||||
Damageable * target = NULL;
|
Damageable * target = NULL;
|
||||||
if (_target != ability->source && (_target->typeAsTarget() == TARGET_CARD || _target->typeAsTarget()
|
if (_target != ability->source && (dynamic_cast<MTGCardInstance *>(_target) || dynamic_cast<Player *>(_target)))
|
||||||
== TARGET_PLAYER))
|
|
||||||
{
|
{
|
||||||
target = (Damageable *) _target;
|
target = (Damageable *) _target;
|
||||||
}
|
}
|
||||||
@@ -346,11 +345,11 @@ Damage * Spell::getNextDamageTarget(Damage * previous)
|
|||||||
return NULL;
|
return NULL;
|
||||||
return tc->getNextDamageTarget(previous);
|
return tc->getNextDamageTarget(previous);
|
||||||
}
|
}
|
||||||
Targetable * Spell::getNextTarget(Targetable * previous, int type)
|
Targetable * Spell::getNextTarget(Targetable * previous)
|
||||||
{
|
{
|
||||||
if (!tc)
|
if (!tc)
|
||||||
return NULL;
|
return NULL;
|
||||||
return tc->getNextTarget(previous, type);
|
return tc->getNextTarget(previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Spell::getNbTargets()
|
int Spell::getNbTargets()
|
||||||
|
|||||||
@@ -568,19 +568,23 @@ int AAProliferate::resolve()
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
vector<MTGAbility*>pcounters;
|
vector<MTGAbility*>pcounters;
|
||||||
if(target->typeAsTarget() == TARGET_PLAYER && ((Player*)target)->poisonCount && target != source->controller())
|
|
||||||
|
Player * pTarget = dynamic_cast<Player *>(target);
|
||||||
|
MTGCardInstance * cTarget = dynamic_cast<MTGCardInstance *>(target);
|
||||||
|
|
||||||
|
if(pTarget && pTarget->poisonCount && pTarget != source->controller())
|
||||||
{
|
{
|
||||||
MTGAbility * a = NEW AAAlterPoison(game, game->mLayers->actionLayer()->getMaxId(), source, target, 1, NULL);
|
MTGAbility * a = NEW AAAlterPoison(game, game->mLayers->actionLayer()->getMaxId(), source, target, 1, NULL);
|
||||||
a->oneShot = true;
|
a->oneShot = true;
|
||||||
pcounters.push_back(a);
|
pcounters.push_back(a);
|
||||||
}
|
}
|
||||||
else if (target->typeAsTarget() == TARGET_CARD && ((MTGCardInstance*)target)->counters)
|
else if (cTarget && cTarget->counters)
|
||||||
{
|
{
|
||||||
Counters * counters = ((MTGCardInstance*)target)->counters;
|
Counters * counters = cTarget->counters;
|
||||||
for(size_t i = 0; i < counters->counters.size(); ++i)
|
for(size_t i = 0; i < counters->counters.size(); ++i)
|
||||||
{
|
{
|
||||||
Counter * counter = counters->counters[i];
|
Counter * counter = counters->counters[i];
|
||||||
MTGAbility * a = NEW AACounter(game, game->mLayers->actionLayer()->getMaxId(), source, (MTGCardInstance*)target,"", counter->name.c_str(), counter->power, counter->toughness, 1,0);
|
MTGAbility * a = NEW AACounter(game, game->mLayers->actionLayer()->getMaxId(), source, cTarget,"", counter->name.c_str(), counter->power, counter->toughness, 1,0);
|
||||||
a->oneShot = true;
|
a->oneShot = true;
|
||||||
pcounters.push_back(a);
|
pcounters.push_back(a);
|
||||||
}
|
}
|
||||||
@@ -651,13 +655,13 @@ int AAFizzler::resolve()
|
|||||||
//ai is casting a spell from its hand to fizzle.
|
//ai is casting a spell from its hand to fizzle.
|
||||||
target = stack->getActionElementFromCard(source->target);
|
target = stack->getActionElementFromCard(source->target);
|
||||||
}
|
}
|
||||||
else if(target->typeAsTarget() == TARGET_CARD)
|
else if(MTGCardInstance * cTarget = dynamic_cast<MTGCardInstance *>(target))
|
||||||
{
|
{
|
||||||
//ai targeted using an ability on a card to fizzle.
|
//ai targeted using an ability on a card to fizzle.
|
||||||
target = stack->getActionElementFromCard((MTGCardInstance*)target);
|
target = stack->getActionElementFromCard(cTarget);
|
||||||
}
|
}
|
||||||
Spell * sTarget = (Spell *) target;
|
Spell * sTarget = (Spell *) target;
|
||||||
MTGCardInstance* sCard = (MTGCardInstance*)sTarget->source;
|
MTGCardInstance* sCard = sTarget->source;
|
||||||
if(!sCard || !sTarget || sCard->has(Constants::NOFIZZLE))
|
if(!sCard || !sTarget || sCard->has(Constants::NOFIZZLE))
|
||||||
return 0;
|
return 0;
|
||||||
stack->Fizzle(sTarget);
|
stack->Fizzle(sTarget);
|
||||||
@@ -1062,7 +1066,7 @@ int AADynamic::resolve()
|
|||||||
_target = OriginalSrc->controller()->opponent();//looking at controllers opponent for amount
|
_target = OriginalSrc->controller()->opponent();//looking at controllers opponent for amount
|
||||||
if(!_target)
|
if(!_target)
|
||||||
return 0;
|
return 0;
|
||||||
while (_target->typeAsTarget() == TARGET_CARD && ((MTGCardInstance *)_target)->next)
|
while (dynamic_cast<MTGCardInstance *>(_target) && ((MTGCardInstance *)_target)->next)
|
||||||
_target = ((MTGCardInstance *)_target)->next;
|
_target = ((MTGCardInstance *)_target)->next;
|
||||||
|
|
||||||
//find the amount variables that will be used
|
//find the amount variables that will be used
|
||||||
@@ -1178,7 +1182,7 @@ int AADynamic::resolve()
|
|||||||
}
|
}
|
||||||
if (_target)
|
if (_target)
|
||||||
{
|
{
|
||||||
while (_target->typeAsTarget() == TARGET_CARD && ((MTGCardInstance *)_target)->next)
|
while (dynamic_cast<MTGCardInstance *>(_target) && ((MTGCardInstance *)_target)->next)
|
||||||
_target = ((MTGCardInstance *)_target)->next;
|
_target = ((MTGCardInstance *)_target)->next;
|
||||||
if(sourceamount < 0)
|
if(sourceamount < 0)
|
||||||
sourceamount = 0;
|
sourceamount = 0;
|
||||||
@@ -1306,7 +1310,7 @@ int AADynamic::resolve()
|
|||||||
}
|
}
|
||||||
case DYNAMIC_ABILITY_EFFECT_COUNTERSONEONE:
|
case DYNAMIC_ABILITY_EFFECT_COUNTERSONEONE:
|
||||||
{
|
{
|
||||||
if(_target->typeAsTarget() != TARGET_CARD)
|
if(!dynamic_cast<MTGCardInstance *>(_target))
|
||||||
_target = OriginalSrc;
|
_target = OriginalSrc;
|
||||||
for(int j = 0;j < sourceamount;j++)
|
for(int j = 0;j < sourceamount;j++)
|
||||||
((MTGCardInstance*)_target)->counters->addCounter(1,1);
|
((MTGCardInstance*)_target)->counters->addCounter(1,1);
|
||||||
@@ -2168,8 +2172,10 @@ int IfThenAbility::resolve()
|
|||||||
MTGAbility * a1 = delayedAbility->clone();
|
MTGAbility * a1 = delayedAbility->clone();
|
||||||
if (!a1)
|
if (!a1)
|
||||||
return 0;
|
return 0;
|
||||||
if(a1->target && a1->target->typeAsTarget() != TARGET_PLAYER)
|
|
||||||
a1->target = aTarget;
|
if(a1->target && !dynamic_cast<Player *>(a1->target))
|
||||||
|
a1->target = aTarget;
|
||||||
|
|
||||||
if(a1->oneShot)
|
if(a1->oneShot)
|
||||||
{
|
{
|
||||||
a1->resolve();
|
a1->resolve();
|
||||||
|
|||||||
@@ -1043,7 +1043,7 @@ MTGAbility * AbilityFactory::parseMagicLine(string s, int id, Spell * spell, MTG
|
|||||||
if (amp)
|
if (amp)
|
||||||
{
|
{
|
||||||
amp->setCost(cost);
|
amp->setCost(cost);
|
||||||
if (cost && card->typeAsTarget() == TARGET_CARD)
|
if (cost)
|
||||||
cost->setExtraCostsAction(a, card);
|
cost->setExtraCostsAction(a, card);
|
||||||
amp->oneShot = 0;
|
amp->oneShot = 0;
|
||||||
amp->tap = doTap;
|
amp->tap = doTap;
|
||||||
@@ -3620,10 +3620,13 @@ Player * MTGAbility::getPlayerFromTarget(Targetable * target)
|
|||||||
if (!target)
|
if (!target)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (target->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * cTarget = dynamic_cast<MTGCardInstance *>(target))
|
||||||
return ((MTGCardInstance *) target)->controller();
|
return cTarget->controller();
|
||||||
|
|
||||||
return (Player *) target;
|
if (Player * cPlayer = dynamic_cast<Player *>(target))
|
||||||
|
return cPlayer;
|
||||||
|
|
||||||
|
return ((Interruptible *) target)->source->controller();
|
||||||
}
|
}
|
||||||
|
|
||||||
Player * MTGAbility::getPlayerFromDamageable(Damageable * target)
|
Player * MTGAbility::getPlayerFromDamageable(Damageable * target)
|
||||||
@@ -3770,8 +3773,8 @@ int ActivatedAbility::reactToTargetClick(Targetable * object)
|
|||||||
ManaCost * cost = getCost();
|
ManaCost * cost = getCost();
|
||||||
if (cost)
|
if (cost)
|
||||||
{
|
{
|
||||||
if (object->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * cObject = dynamic_cast<MTGCardInstance *>(object))
|
||||||
cost->setExtraCostsAction(this, (MTGCardInstance *) object);
|
cost->setExtraCostsAction(this, cObject);
|
||||||
if (!cost->isExtraPaymentSet())
|
if (!cost->isExtraPaymentSet())
|
||||||
{
|
{
|
||||||
game->mExtraPayment = cost->extraCosts;
|
game->mExtraPayment = cost->extraCosts;
|
||||||
@@ -3891,8 +3894,9 @@ TargetAbility::TargetAbility(GameObserver* observer, int id, MTGCardInstance * c
|
|||||||
|
|
||||||
int TargetAbility::reactToTargetClick(Targetable * object)
|
int TargetAbility::reactToTargetClick(Targetable * object)
|
||||||
{
|
{
|
||||||
if (object->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * cObject = dynamic_cast<MTGCardInstance *>(object))
|
||||||
return reactToClick((MTGCardInstance *) object);
|
return reactToClick(cObject);
|
||||||
|
|
||||||
if (waitingForAnswer)
|
if (waitingForAnswer)
|
||||||
{
|
{
|
||||||
if (tc->toggleTarget(object) == TARGET_OK_FULL)
|
if (tc->toggleTarget(object) == TARGET_OK_FULL)
|
||||||
@@ -3976,7 +3980,7 @@ int TargetAbility::resolve()
|
|||||||
|
|
||||||
ability->target = t;
|
ability->target = t;
|
||||||
//do nothing if the target controller responded by phasing out the target.
|
//do nothing if the target controller responded by phasing out the target.
|
||||||
if (t->typeAsTarget() == TARGET_CARD && ((MTGCardInstance*)t)->isPhased)
|
if (dynamic_cast<MTGCardInstance *>(t) && ((MTGCardInstance*)t)->isPhased)
|
||||||
return 0;
|
return 0;
|
||||||
if (ability->oneShot)
|
if (ability->oneShot)
|
||||||
{
|
{
|
||||||
@@ -4575,21 +4579,12 @@ int AManaProducer::isReactingToClick(MTGCardInstance * _card, ManaCost * mana)
|
|||||||
int AManaProducer::resolve()
|
int AManaProducer::resolve()
|
||||||
{
|
{
|
||||||
Targetable * _target = getTarget();
|
Targetable * _target = getTarget();
|
||||||
Player * player;
|
Player * player = getPlayerFromTarget(_target);
|
||||||
if (_target)
|
if (!player)
|
||||||
{
|
return 0;
|
||||||
if (_target->typeAsTarget() == TARGET_CARD)
|
|
||||||
{
|
player->getManaPool()->add(output, source);
|
||||||
player = ((MTGCardInstance *) _target)->controller();
|
return 1;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
player = (Player *) _target;
|
|
||||||
}
|
|
||||||
player->getManaPool()->add(output, source);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int AManaProducer::reactToClick(MTGCardInstance * _card)
|
int AManaProducer::reactToClick(MTGCardInstance * _card)
|
||||||
@@ -4672,19 +4667,7 @@ Targetable * AbilityTP::getTarget()
|
|||||||
switch (who)
|
switch (who)
|
||||||
{
|
{
|
||||||
case TargetChooser::TARGET_CONTROLLER:
|
case TargetChooser::TARGET_CONTROLLER:
|
||||||
if (target)
|
return getPlayerFromTarget(target);
|
||||||
{
|
|
||||||
switch (target->typeAsTarget())
|
|
||||||
{
|
|
||||||
case TARGET_CARD:
|
|
||||||
return ((MTGCardInstance *) target)->controller();
|
|
||||||
case TARGET_STACKACTION:
|
|
||||||
return ((Interruptible *) target)->source->controller();
|
|
||||||
default:
|
|
||||||
return (Player *) target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
case TargetChooser::CONTROLLER:
|
case TargetChooser::CONTROLLER:
|
||||||
return source->controller();
|
return source->controller();
|
||||||
case TargetChooser::OPPONENT:
|
case TargetChooser::OPPONENT:
|
||||||
@@ -4709,19 +4692,7 @@ Targetable * ActivatedAbilityTP::getTarget()
|
|||||||
switch (who)
|
switch (who)
|
||||||
{
|
{
|
||||||
case TargetChooser::TARGET_CONTROLLER:
|
case TargetChooser::TARGET_CONTROLLER:
|
||||||
if (target)
|
return getPlayerFromTarget(target);
|
||||||
{
|
|
||||||
switch (target->typeAsTarget())
|
|
||||||
{
|
|
||||||
case TARGET_CARD:
|
|
||||||
return ((MTGCardInstance *) target)->controller();
|
|
||||||
case TARGET_STACKACTION:
|
|
||||||
return ((Interruptible *) target)->source->controller();
|
|
||||||
default:
|
|
||||||
return (Player *) target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
case TargetChooser::CONTROLLER:
|
case TargetChooser::CONTROLLER:
|
||||||
return source->controller();
|
return source->controller();
|
||||||
case TargetChooser::OPPONENT:
|
case TargetChooser::OPPONENT:
|
||||||
@@ -4747,19 +4718,7 @@ Targetable * InstantAbilityTP::getTarget()
|
|||||||
switch (who)
|
switch (who)
|
||||||
{
|
{
|
||||||
case TargetChooser::TARGET_CONTROLLER:
|
case TargetChooser::TARGET_CONTROLLER:
|
||||||
if (target)
|
return getPlayerFromTarget(target);
|
||||||
{
|
|
||||||
switch (target->typeAsTarget())
|
|
||||||
{
|
|
||||||
case TARGET_CARD:
|
|
||||||
return ((MTGCardInstance *) target)->controller();
|
|
||||||
case TARGET_STACKACTION:
|
|
||||||
return ((Interruptible *) target)->source->controller();
|
|
||||||
default:
|
|
||||||
return (Player *) target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
case TargetChooser::CONTROLLER:
|
case TargetChooser::CONTROLLER:
|
||||||
return source->controller();
|
return source->controller();
|
||||||
case TargetChooser::OPPONENT:
|
case TargetChooser::OPPONENT:
|
||||||
|
|||||||
@@ -1877,52 +1877,53 @@ PermanentAbility(observer, _id)
|
|||||||
|
|
||||||
int MTGVampireRule::receiveEvent(WEvent * event)
|
int MTGVampireRule::receiveEvent(WEvent * event)
|
||||||
{
|
{
|
||||||
WEventDamage * e = dynamic_cast<WEventDamage *> (event);
|
if (WEventDamage * e = dynamic_cast<WEventDamage *> (event))
|
||||||
WEventZoneChange * z = dynamic_cast<WEventZoneChange *> (event);
|
|
||||||
WEventPhaseChange * pe = dynamic_cast<WEventPhaseChange*>(event);
|
|
||||||
if (e)
|
|
||||||
{
|
{
|
||||||
if(!e->damage->damage)
|
if(!e->damage->damage)
|
||||||
return 0;
|
return 0;
|
||||||
if (!e->damage->target)
|
if (!e->damage->target)
|
||||||
return 0;
|
return 0;
|
||||||
if(e->damage->target->typeAsTarget() != TARGET_CARD)
|
|
||||||
|
MTGCardInstance * newVictim = dynamic_cast<MTGCardInstance*>(e->damage->target);
|
||||||
|
if(!newVictim)
|
||||||
return 0;
|
return 0;
|
||||||
MTGCardInstance * newVictem = (MTGCardInstance*)(e->damage->target);
|
|
||||||
MTGCardInstance * vampire = (MTGCardInstance*)(e->damage->source);
|
MTGCardInstance * vampire = (MTGCardInstance*)(e->damage->source);
|
||||||
|
|
||||||
victems[newVictem].push_back(vampire);
|
victims[newVictim].push_back(vampire);
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (z)
|
else if ( WEventZoneChange * z = dynamic_cast<WEventZoneChange *> (event))
|
||||||
{
|
{
|
||||||
MTGCardInstance * card = z->card->previous;
|
MTGCardInstance * card = z->card->previous;
|
||||||
if(card && victems[card].empty())
|
if(card && victims[card].empty())
|
||||||
return 0;
|
return 0;
|
||||||
std::sort(victems[card].begin(), victems[card].end());
|
|
||||||
victems[card].erase(std::unique(victems[card].begin(), victems[card].end()), victems[card].end());
|
|
||||||
//sort and remove duplicates, we only want one event of a vampire damaging a card stored per victem.
|
//sort and remove duplicates, we only want one event of a vampire damaging a card stored per victem.
|
||||||
for(unsigned int w = 0;w < victems[card].size();w++)
|
std::sort(victims[card].begin(), victims[card].end());
|
||||||
|
victims[card].erase(std::unique(victims[card].begin(), victims[card].end()), victims[card].end());
|
||||||
|
|
||||||
|
for(unsigned int w = 0;w < victims[card].size();w++)
|
||||||
{
|
{
|
||||||
if(victems[card].at(w) == NULL)
|
if(victims[card].at(w) == NULL)
|
||||||
continue;
|
continue;
|
||||||
Player * p = card->controller();
|
Player * p = card->controller();
|
||||||
if (z->from == p->game->inPlay && z->to == p->game->graveyard)
|
if (z->from == p->game->inPlay && z->to == p->game->graveyard)
|
||||||
{
|
{
|
||||||
if(card == z->card->previous)
|
if(card == z->card->previous)
|
||||||
{
|
{
|
||||||
WEvent * e = NEW WEventVampire(card,victems[card].at(w),card);
|
WEvent * e = NEW WEventVampire(card,victims[card].at(w),card);
|
||||||
game->receiveEvent(e);
|
game->receiveEvent(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else if (pe)
|
else if (WEventPhaseChange * pe = dynamic_cast<WEventPhaseChange*>(event))
|
||||||
{
|
{
|
||||||
if( pe->from->id == Constants::MTG_PHASE_ENDOFTURN)
|
if( pe->from->id == Constants::MTG_PHASE_ENDOFTURN)
|
||||||
{
|
{
|
||||||
victems.clear();
|
victims.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -467,16 +467,17 @@ TargetChooser * TargetChooserFactory::createTargetChooser(string s, MTGCardInsta
|
|||||||
size_t start = attribute.find("share!");
|
size_t start = attribute.find("share!");
|
||||||
size_t end = attribute.rfind("!");
|
size_t end = attribute.rfind("!");
|
||||||
string CDtype = attribute.substr(start + 6,end - start);
|
string CDtype = attribute.substr(start + 6,end - start);
|
||||||
if( card && card->isSpell() && card->backupTargets.size() && card->backupTargets[0]->typeAsTarget() == TARGET_STACKACTION)
|
|
||||||
|
if( card && card->isSpell() && card->backupTargets.size())
|
||||||
{
|
{
|
||||||
//spells always store their targets in :targets[]
|
if (Spell * targetSpell = dynamic_cast<Spell *>(card->backupTargets[0]))
|
||||||
//however they are all erased as the spell resolves
|
{
|
||||||
//added a array to store these backups incase theyre needed
|
//spells always store their targets in :targets[]
|
||||||
//again for effects such as these.
|
//however they are all erased as the spell resolves
|
||||||
Spell * spell;
|
//added a array to store these backups incase theyre needed
|
||||||
spell = (Spell*)card->backupTargets[0];
|
//again for effects such as these.
|
||||||
if(spell)
|
card->target = targetSpell->source;
|
||||||
card->target = spell->source;
|
}
|
||||||
}
|
}
|
||||||
if( CDtype.find("name") != string::npos )
|
if( CDtype.find("name") != string::npos )
|
||||||
{
|
{
|
||||||
@@ -715,9 +716,8 @@ TargetChooser::TargetChooser(GameObserver *observer, MTGCardInstance * card, int
|
|||||||
bool TargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
bool TargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
||||||
{
|
{
|
||||||
if (!target) return false;
|
if (!target) return false;
|
||||||
if (target->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * card = dynamic_cast<MTGCardInstance *>(target))
|
||||||
{
|
{
|
||||||
MTGCardInstance * card = (MTGCardInstance *) target;
|
|
||||||
if (other)
|
if (other)
|
||||||
{
|
{
|
||||||
MTGCardInstance * tempcard = card;
|
MTGCardInstance * tempcard = card;
|
||||||
@@ -747,7 +747,9 @@ bool TargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (target->typeAsTarget() == TARGET_STACKACTION) return true;
|
else if (dynamic_cast<Interruptible *>(target))
|
||||||
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,10 +877,12 @@ CardTargetChooser::CardTargetChooser(GameObserver *observer, MTGCardInstance * _
|
|||||||
bool CardTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
bool CardTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
||||||
{
|
{
|
||||||
if (!target) return false;
|
if (!target) return false;
|
||||||
if (target->typeAsTarget() != TARGET_CARD) return false;
|
|
||||||
|
MTGCardInstance * card = dynamic_cast<MTGCardInstance *>(target);
|
||||||
|
if (!card) return false;
|
||||||
if (!nbzones && !TargetChooser::canTarget(target,withoutProtections)) return false;
|
if (!nbzones && !TargetChooser::canTarget(target,withoutProtections)) return false;
|
||||||
if (nbzones && !TargetZoneChooser::canTarget(target,withoutProtections)) return false;
|
if (nbzones && !TargetZoneChooser::canTarget(target,withoutProtections)) return false;
|
||||||
MTGCardInstance * card = (MTGCardInstance *) target;
|
|
||||||
while (card)
|
while (card)
|
||||||
{
|
{
|
||||||
if (card == validTarget) return true;
|
if (card == validTarget) return true;
|
||||||
@@ -953,9 +957,8 @@ void TypeTargetChooser::addType(int type)
|
|||||||
bool TypeTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
bool TypeTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
||||||
{
|
{
|
||||||
if (!TargetZoneChooser::canTarget(target,withoutProtections)) return false;
|
if (!TargetZoneChooser::canTarget(target,withoutProtections)) return false;
|
||||||
if (target->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * card = dynamic_cast<MTGCardInstance *>(target))
|
||||||
{
|
{
|
||||||
MTGCardInstance * card = (MTGCardInstance *) target;
|
|
||||||
for (int i = 0; i < nbtypes; i++)
|
for (int i = 0; i < nbtypes; i++)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -967,9 +970,9 @@ bool TypeTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (target->typeAsTarget() == TARGET_STACKACTION)
|
else if (Interruptible * action = dynamic_cast<Interruptible *>(target))
|
||||||
{
|
{
|
||||||
Interruptible * action = (Interruptible *) target;
|
|
||||||
if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
|
if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
|
||||||
{
|
{
|
||||||
Spell * spell = (Spell *) action;
|
Spell * spell = (Spell *) action;
|
||||||
@@ -1049,15 +1052,13 @@ DescriptorTargetChooser::DescriptorTargetChooser(GameObserver *observer, CardDes
|
|||||||
bool DescriptorTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
bool DescriptorTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
||||||
{
|
{
|
||||||
if (!TargetZoneChooser::canTarget(target,withoutProtections)) return false;
|
if (!TargetZoneChooser::canTarget(target,withoutProtections)) return false;
|
||||||
if (target->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * _target = dynamic_cast<MTGCardInstance *>(target))
|
||||||
{
|
{
|
||||||
MTGCardInstance * _target = (MTGCardInstance *) target;
|
|
||||||
if (cd->match(_target))
|
if (cd->match(_target))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (target->typeAsTarget() == TARGET_STACKACTION)
|
else if (Interruptible * action = dynamic_cast<Interruptible *>(target))
|
||||||
{
|
{
|
||||||
Interruptible * action = (Interruptible *) target;
|
|
||||||
if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
|
if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
|
||||||
{
|
{
|
||||||
Spell * spell = (Spell *) action;
|
Spell * spell = (Spell *) action;
|
||||||
@@ -1126,22 +1127,19 @@ int TargetZoneChooser::setAllZones()
|
|||||||
bool TargetZoneChooser::canTarget(Targetable * target,bool withoutProtections)
|
bool TargetZoneChooser::canTarget(Targetable * target,bool withoutProtections)
|
||||||
{
|
{
|
||||||
if (!TargetChooser::canTarget(target,withoutProtections)) return false;
|
if (!TargetChooser::canTarget(target,withoutProtections)) return false;
|
||||||
if (target->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * card = dynamic_cast<MTGCardInstance *>(target))
|
||||||
{
|
{
|
||||||
MTGCardInstance * card = (MTGCardInstance *) target;
|
|
||||||
for (int i = 0; i < nbzones; i++)
|
for (int i = 0; i < nbzones; i++)
|
||||||
{
|
{
|
||||||
if (zones[i] == MTGGameZone::ALL_ZONES) return true;
|
if (zones[i] == MTGGameZone::ALL_ZONES) return true;
|
||||||
if (MTGGameZone::intToZone(observer, zones[i], source, card)->hasCard(card)) return true;
|
if (MTGGameZone::intToZone(observer, zones[i], source, card)->hasCard(card)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (target->typeAsTarget() == TARGET_STACKACTION)
|
else if (Spell * spell = dynamic_cast<Spell *>(target))
|
||||||
{
|
{
|
||||||
DebugTrace("CHECKING INTERRUPTIBLE\n");
|
DebugTrace("CHECKING Spell\n");
|
||||||
Interruptible * action = (Interruptible *) target;
|
if (spell->state == NOT_RESOLVED)
|
||||||
if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
|
|
||||||
{
|
{
|
||||||
Spell * spell = (Spell *) action;
|
|
||||||
MTGCardInstance * card = spell->source;
|
MTGCardInstance * card = spell->source;
|
||||||
for (int i = 0; i < nbzones; i++)
|
for (int i = 0; i < nbzones; i++)
|
||||||
if (MTGGameZone::intToZone(Owner->getObserver(), zones[i], source, card)->hasCard(card)) return true;
|
if (MTGGameZone::intToZone(Owner->getObserver(), zones[i], source, card)->hasCard(card)) return true;
|
||||||
@@ -1217,7 +1215,9 @@ bool PlayerTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
|||||||
if (source && targetter && (targetter->controller() == targetter->controller())
|
if (source && targetter && (targetter->controller() == targetter->controller())
|
||||||
&& (targetter->controller()->game->inPlay->hasAbility(Constants::PLAYERSHROUD)) && targetter->controller()
|
&& (targetter->controller()->game->inPlay->hasAbility(Constants::PLAYERSHROUD)) && targetter->controller()
|
||||||
== target) return false;
|
== target) return false;
|
||||||
return (target->typeAsTarget() == TARGET_PLAYER) && (!p || p == (Player*) target);
|
|
||||||
|
Player * pTarget = dynamic_cast<Player *>(target);
|
||||||
|
return (pTarget && (!p || p == pTarget));
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerTargetChooser* PlayerTargetChooser::clone() const
|
PlayerTargetChooser* PlayerTargetChooser::clone() const
|
||||||
@@ -1251,7 +1251,7 @@ bool DamageableTargetChooser::canTarget(Targetable * target,bool withoutProtecti
|
|||||||
if (source && targetter && (targetter->controller() == targetter->controller())
|
if (source && targetter && (targetter->controller() == targetter->controller())
|
||||||
&& (targetter->controller()->game->inPlay->hasAbility(Constants::PLAYERSHROUD)) && targetter->controller()
|
&& (targetter->controller()->game->inPlay->hasAbility(Constants::PLAYERSHROUD)) && targetter->controller()
|
||||||
== target) return false;
|
== target) return false;
|
||||||
if (target->typeAsTarget() == TARGET_PLAYER)
|
if (dynamic_cast<Player *>(target))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1284,16 +1284,14 @@ SpellTargetChooser::SpellTargetChooser(GameObserver *observer, MTGCardInstance *
|
|||||||
|
|
||||||
bool SpellTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
bool SpellTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
||||||
{
|
{
|
||||||
MTGCardInstance * card = NULL;
|
Spell * spell = dynamic_cast<Spell *>(target);
|
||||||
if (target->typeAsTarget() == TARGET_STACKACTION)
|
if (!spell)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (spell->state == NOT_RESOLVED)
|
||||||
{
|
{
|
||||||
Interruptible * action = (Interruptible *) target;
|
MTGCardInstance * card = spell->source;
|
||||||
if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
|
if (card && (color == -1 || card->hasColor(color))) return true;
|
||||||
{
|
|
||||||
Spell * spell = (Spell *) action;
|
|
||||||
card = spell->source;
|
|
||||||
if (card && (color == -1 || card->hasColor(color))) return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -1329,19 +1327,16 @@ SpellOrPermanentTargetChooser::SpellOrPermanentTargetChooser(GameObserver *obser
|
|||||||
|
|
||||||
bool SpellOrPermanentTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
bool SpellOrPermanentTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
||||||
{
|
{
|
||||||
MTGCardInstance * card = NULL;
|
|
||||||
if (target->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * card = dynamic_cast<MTGCardInstance *>(target))
|
||||||
{
|
{
|
||||||
card = (MTGCardInstance *) target;
|
|
||||||
if (color == -1 || card->hasColor(color)) return TargetZoneChooser::canTarget(target,withoutProtections);
|
if (color == -1 || card->hasColor(color)) return TargetZoneChooser::canTarget(target,withoutProtections);
|
||||||
}
|
}
|
||||||
else if (target->typeAsTarget() == TARGET_STACKACTION)
|
else if (Spell * spell = dynamic_cast<Spell *>(target))
|
||||||
{
|
{
|
||||||
Interruptible * action = (Interruptible *) target;
|
if (spell->state == NOT_RESOLVED)
|
||||||
if (action->type == ACTION_SPELL && action->state == NOT_RESOLVED)
|
|
||||||
{
|
{
|
||||||
Spell * spell = (Spell *) action;
|
MTGCardInstance * card = spell->source;
|
||||||
card = spell->source;
|
|
||||||
if (card && (color == -1 || card->hasColor(color))) return true;
|
if (card && (color == -1 || card->hasColor(color))) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1377,14 +1372,11 @@ DamageTargetChooser::DamageTargetChooser(GameObserver *observer, MTGCardInstance
|
|||||||
|
|
||||||
bool DamageTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
bool DamageTargetChooser::canTarget(Targetable * target,bool withoutProtections)
|
||||||
{
|
{
|
||||||
MTGCardInstance * card = NULL;
|
if ( Damage * damage = dynamic_cast<Damage *>(target))
|
||||||
if (target->typeAsTarget() == TARGET_STACKACTION)
|
|
||||||
{
|
{
|
||||||
Interruptible * action = (Interruptible *) target;
|
if (damage->state == state || state == -1)
|
||||||
if (action->type == ACTION_DAMAGE && (action->state == state || state == -1))
|
|
||||||
{
|
{
|
||||||
Damage * damage = (Damage *) action;
|
MTGCardInstance * card = damage->source;
|
||||||
card = damage->source;
|
|
||||||
if (card && (color == -1 || card->hasColor(color))) return true;
|
if (card && (color == -1 || card->hasColor(color))) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1450,16 +1442,14 @@ bool TriggerTargetChooser::equals(TargetChooser * tc)
|
|||||||
/*Proliferate Target */
|
/*Proliferate Target */
|
||||||
bool ProliferateChooser::canTarget(Targetable * target,bool withoutProtections)
|
bool ProliferateChooser::canTarget(Targetable * target,bool withoutProtections)
|
||||||
{
|
{
|
||||||
if (target->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * card = dynamic_cast<MTGCardInstance*>(target))
|
||||||
{
|
{
|
||||||
MTGCardInstance * card = (MTGCardInstance*)target;
|
|
||||||
if(card->counters && card->counters->counters.empty())
|
if(card->counters && card->counters->counters.empty())
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (target->typeAsTarget() == TARGET_PLAYER)
|
else if (Player * p = dynamic_cast<Player*>(target))
|
||||||
{
|
{
|
||||||
Player * p = (Player*)target;
|
|
||||||
if(!p->poisonCount)
|
if(!p->poisonCount)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
@@ -1486,9 +1476,8 @@ bool ProliferateChooser::equals(TargetChooser * tc)
|
|||||||
/*parents or children Target */
|
/*parents or children Target */
|
||||||
bool ParentChildChooser::canTarget(Targetable * target,bool withoutProtections)
|
bool ParentChildChooser::canTarget(Targetable * target,bool withoutProtections)
|
||||||
{
|
{
|
||||||
if (target->typeAsTarget() == TARGET_CARD)
|
if (MTGCardInstance * card = dynamic_cast<MTGCardInstance*>(target))
|
||||||
{
|
{
|
||||||
MTGCardInstance * card = (MTGCardInstance*)target;
|
|
||||||
if(type == 1)
|
if(type == 1)
|
||||||
{
|
{
|
||||||
if(!source->childrenCards.size())
|
if(!source->childrenCards.size())
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ int TargetsList::addTarget(Targetable * target)
|
|||||||
if(!tc || (tc && tc->maxtargets == 1))
|
if(!tc || (tc && tc->maxtargets == 1))
|
||||||
{
|
{
|
||||||
//because this was originally coded with targets as an array
|
//because this was originally coded with targets as an array
|
||||||
//we have to add this condiational to insure that cards with single target effects
|
//we have to add this conditional to insure that cards with single target effects
|
||||||
//and abilities that seek the nextcardtarget still work correctly.
|
//and abilities that seek the nextcardtarget still work correctly.
|
||||||
targets.clear();
|
targets.clear();
|
||||||
targets.push_back(target);
|
targets.push_back(target);
|
||||||
@@ -76,46 +76,66 @@ int TargetsList::toggleTarget(Targetable * target)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Targetable * TargetsList::getNextTarget(Targetable * previous, int type)
|
size_t TargetsList::iterateTarget(Targetable * previous){
|
||||||
{
|
if (!previous)
|
||||||
int found = 0;
|
return 0;
|
||||||
if (!previous) found = 1;
|
|
||||||
for (size_t i = 0; i < targets.size(); i++)
|
for (size_t i = 0; i < targets.size(); i++) {
|
||||||
{
|
if (targets[i] == previous)
|
||||||
if (found && (type == -1 || targets[i]->typeAsTarget() == type))
|
return i + 1;
|
||||||
{
|
|
||||||
return targets[i];
|
|
||||||
}
|
|
||||||
if (targets[i] == previous) found = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return targets.size() + 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Targetable * TargetsList::getNextTarget(Targetable * previous)
|
||||||
|
{
|
||||||
|
size_t nextIndex = iterateTarget(previous);
|
||||||
|
|
||||||
|
if (nextIndex < targets.size())
|
||||||
|
return targets[nextIndex];
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
MTGCardInstance * TargetsList::getNextCardTarget(MTGCardInstance * previous)
|
MTGCardInstance * TargetsList::getNextCardTarget(MTGCardInstance * previous)
|
||||||
{
|
{
|
||||||
return ((MTGCardInstance *) getNextTarget(previous, TARGET_CARD));
|
size_t nextIndex = iterateTarget(previous);
|
||||||
|
for (size_t i = nextIndex; i < targets.size(); ++i)
|
||||||
|
{
|
||||||
|
if (MTGCardInstance * c = dynamic_cast<MTGCardInstance *>(targets[i]))
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Player * TargetsList::getNextPlayerTarget(Player * previous)
|
Player * TargetsList::getNextPlayerTarget(Player * previous)
|
||||||
{
|
{
|
||||||
return ((Player *) getNextTarget(previous, TARGET_PLAYER));
|
size_t nextIndex = iterateTarget(previous);
|
||||||
|
for (size_t i = nextIndex; i < targets.size(); ++i)
|
||||||
|
{
|
||||||
|
if (Player * p = dynamic_cast<Player *>(targets[i]))
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Interruptible * TargetsList::getNextInterruptible(Interruptible * previous, int type)
|
Interruptible * TargetsList::getNextInterruptible(Interruptible * previous, int type)
|
||||||
{
|
{
|
||||||
int found = 0;
|
size_t nextIndex = iterateTarget(previous);
|
||||||
if (!previous) found = 1;
|
|
||||||
for (size_t i = 0; i < targets.size(); i++)
|
for (size_t i = nextIndex; i < targets.size(); i++)
|
||||||
{
|
{
|
||||||
if (found && targets[i]->typeAsTarget() == TARGET_STACKACTION)
|
if (Interruptible * action = dynamic_cast<Interruptible *>(targets[i]))
|
||||||
{
|
{
|
||||||
Interruptible * action = (Interruptible *) targets[i];
|
|
||||||
if (action->type == type)
|
if (action->type == type)
|
||||||
{
|
{
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (targets[i] == previous) found = 1;
|
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -135,32 +155,17 @@ Damage * TargetsList::getNextDamageTarget(Damage * previous)
|
|||||||
|
|
||||||
Damageable * TargetsList::getNextDamageableTarget(Damageable * previous)
|
Damageable * TargetsList::getNextDamageableTarget(Damageable * previous)
|
||||||
{
|
{
|
||||||
int found = 0;
|
size_t nextIndex = iterateTarget(previous);
|
||||||
if (!previous) found = 1;
|
for (size_t i = nextIndex; i < targets.size(); i++)
|
||||||
for (size_t i = 0; i < targets.size(); i++)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
if (targets[i]->typeAsTarget() == TARGET_PLAYER)
|
if (Player * pTarget = dynamic_cast<Player *>(targets[i]))
|
||||||
{
|
{
|
||||||
if (found)
|
return pTarget;
|
||||||
{
|
|
||||||
return ((Player *) targets[i]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((Player *) targets[i] == previous) found = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (targets[i]->typeAsTarget() == TARGET_CARD)
|
else if (MTGCardInstance * cTarget = dynamic_cast<MTGCardInstance *>(targets[i]))
|
||||||
{
|
{
|
||||||
if (found)
|
return cTarget;
|
||||||
{
|
|
||||||
return ((MTGCardInstance *) targets[i]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((MTGCardInstance *) targets[i] == previous) found = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
Reference in New Issue
Block a user