#include "../include/config.h" #include "../include/AIPlayer.h" #include "../include/CardDescriptor.h" #include "../include/AIStats.h" #include "../include/AllAbilities.h" #include "../include/ExtraCost.h" #include "../include/GuiCombat.h" const char * const MTG_LAND_TEXTS[] = {"artifact","forest","island","mountain","swamp","plains","other lands"}; int AIAction::currentId = 0; int AIAction::Act(){ GameObserver * g = GameObserver::GetInstance(); if (player){ g->cardClick(NULL, player); return 1; } if (ability){ g->mLayers->actionLayer()->reactToClick(ability,click); if (target) g->cardClick(target); return 1; }else if (click){ //Shouldn't be used, really... g->cardClick(click,click); if (target) g->cardClick(target); return 1; } return 0; } AIPlayer::AIPlayer(MTGPlayerCards * deck, string file, string fileSmall) : Player(deck, file, fileSmall) { nextCardToPlay = NULL; stats = NULL; agressivity = 50; forceBestAbilityUse = false; } AIPlayer::~AIPlayer(){ if (stats){ stats->save(); SAFE_DELETE(stats); } while(!clickstream.empty()){ AIAction * action = clickstream.front(); SAFE_DELETE(action); clickstream.pop(); } } MTGCardInstance * AIPlayer::chooseCard(TargetChooser * tc, MTGCardInstance * source, int random){ for (int i = 0; i < game->hand->nb_cards; i++){ MTGCardInstance * card = game->hand->cards[i]; if (!tc->alreadyHasTarget(card) && tc->canTarget(card)){ return card; } } return NULL; } int AIPlayer::Act(float dt){ GameObserver * gameObs = GameObserver::GetInstance(); if (gameObs->currentPlayer == this) gameObs->userRequestNextGamePhase(); return 1; } void AIPlayer::tapLandsForMana(ManaCost * cost,MTGCardInstance * target){ #if defined (WIN32) || defined (LINUX) OutputDebugString("tapping land for mana\n"); #endif if (!cost) return; ManaCost * pMana = getPotentialMana(target); ManaCost * diff = pMana->Diff(cost); delete(pMana); GameObserver * g = GameObserver::GetInstance(); mapused; for (int i = 1; i < g->mLayers->actionLayer()->mCount; i++){ //0 is not a mtgability...hackish //Make sure we can use the ability MTGAbility * a = ((MTGAbility *)g->mLayers->actionLayer()->mObjects[i]); AManaProducer * amp = dynamic_cast(a); if (amp && canHandleCost(amp)){ MTGCardInstance * card = amp->source; if (card == target) used[card] = true; //http://code.google.com/p/wagic/issues/detail?id=76 if (!used[card] && amp->isReactingToClick(card) && amp->output->getConvertedCost()==1){ used[card] = true; int doTap = 1; for (int i=Constants::MTG_NB_COLORS-1; i>= 0; i--){ if (diff->getCost(i) && amp->output->getCost(i) ){ diff->remove(i,1); doTap = 0; break; } } if (doTap){ AIAction * action = NEW AIAction(amp,card); clickstream.push(action); } } } } delete(diff); } ManaCost * AIPlayer::getPotentialMana(MTGCardInstance * target){ ManaCost * result = NEW ManaCost(); GameObserver * g = GameObserver::GetInstance(); mapused; for (int i = 1; i < g->mLayers->actionLayer()->mCount; i++){ //0 is not a mtgability...hackish //Make sure we can use the ability MTGAbility * a = ((MTGAbility *)g->mLayers->actionLayer()->mObjects[i]); AManaProducer * amp = dynamic_cast(a); if (amp && canHandleCost(amp)){ MTGCardInstance * card = amp->source; if (card == target) used[card] = true; //http://code.google.com/p/wagic/issues/detail?id=76 if (!used[card] && amp->isReactingToClick(card) && amp->output->getConvertedCost()==1){ result->add(amp->output); used[card] = true; } } } return result; } int AIPlayer::getEfficiency(AIAction * action){ return action->getEfficiency(); } int AIPlayer::canHandleCost(MTGAbility * ability){ //Can't handle sacrifice costs that require a target yet :( if (ability->cost){ ExtraCosts * ec = ability->cost->extraCosts; if (ec){ for (size_t i = 0; i < ec->costs.size(); i++){ if (ec->costs[i]->tc) return 0; } } } return 1; } MTGAbility * AIAction::getCoreAbility(MTGAbility * a){ GenericTargetAbility * gta = dynamic_cast(a); if (gta) return getCoreAbility(gta->ability); GenericActivatedAbility * gaa = dynamic_cast(a); if (gaa) return getCoreAbility(gaa->ability); if (MultiAbility * abi = dynamic_cast(a)) return getCoreAbility(abi->abilities[0]); return a; } int AIAction::getEfficiency(){ //TODO add multiplier according to what the player wants if (efficiency != -1) return efficiency; if (!ability) return 0; GameObserver * g = GameObserver::GetInstance(); ActionStack * s = g->mLayers->stackLayer(); Player * p = g->currentlyActing(); if (s->has(ability)) return 0; MTGAbility * a = getCoreAbility(ability); if (!a){ OutputDebugString("FATAL: Ability is NULL in AIAction::getEfficiency()"); return 0; } if (!((AIPlayer *)p)->canHandleCost(ability)) return 0; switch (a->aType){ case MTGAbility::DAMAGER: { AADamager * aad = (AADamager *) a; if (!target){ Targetable * _t = aad->getTarget(); if (_t == p->opponent()) efficiency = 90; else efficiency = 0; break; } if ( p == target->controller()){ efficiency = 0; }else if (aad->damage->getValue() >= target->toughness){ efficiency = 100; }else if (target->toughness){ efficiency = (50 * aad->damage->getValue()) / target->toughness; }else{ efficiency = 0; } break; } case MTGAbility::STANDARD_REGENERATE: { MTGCardInstance * _target = (MTGCardInstance *)(a->target); efficiency = 0; if (!_target->regenerateTokens && g->getCurrentGamePhase()< Constants::MTG_PHASE_COMBATDAMAGE && (_target->defenser || _target->blockers.size())){ efficiency = 95; } //TODO If the card is the target of a damage spell break; } case MTGAbility::MANA_PRODUCER: //can't use mana producers right now :/ efficiency = 0; break; default: if (target){ AbilityFactory af; int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); if ((suggestion == BAKA_EFFECT_BAD && p==target->controller()) ||(suggestion == BAKA_EFFECT_GOOD && p!=target->controller())){ efficiency =0; }else{ efficiency = WRand() % 5; //Small percentage of chance for unknown abilities } }else{ efficiency = WRand() % 10; } break; } if (p->game->hand->nb_cards == 0) efficiency = (int) ((float) efficiency * 1.3); //increase chance of using ability if hand is empty if (ability->cost){ ExtraCosts * ec = ability->cost->extraCosts; if (ec) efficiency = efficiency / 3; //Decrease chance of using ability if there is an extra cost to use the ability } return efficiency; } int AIPlayer::createAbilityTargets(MTGAbility * a, MTGCardInstance * c, map * ranking){ if (!a->tc){ AIAction * as = NEW AIAction(a,c,NULL); (*ranking)[as] = 1; return 1; } GameObserver * g = GameObserver::GetInstance(); for (int i = 0; i < 2; i++){ Player * p = g->players[i]; MTGGameZone * playerZones[] = {p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay}; for (int j = 0; j < 4; j++){ MTGGameZone * zone = playerZones[j]; for (int k=0; k < zone->nb_cards; k++){ MTGCardInstance * t = zone->cards[k]; if (a->tc->canTarget(t)){ AIAction * as = NEW AIAction(a,c,t); (*ranking)[as] = 1; } } } } return 1; } int AIPlayer::selectAbility(){ mapranking; list::iterator it; GameObserver * g = GameObserver::GetInstance(); //This loop is extrmely inefficient. TODO: optimize! ManaCost * totalPotentialMana = getPotentialMana(); for (int i = 1; i < g->mLayers->actionLayer()->mCount; i++){ //0 is not a mtgability...hackish MTGAbility * a = ((MTGAbility *)g->mLayers->actionLayer()->mObjects[i]); //Skip mana abilities for performance if (dynamic_cast(a)) continue; //Make sure we can use the ability for (int j=0; j < game->inPlay->nb_cards; j++){ MTGCardInstance * card = game->inPlay->cards[j]; if (a->isReactingToClick(card,totalPotentialMana)){ //This test is to avod the huge call to getPotentialManaCost after that ManaCost * pMana = getPotentialMana(card); if (a->isReactingToClick(card,pMana)) createAbilityTargets(a, card, &ranking); delete(pMana); } } } delete totalPotentialMana; if (ranking.size()){ AIAction * a = ranking.begin()->first; int chance = 1; if (!forceBestAbilityUse) chance = 1 + WRand() % 100; if (getEfficiency(a) < chance){ a = NULL; }else{ OutputDebugString("AIPlayer:Using Activated ability\n"); tapLandsForMana(a->ability->cost,a->click); clickstream.push(a); } map::iterator it2; for (it2 = ranking.begin(); it2!=ranking.end(); it2++){ if (a != it2->first) delete(it2->first); } } return 1; } int AIPlayer::interruptIfICan(){ GameObserver * g = GameObserver::GetInstance(); if (g->mLayers->stackLayer()->askIfWishesToInterrupt == this){ if (!clickstream.empty()) g->mLayers->stackLayer()->cancelInterruptOffer(); else g->mLayers->stackLayer()->setIsInterrupting(this); return 1; } return 0; } int AIPlayer::effectBadOrGood(MTGCardInstance * card, int mode, TargetChooser * tc){ int id = card->getMTGId(); AbilityFactory * af = NEW AbilityFactory(); int autoGuess = af->magicText(id,NULL,card, mode, tc); delete af; if (autoGuess) return autoGuess; return BAKA_EFFECT_DONTKNOW; } int AIPlayer::chooseTarget(TargetChooser * tc){ vectorpotentialTargets; int nbtargets = 0; GameObserver * gameObs = GameObserver::GetInstance(); int checkOnly = 0; if (tc){ checkOnly = 1; }else{ tc = gameObs->getCurrentTargetChooser(); } if (!tc) return 0; if (!(gameObs->currentlyActing() == this)) return 0; Player * target = this; int cardEffect = effectBadOrGood(tc->source, MODE_TARGET, tc); if (cardEffect != BAKA_EFFECT_GOOD){ target = this->opponent(); } if (!tc->alreadyHasTarget(target) && tc->canTarget(target) && nbtargets < 50){ for (int i = 0; i < 3; i++){ //Increase probability to target a player when this is possible potentialTargets.push_back(target); nbtargets++; } if (checkOnly) return 1; } MTGPlayerCards * playerZones = target->game; MTGGameZone * zones[] = {playerZones->hand,playerZones->library,playerZones->inPlay, playerZones->graveyard}; for (int j = 0; j < 4; j++){ MTGGameZone * zone = zones[j]; for (int k=0; k< zone->nb_cards; k++){ MTGCardInstance * card = zone->cards[k]; if (!tc->alreadyHasTarget(card) && tc->canTarget(card) && nbtargets < 50){ if (checkOnly) return 1; int multiplier = 1; if (getStats() && getStats()->isInTop(card,10)){ multiplier++; if (getStats()->isInTop(card,5)){ multiplier++; if (getStats()->isInTop(card,3)){ multiplier++; } } } for (int l=0; l < multiplier; l++){ potentialTargets.push_back(card); nbtargets++; } } } } if (nbtargets){ int i = WRand() % nbtargets; int type = potentialTargets[i]->typeAsTarget(); switch(type){ case TARGET_CARD: { MTGCardInstance * card = ((MTGCardInstance *) potentialTargets[i]); clickstream.push(NEW AIAction(card)); return 1; break; } case TARGET_PLAYER: { Player * player = ((Player *) potentialTargets[i]); clickstream.push(NEW AIAction(player)); return 1; break; } } } //BIG PROBLEM gameObs->cancelCurrentAction(); return 0; } int AIPlayer::getCreaturesInfo(Player * player, int neededInfo , int untapMode, int canAttack){ int result = 0; CardDescriptor cd; cd.init(); cd.setType("Creature"); cd.unsecureSetTapped(untapMode); MTGCardInstance * card = NULL; while((card = cd.nextmatch(player->game->inPlay, card))){ if (!canAttack || card->canAttack()){ if (neededInfo == INFO_NBCREATURES){ result++; }else{ result+=card->power; } } } return result; } int AIPlayer::chooseAttackers(){ //Attack with all creatures //How much damage can the other player do during his next Attack ? int opponentForce = getCreaturesInfo(opponent(),INFO_CREATURESPOWER); int opponentCreatures = getCreaturesInfo(opponent(), INFO_NBCREATURES); int myForce = getCreaturesInfo(this,INFO_CREATURESPOWER,-1,1); int myCreatures = getCreaturesInfo(this, INFO_NBCREATURES, -1,1); bool attack = ((myCreatures > opponentCreatures) || (myForce > opponentForce) || (myForce > 2*opponent()->life)); if (agressivity > 80 && !attack && life > opponentForce) { opponentCreatures = getCreaturesInfo(opponent(), INFO_NBCREATURES,-1); opponentForce = getCreaturesInfo(opponent(),INFO_CREATURESPOWER,-1); attack = (myCreatures >= opponentCreatures && myForce > opponentForce) || (myForce > opponentForce) || (myForce > opponent()->life); } printf("Choose attackers : %i %i %i %i -> %i\n", opponentForce, opponentCreatures, myForce, myCreatures, attack); if (attack){ CardDescriptor cd; cd.init(); cd.setType("creature"); MTGCardInstance * card = NULL; GameObserver * g = GameObserver::GetInstance(); MTGAbility * a = g->mLayers->actionLayer()->getAbility(MTGAbility::MTG_ATTACK_RULE); while((card = cd.nextmatch(game->inPlay, card))){ g->mLayers->actionLayer()->reactToClick(a,card); } } return 1; } /* Can I first strike my oponent and get away with murder ? */ int AIPlayer::canFirstStrikeKill(MTGCardInstance * card, MTGCardInstance *ennemy){ if (ennemy->has(Constants::FIRSTSTRIKE) || ennemy->has(Constants::DOUBLESTRIKE)) return 0; if (!(card->has(Constants::FIRSTSTRIKE) || card->has(Constants::DOUBLESTRIKE))) return 0; if (!(card->power >= ennemy->toughness)) return 0; return 1; } int AIPlayer::chooseBlockers(){ map opponentsToughness; int opponentForce = getCreaturesInfo(opponent(),INFO_CREATURESPOWER); //int opponentCreatures = getCreaturesInfo(opponent(), INFO_NBCREATURES, -1); //int myForce = getCreaturesInfo(this,INFO_CREATURESPOWER); //int myCreatures = getCreaturesInfo(this, INFO_NBCREATURES, -1); CardDescriptor cd; cd.init(); cd.setType("Creature"); cd.unsecureSetTapped(-1); MTGCardInstance * card = NULL; GameObserver * g = GameObserver::GetInstance(); MTGAbility * a = g->mLayers->actionLayer()->getAbility(MTGAbility::MTG_BLOCK_RULE); while((card = cd.nextmatch(game->inPlay, card))){ g->mLayers->actionLayer()->reactToClick(a,card); int set = 0; while(!set){ if (!card->defenser){ set = 1; }else{ MTGCardInstance * attacker = card->defenser; map::iterator it = opponentsToughness.find(attacker); if ( it == opponentsToughness.end()){ opponentsToughness[attacker] = attacker->toughness; it = opponentsToughness.find(attacker); } if (opponentsToughness[attacker] > 0 && getStats() && getStats()->isInTop(attacker,3,false)){ opponentsToughness[attacker]-= card->power; set = 1; }else{ g->mLayers->actionLayer()->reactToClick(a,card); } } } } card = NULL; while((card = cd.nextmatch(game->inPlay, card))){ if (card->defenser && opponentsToughness[card->defenser] > 0){ while (card->defenser){ g->mLayers->actionLayer()->reactToClick(a,card); } } } card = NULL; while((card = cd.nextmatch(game->inPlay, card))){ if(!card->defenser){ g->mLayers->actionLayer()->reactToClick(a,card); int set = 0; while(!set){ if (!card->defenser){ set = 1; }else{ MTGCardInstance * attacker = card->defenser; if (opponentsToughness[attacker] <= 0 || (card->toughness <= attacker->power && opponentForce*2 nbOpponents()>1){ g->mLayers->actionLayer()->reactToClick(a,card); }else{ set = 1; } } } } } return 1; } int AIPlayer::orderBlockers(){ GameObserver * g = GameObserver::GetInstance(); if (ORDER == g->combatStep && g->currentPlayer==this) { OutputDebugString("AIPLAYER: order blockers\n"); g->userRequestNextGamePhase(); //TODO clever rank of blockers return 1; } return 0; } int AIPlayer::affectCombatDamages(CombatStep step){ GameObserver * g = GameObserver::GetInstance(); GuiCombat * gc = g->mLayers->combatLayer(); for (vector::iterator attacker = gc->attackers.begin(); attacker != gc->attackers.end(); ++attacker) gc->autoaffectDamage(*attacker, step); return 1; } //TODO: Deprecate combatDamages int AIPlayer::combatDamages(){ //int result = 0; GameObserver * gameObs = GameObserver::GetInstance(); int currentGamePhase = gameObs->getCurrentGamePhase(); if (currentGamePhase == Constants::MTG_PHASE_COMBATBLOCKERS) return orderBlockers(); if (currentGamePhase != Constants::MTG_PHASE_COMBATDAMAGE) return 0; return 0; } AIStats * AIPlayer::getStats(){ if (!stats){ char statFile[512]; sprintf(statFile, RESPATH"/ai/baka/stats/%s.stats", opponent()->deckFileSmall.c_str()); stats = NEW AIStats(this, statFile); } return stats; } AIPlayer * AIPlayerFactory::createAIPlayer(MTGAllCards * collection, Player * opponent, int deckid){ char deckFile[512]; char avatarFile[512]; char deckFileSmall[512]; if (deckid == -1){ //Evil twin sprintf(deckFile, opponent->deckFile.c_str()); OutputDebugString(opponent->deckFile.c_str()); sprintf(avatarFile, "baka.jpg"); sprintf(deckFileSmall, "ai_baka_eviltwin"); }else{ if (!deckid){ int nbdecks = 0; int found = 1; while (found){ found = 0; char buffer[512]; sprintf(buffer, RESPATH"/ai/baka/deck%i.txt",nbdecks+1); std::ifstream file(buffer); if(file){ found = 1; file.close(); nbdecks++; } } if (!nbdecks) return NULL; deckid = 1 + WRand() % (nbdecks); } sprintf(deckFile, RESPATH"/ai/baka/deck%i.txt",deckid); sprintf(avatarFile, "avatar%i.jpg",deckid); sprintf(deckFileSmall, "ai_baka_deck%i",deckid); } MTGDeck * tempDeck = NEW MTGDeck(deckFile, collection); MTGPlayerCards * deck = NEW MTGPlayerCards(tempDeck); delete tempDeck; AIPlayerBaka * baka = NEW AIPlayerBaka(deck,deckFile, deckFileSmall, avatarFile); baka->deckId = deckid; return baka; } MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * type){ int maxCost = -1; MTGCardInstance * nextCardToPlay = NULL; MTGCardInstance * card = NULL; CardDescriptor cd; cd.init(); cd.setType(type); card = NULL; while((card = cd.nextmatch(game->hand, card))){ if (card->hasType(Subtypes::TYPE_LAND) && !this->canPutLandsIntoPlay) continue; if (card->hasType(Subtypes::TYPE_LEGENDARY) && game->inPlay->findByName(card->name)) continue; int currentCost = card->getManaCost()->getConvertedCost(); int hasX = card->getManaCost()->hasX(); if ((currentCost > maxCost || hasX) && pMana->canAfford(card->getManaCost())){ TargetChooserFactory * tcf = NEW TargetChooserFactory(); TargetChooser * tc = tcf->createTargetChooser(card); delete tcf; int shouldPlayPercentage = 10; if (tc){ int hasTarget = (chooseTarget(tc)); delete tc; if (!hasTarget)continue; shouldPlayPercentage = 90; }else{ int shouldPlay = effectBadOrGood(card); if (shouldPlay == BAKA_EFFECT_GOOD){ shouldPlayPercentage = 90; }else if(BAKA_EFFECT_DONTKNOW == shouldPlay){ shouldPlayPercentage = 80; } } //Reduce the chances of playing a spell with X cost if available mana is low if (hasX){ int xDiff = pMana->getConvertedCost() - currentCost; if (xDiff < 0) xDiff = 0; shouldPlayPercentage = shouldPlayPercentage - ((shouldPlayPercentage * 1.9) / (1 + xDiff)); } if (WRand() % 100 > shouldPlayPercentage) continue; nextCardToPlay = card; maxCost = currentCost; if(hasX) maxCost = pMana->getConvertedCost(); } } return nextCardToPlay; } AIPlayerBaka::AIPlayerBaka(MTGPlayerCards * deck, string file, string fileSmall, string avatarFile) : AIPlayer(deck, file, fileSmall) { mAvatarTex = resources.RetrieveTexture(avatarFile,RETRIEVE_LOCK,TEXTURE_SUB_AVATAR); if(!mAvatarTex){ avatarFile = "baka.jpg"; mAvatarTex = resources.RetrieveTexture(avatarFile,RETRIEVE_LOCK,TEXTURE_SUB_AVATAR); } if(mAvatarTex) mAvatar = resources.RetrieveQuad(avatarFile, 0, 0, 35, 50,"bakaAvatar",RETRIEVE_NORMAL,TEXTURE_SUB_AVATAR); else mAvatar = NULL; initTimer(); } void AIPlayerBaka::initTimer(){ timer = 0.1; } int AIPlayerBaka::computeActions(){ GameObserver * g = GameObserver::GetInstance(); Player * p = g->currentPlayer; if (!(g->currentlyActing() == this)) return 0; if (g->mLayers->actionLayer()->menuObject){ g->mLayers->actionLayer()->doReactTo(0); return 1; } if (chooseTarget()) return 1; int currentGamePhase = g->getCurrentGamePhase(); if (g->isInterrupting == this){ // interrupting selectAbility(); return 1; }else if (p == this && g->mLayers->stackLayer()->count(0,NOT_RESOLVED) == 0){ //standard actions CardDescriptor cd; MTGCardInstance * card = NULL; switch(currentGamePhase){ case Constants::MTG_PHASE_FIRSTMAIN: case Constants::MTG_PHASE_SECONDMAIN: { bool potential = false; ManaCost * currentMana = manaPool; if (!currentMana->getConvertedCost()){ currentMana = getPotentialMana(); potential = true; } nextCardToPlay = FindCardToPlay(currentMana, "land"); //look for the most expensive creature we can afford if (!nextCardToPlay) nextCardToPlay = FindCardToPlay(currentMana, "creature"); //Let's Try an enchantment maybe ? if (!nextCardToPlay) nextCardToPlay = FindCardToPlay(currentMana, "enchantment"); if (!nextCardToPlay) nextCardToPlay = FindCardToPlay(currentMana, "artifact"); if (!nextCardToPlay) nextCardToPlay = FindCardToPlay(currentMana, "sorcery"); if (!nextCardToPlay) nextCardToPlay = FindCardToPlay(currentMana, "instant"); if (potential) delete(currentMana); if (nextCardToPlay){ if (potential){ tapLandsForMana(nextCardToPlay->getManaCost()); } AIAction * a = NEW AIAction(nextCardToPlay); clickstream.push(a); return 1; }else{ selectAbility(); } break; } case Constants::MTG_PHASE_COMBATATTACKERS: chooseAttackers(); break; default: selectAbility(); break; } }else{ cout << "my turn" << endl; switch(currentGamePhase){ case Constants::MTG_PHASE_COMBATBLOCKERS: chooseBlockers(); break; default: break; } return 1; } return 1; }; int AIPlayer::receiveEvent(WEvent * event){ if (getStats()) return getStats()->receiveEvent(event); return 0; } void AIPlayer::Render(){ #ifdef RENDER_AI_STATS if (getStats()) getStats()->Render(); #endif } int AIPlayerBaka::Act(float dt){ GameObserver * g = GameObserver::GetInstance(); if (!(g->currentlyActing() == this)){ return 0; } int currentGamePhase = g->getCurrentGamePhase(); oldGamePhase = currentGamePhase; timer-= dt; if (timer>0){ return 0; } initTimer(); if (combatDamages()){ return 0; } interruptIfICan(); if (!(g->currentlyActing() == this)){ OutputDebugString("Cannot interrupt\n"); return 0; } if (clickstream.empty()) computeActions(); if (clickstream.empty()){ if (g->isInterrupting == this){ g->mLayers->stackLayer()->cancelInterruptOffer(); //endOfInterruption(); }else{ g->userRequestNextGamePhase(); } } else { AIAction * action = clickstream.front(); action->Act(); SAFE_DELETE(action); clickstream.pop(); } return 1; };