コード例 #1
0
ファイル: AbilityImpl.java プロジェクト: PwnedGalaxy/mage
  @Override
  public boolean resolve(Game game) {
    boolean result = true;
    // 20100716 - 117.12
    if (checkIfClause(game)) {

      for (Effect effect : getEffects()) {
        if (effect instanceof OneShotEffect) {
          boolean effectResult = effect.apply(game, this);
          result &= effectResult;
          if (logger.isDebugEnabled()) {
            if (!this.getAbilityType().equals(AbilityType.MANA)) {
              if (!effectResult) {
                if (this.getSourceId() != null) {
                  MageObject mageObject = game.getObject(this.getSourceId());
                  if (mageObject != null) {
                    logger.debug("AbilityImpl.resolve: object: " + mageObject.getName());
                  }
                }
                logger.debug(
                    "AbilityImpl.resolve: effect returned false -"
                        + effect.getText(this.getModes().getMode()));
              }
            }
          }
        } else {
          game.addEffect((ContinuousEffect) effect, this);
        }
        /**
         * All restrained trigger events are fired now. To restrain the events is mainly neccessary
         * because of the movement of multiple object at once. If the event is fired directly as one
         * object moved, other objects are not already in the correct zone to check for their
         * effects. (e.g. Valakut, the Molten Pinnacle)
         */
        game.getState().handleSimultaneousEvent(game);
        game.resetShortLivingLKI();
        /**
         * game.applyEffects() has to be done at least for every effect that moves cards/permanent
         * between zones, or changes control of objects so Static effects work as intened if
         * dependant from the moved objects zone it is in Otherwise for example were static
         * abilities with replacement effects deactivated too late Example: {@link
         * org.mage.test.cards.replacement.DryadMilitantTest#testDiesByDestroy testDiesByDestroy}
         */
        if (effect.applyEffectsAfter()) {
          game.applyEffects();
          game.getState().getTriggers().checkStateTriggers(game);
        }
      }
    }
    return result;
  }
コード例 #2
0
 protected void simulateStep(Game game, Step step) {
   if (ALLOW_INTERRUPT && Thread.interrupted()) {
     Thread.currentThread().interrupt();
     logger.debug("interrupted");
     return;
   }
   if (!game.gameOver(null)) {
     game.getPhase().setStep(step);
     if (!step.skipStep(game, game.getActivePlayerId())) {
       step.beginStep(game, game.getActivePlayerId());
       game.checkStateAndTriggered();
       while (!game.getStack().isEmpty()) {
         game.getStack().resolve(game);
         game.applyEffects();
       }
       step.endStep(game, game.getActivePlayerId());
     }
   }
 }
コード例 #3
0
 protected void addAbilityNode(SimulationNode parent, Ability ability, Game game) {
   Game sim = game.copy();
   sim.getStack().push(new StackAbility(ability, playerId));
   ability.activate(sim, false);
   sim.applyEffects();
   SimulationNode newNode = new SimulationNode(parent, sim, playerId);
   logger.debug(
       indent(newNode.getDepth())
           + "simulating -- node #:"
           + SimulationNode.getCount()
           + " triggered ability option");
   for (Target target : ability.getTargets()) {
     for (UUID targetId : target.getTargets()) {
       newNode.getTargets().add(targetId);
     }
   }
   for (Choice choice : ability.getChoices()) {
     newNode.getChoices().add(choice.getChoice());
   }
   parent.children.add(newNode);
 }
コード例 #4
0
ファイル: Token.java プロジェクト: xdaft/mage
 public boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId) {
   Card source = game.getCard(sourceId);
   String setCode = source != null ? source.getExpansionSetCode() : null;
   GameEvent event =
       GameEvent.getEvent(EventType.CREATE_TOKEN, null, sourceId, controllerId, amount);
   if (!game.replaceEvent(event)) {
     amount = event.getAmount();
     for (int i = 0; i < amount; i++) {
       PermanentToken permanent = new PermanentToken(this, controllerId, setCode, game);
       game.getState().addCard(permanent);
       game.addPermanent(permanent);
       this.lastAddedTokenId = permanent.getId();
       permanent.entersBattlefield(sourceId, game);
       game.applyEffects();
       game.fireEvent(
           new ZoneChangeEvent(permanent, controllerId, Zone.OUTSIDE, Zone.BATTLEFIELD));
     }
     return true;
   }
   return false;
 }
コード例 #5
0
 @Override
 public boolean triggerAbility(TriggeredAbility source, Game game) {
   Ability ability = source.copy();
   List<Ability> options = getPlayableOptions(ability, game);
   if (options.isEmpty()) {
     if (logger.isDebugEnabled()) logger.debug("simulating -- triggered ability:" + ability);
     game.getStack().push(new StackAbility(ability, playerId));
     ability.activate(game, false);
     game.applyEffects();
     game.getPlayers().resetPassed();
   } else {
     SimulationNode parent = (SimulationNode) game.getCustomData();
     if (parent.getDepth() == maxDepth) return true;
     logger.debug(
         indent(parent.getDepth())
             + "simulating -- triggered ability - adding children:"
             + options.size());
     for (Ability option : options) {
       addAbilityNode(parent, option, game);
     }
   }
   return true;
 }
コード例 #6
0
 protected int simulateBlockers(
     Game game,
     SimulationNode2 node,
     UUID defenderId,
     int depth,
     int alpha,
     int beta,
     boolean counter) {
   if (ALLOW_INTERRUPT && Thread.interrupted()) {
     Thread.currentThread().interrupt();
     logger.debug("interrupted");
     return GameStateEvaluator2.evaluate(playerId, game);
   }
   Integer val = null;
   SimulationNode2 bestNode = null;
   // check if defender is being attacked
   if (game.getCombat().isAttacked(defenderId, game)) {
     SimulatedPlayer2 defender = (SimulatedPlayer2) game.getPlayer(defenderId);
     if (logger.isDebugEnabled()) {
       logger.debug(
           defender.getName() + "'s possible blockers: " + defender.getAvailableBlockers(game));
     }
     List<Combat> combats = defender.addBlockers(game);
     for (Combat engagement : combats) {
       if (alpha >= beta) {
         logger.debug("Sim blockers -- pruning blockers");
         break;
       }
       Game sim = game.copy();
       for (CombatGroup group : engagement.getGroups()) {
         if (group.getAttackers().size() > 0) {
           UUID attackerId = group.getAttackers().get(0);
           for (UUID blockerId : group.getBlockers()) {
             sim.getPlayer(defenderId).declareBlocker(defenderId, blockerId, attackerId, sim);
           }
         }
       }
       sim.fireEvent(
           GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId));
       SimulationNode2 newNode = new SimulationNode2(node, sim, depth, defenderId);
       if (logger.isDebugEnabled()) {
         logger.debug("Sim block for player:" + game.getPlayer(defenderId).getName());
       }
       sim.checkStateAndTriggered();
       while (!sim.getStack().isEmpty()) {
         sim.getStack().resolve(sim);
         logger.debug("Sim blockers: resolving triggered abilities");
         sim.applyEffects();
       }
       sim.fireEvent(
           GameEvent.getEvent(
               GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST,
               sim.getActivePlayerId(),
               sim.getActivePlayerId()));
       Combat simCombat = sim.getCombat().copy();
       finishCombat(sim);
       if (sim.gameOver(null)) {
         val = GameStateEvaluator2.evaluate(playerId, sim);
       } else if (!counter) {
         val = simulatePostCombatMain(sim, newNode, depth - 1, alpha, beta);
       } else {
         val = GameStateEvaluator2.evaluate(playerId, sim);
       }
       if (!defenderId.equals(playerId)) {
         if (val < beta) {
           beta = val;
           bestNode = newNode;
           bestNode.setScore(val);
           bestNode.setCombat(simCombat);
         }
       } else {
         if (val > alpha) {
           alpha = val;
           bestNode = newNode;
           bestNode.setScore(val);
           bestNode.setCombat(simCombat);
         }
       }
     }
   }
   if (val == null) {
     val = GameStateEvaluator2.evaluate(playerId, game);
   }
   if (bestNode != null) {
     node.children.clear();
     node.children.add(bestNode);
     node.setScore(bestNode.getScore());
   }
   if (logger.isDebugEnabled()) {
     logger.debug(
         "Sim blockers: returning score: "
             + val
             + " depth:"
             + depth
             + " for player:"
             + game.getPlayer(node.getPlayerId()).getName());
   }
   return val;
 }
コード例 #7
0
 protected int simulateAttackers(
     Game game,
     SimulationNode2 node,
     UUID attackerId,
     int depth,
     int alpha,
     int beta,
     boolean counter) {
   if (ALLOW_INTERRUPT && Thread.interrupted()) {
     Thread.currentThread().interrupt();
     logger.debug("interrupted");
     return GameStateEvaluator2.evaluate(playerId, game);
   }
   Integer val = null;
   SimulationNode2 bestNode = null;
   SimulatedPlayer2 attacker = (SimulatedPlayer2) game.getPlayer(attackerId);
   UUID defenderId = game.getOpponents(attackerId).iterator().next();
   if (logger.isDebugEnabled()) {
     logger.debug(
         attacker.getName()
             + "'s possible attackers: "
             + attacker.getAvailableAttackers(defenderId, game));
   }
   for (Combat engagement : attacker.addAttackers(game)) {
     if (logger.isDebugEnabled()) {
       logger.debug(
           "Sim Attackers: "
               + engagement.getAttackers()
               + ", blockers: "
               + engagement.getBlockers());
     }
     if (alpha >= beta) {
       logger.debug("Sim Attackers -- pruning attackers");
       break;
     }
     Game sim = game.copy();
     for (CombatGroup group : engagement.getGroups()) {
       for (UUID attackId : group.getAttackers()) {
         sim.getPlayer(attackerId).declareAttacker(attackId, defenderId, sim, false);
       }
     }
     sim.fireEvent(
         GameEvent.getEvent(GameEvent.EventType.DECLARED_ATTACKERS, attackerId, attackerId));
     SimulationNode2 newNode = new SimulationNode2(node, sim, depth, attackerId);
     if (logger.isDebugEnabled()) {
       logger.debug("Sim attack for player:" + game.getPlayer(attackerId).getName());
     }
     sim.checkStateAndTriggered();
     while (!sim.getStack().isEmpty()) {
       sim.getStack().resolve(sim);
       logger.debug("Sim attack: resolving triggered abilities");
       sim.applyEffects();
     }
     sim.fireEvent(
         GameEvent.getEvent(
             GameEvent.EventType.DECLARE_ATTACKERS_STEP_POST,
             sim.getActivePlayerId(),
             sim.getActivePlayerId()));
     Combat simCombat = sim.getCombat().copy();
     sim.getPhase().setStep(new DeclareBlockersStep());
     val = simulateCombat(sim, newNode, depth - 1, alpha, beta, counter);
     if (!attackerId.equals(playerId)) {
       if (val < beta) {
         beta = val;
         bestNode = newNode;
         bestNode.setScore(val);
         if (newNode.getChildren().size() > 0) {
           bestNode.setCombat(newNode.getChildren().get(0).getCombat());
         }
       }
     } else {
       if (val > alpha) {
         alpha = val;
         bestNode = newNode;
         bestNode.setScore(val);
         if (newNode.getChildren().size() > 0) {
           bestNode.setCombat(newNode.getChildren().get(0).getCombat());
         }
       }
     }
   }
   if (val == null) {
     val = GameStateEvaluator2.evaluate(playerId, game);
   }
   if (bestNode != null) {
     node.children.clear();
     node.children.add(bestNode);
     node.setScore(bestNode.getScore());
   }
   if (logger.isDebugEnabled()) {
     logger.debug(
         "Sim attackers: returning score: "
             + val
             + " depth:"
             + depth
             + " for player:"
             + game.getPlayer(node.getPlayerId()).getName());
   }
   return val;
 }
コード例 #8
0
  public boolean replaceEvent(GameEvent event, Game game) {
    boolean caught = false;
    HashMap<UUID, HashSet<UUID>> consumed = new HashMap<>();
    do {
      HashMap<ReplacementEffect, HashSet<Ability>> rEffects =
          getApplicableReplacementEffects(event, game);
      // Remove all consumed effects (ability dependant)
      for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext(); ) {
        ReplacementEffect entry = it1.next();
        if (consumed.containsKey(entry.getId())) {
          HashSet<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
          if (rEffects.get(entry) == null
              || consumedAbilitiesIds.size() == rEffects.get(entry).size()) {
            it1.remove();
          } else {
            Iterator it = rEffects.get(entry).iterator();
            while (it.hasNext()) {
              Ability ability = (Ability) it.next();
              if (consumedAbilitiesIds.contains(ability.getId())) {
                it.remove();
              }
            }
          }
        }
      }
      // no effects left, quit
      if (rEffects.isEmpty()) {
        break;
      }
      int index;
      boolean onlyOne = false;
      if (rEffects.size() == 1) {
        ReplacementEffect effect = rEffects.keySet().iterator().next();
        HashSet<Ability> abilities;
        if (effect.getEffectType().equals(EffectType.REPLACEMENT)) {
          abilities = replacementEffects.getAbility(effect.getId());
        } else {
          abilities = preventionEffects.getAbility(effect.getId());
        }
        if (abilities == null || abilities.size() == 1) {
          onlyOne = true;
        }
      }
      if (onlyOne) {
        index = 0;
      } else {
        // 20100716 - 616.1c
        Player player = game.getPlayer(event.getPlayerId());
        index = player.chooseReplacementEffect(getReplacementEffectsTexts(rEffects, game), game);
      }
      // get the selected effect
      int checked = 0;
      ReplacementEffect rEffect = null;
      Ability rAbility = null;
      for (Map.Entry<ReplacementEffect, HashSet<Ability>> entry : rEffects.entrySet()) {
        if (entry.getValue() == null) {
          if (checked == index) {
            rEffect = entry.getKey();
            break;
          } else {
            checked++;
          }
        } else {
          HashSet<Ability> abilities = entry.getValue();
          int size = abilities.size();
          if (index > (checked + size - 1)) {
            checked += size;
          } else {
            rEffect = entry.getKey();
            Iterator it = abilities.iterator();
            while (it.hasNext() && rAbility == null) {
              if (checked == index) {
                rAbility = (Ability) it.next();
              } else {
                it.next();
                checked++;
              }
            }
            break;
          }
        }
      }

      if (rEffect != null) {
        event.getAppliedEffects().add(rEffect.getId());
        caught = rEffect.replaceEvent(event, rAbility, game);
      }
      if (caught) { // Event was completely replaced -> stop applying effects to it
        break;
      }

      // add the applied effect to the consumed effects
      if (rEffect != null) {
        if (consumed.containsKey(rEffect.getId())) {
          HashSet<UUID> set = consumed.get(rEffect.getId());
          if (rAbility != null) {
            if (!set.contains(rAbility.getId())) {
              set.add(rAbility.getId());
            }
          }
        } else {
          HashSet<UUID> set = new HashSet<>();
          if (rAbility
              != null) { // in case of AuraReplacementEffect or PlaneswalkerReplacementEffect there
                         // is no Ability
            set.add(rAbility.getId());
          }
          consumed.put(rEffect.getId(), set);
        }
      }
      // Must be called here for some effects to be able to work correctly
      // TODO: add info which effects need that call
      game.applyEffects();
    } while (true);
    return caught;
  }
コード例 #9
0
ファイル: AbilityImpl.java プロジェクト: PwnedGalaxy/mage
  @Override
  public boolean activate(Game game, boolean noMana) {
    Player controller = game.getPlayer(this.getControllerId());
    if (controller == null) {
      return false;
    }
    game.applyEffects();

    /* 20130201 - 601.2b
     * If the spell is modal the player announces the mode choice (see rule 700.2).
     */
    if (!getModes().choose(game, this)) {
      return false;
    }
    if (controller.isTestMode()) {
      if (!controller.addTargets(this, game)) {
        return false;
      }
    }

    getSourceObject(game);

    /* 20130201 - 601.2b
     * If the player wishes to splice any cards onto the spell (see rule 702.45), he
     * or she reveals those cards in his or her hand.
     */
    if (this.abilityType.equals(AbilityType.SPELL)) {
      game.getContinuousEffects().applySpliceEffects(this, game);
    }

    // if ability can be cast for no mana, clear the mana costs now, because additional mana costs
    // must be paid.
    // For Flashback ability can be set X before, so the X costs have to be restored for the
    // flashbacked ability
    if (noMana) {
      if (this.getManaCostsToPay().getVariableCosts().size() > 0) {
        int xValue = this.getManaCostsToPay().getX();
        this.getManaCostsToPay().clear();
        VariableManaCost xCosts = new VariableManaCost();
        xCosts.setAmount(xValue);
        this.getManaCostsToPay().add(xCosts);
      } else {
        this.getManaCostsToPay().clear();
      }
    }
    // 20130201 - 601.2b
    // If the spell has alternative or additional costs that will be paid as it's being cast such
    // as buyback, kicker, or convoke costs (see rules 117.8 and 117.9), the player announces his
    // or her intentions to pay any or all of those costs (see rule 601.2e).
    // A player can't apply two alternative methods of casting or two alternative costs to a single
    // spell.
    if (!activateAlternateOrAdditionalCosts(sourceObject, noMana, controller, game)) {
      if (getAbilityType().equals(AbilityType.SPELL)
          && ((SpellAbility) this)
              .getSpellAbilityType()
              .equals(SpellAbilityType.FACE_DOWN_CREATURE)) {
        return false;
      }
    }

    // 20121001 - 601.2b
    // If the spell has a variable cost that will be paid as it's being cast (such as an {X} in
    // its mana cost; see rule 107.3), the player announces the value of that variable.
    VariableManaCost variableManaCost = handleManaXCosts(game, noMana, controller);
    String announceString = handleOtherXCosts(game, controller);
    // For effects from cards like Void Winnower x costs have to be set
    if (this.getAbilityType().equals(AbilityType.SPELL)
        && game.replaceEvent(
            GameEvent.getEvent(
                GameEvent.EventType.CAST_SPELL_LATE, getId(), getSourceId(), getControllerId()),
            this)) {
      return false;
    }
    for (Mode mode : this.getModes().getSelectedModes()) {
      this.getModes().setActiveMode(mode);
      // 20121001 - 601.2c
      // 601.2c The player announces his or her choice of an appropriate player, object, or zone for
      // each target the spell requires. A spell may require some targets only if an alternative or
      // additional cost (such as a buyback or kicker cost), or a particular mode, was chosen for
      // it;
      // otherwise, the spell is cast as though it did not require those targets. If the spell has a
      // variable number of targets, the player announces how many targets he or she will choose
      // before
      // he or she announces those targets. The same target can't be chosen multiple times for any
      // one
      // instance of the word "target" on the spell. However, if the spell uses the word "target" in
      // multiple places, the same object, player, or zone can be chosen once for each instance of
      // the
      // word "target" (as long as it fits the targeting criteria). If any effects say that an
      // object
      // or player must be chosen as a target, the player chooses targets so that he or she obeys
      // the
      // maximum possible number of such effects without violating any rules or effects that say
      // that
      // an object or player can't be chosen as a target. The chosen players, objects, and/or zones
      // each become a target of that spell. (Any abilities that trigger when those players,
      // objects,
      // and/or zones become the target of a spell trigger at this point; they'll wait to be put on
      // the stack until the spell has finished being cast.)

      if (sourceObject != null
          && !this.getAbilityType()
              .equals(AbilityType.TRIGGERED)) { // triggered abilities check this already in
        // playerImpl.triggerAbility
        sourceObject.adjustTargets(this, game);
      }
      if (mode.getTargets().size() > 0
          && mode.getTargets()
                  .chooseTargets(
                      getEffects().get(0).getOutcome(), this.controllerId, this, noMana, game)
              == false) {
        if ((variableManaCost != null || announceString != null) && !game.isSimulation()) {
          game.informPlayer(
              controller,
              (sourceObject != null ? sourceObject.getIdName() : "")
                  + ": no valid targets with this value of X");
        }
        return false; // when activation of ability is canceled during target selection
      }
    } // end modes

    // TODO: Handle optionalCosts at the same time as already OptionalAdditionalSourceCosts are
    // handled.
    for (Cost cost : optionalCosts) {
      if (cost instanceof ManaCost) {
        cost.clearPaid();
        if (controller.chooseUse(
            Outcome.Benefit, "Pay optional cost " + cost.getText() + "?", this, game)) {
          manaCostsToPay.add((ManaCost) cost);
        }
      }
    }
    // 20100716 - 601.2e
    if (sourceObject != null) {
      sourceObject.adjustCosts(this, game);
      if (sourceObject instanceof Card) {
        for (Ability ability : ((Card) sourceObject).getAbilities(game)) {
          if (ability instanceof AdjustingSourceCosts) {
            ((AdjustingSourceCosts) ability).adjustCosts(this, game);
          }
        }
      } else {
        for (Ability ability : sourceObject.getAbilities()) {
          if (ability instanceof AdjustingSourceCosts) {
            ((AdjustingSourceCosts) ability).adjustCosts(this, game);
          }
        }
      }
    }

    // this is a hack to prevent mana abilities with mana costs from causing endless loops - pay
    // other costs first
    if (this instanceof ManaAbility
        && !costs.pay(this, game, sourceId, controllerId, noMana, null)) {
      logger.debug("activate mana ability failed - non mana costs");
      return false;
    }

    // 20101001 - 601.2e
    if (costModificationActive) {
      game.getContinuousEffects().costModification(this, game);
    } else {
      costModificationActive = true;
    }

    UUID activatorId = controllerId;
    if ((this instanceof ActivatedAbilityImpl)
        && ((ActivatedAbilityImpl) this).getActivatorId() != null) {
      activatorId = ((ActivatedAbilityImpl) this).getActivatorId();
    }

    // 20100716 - 601.2f  (noMana is not used here, because mana costs were cleared for this ability
    // before adding additional costs and applying cost modification effects)
    if (!manaCostsToPay.pay(this, game, sourceId, activatorId, false, null)) {
      return false; // cancel during mana payment
    }

    // 20100716 - 601.2g
    if (!costs.pay(this, game, sourceId, activatorId, noMana, null)) {
      logger.debug("activate failed - non mana costs");
      return false;
    }
    if (!game.isSimulation()) {
      // inform about x costs now, so canceled announcements are not shown in the log
      if (announceString != null) {
        game.informPlayers(announceString);
      }
      if (variableManaCost != null) {
        int xValue = getManaCostsToPay().getX();
        game.informPlayers(
            controller.getLogName()
                + " announces a value of "
                + xValue
                + " for "
                + variableManaCost.getText());
      }
    }
    activated = true;
    // fire if tapped for mana (may only fire now because else costs of ability itself can be payed
    // with mana of abilities that trigger for that event
    if (this.getAbilityType().equals(AbilityType.MANA)) {
      for (Cost cost : costs) {
        if (cost instanceof TapSourceCost) {
          Mana mana = null;
          Effect effect = getEffects().get(0);
          if (effect instanceof DynamicManaEffect) {
            mana = ((DynamicManaEffect) effect).getMana(game, this);
          } else if (effect instanceof BasicManaEffect) {
            mana = ((BasicManaEffect) effect).getMana(game, this);
          }
          if (mana != null
              && mana.getAny()
                  == 0) { // if mana == null or Any > 0 the event has to be fired in the mana effect
            // to know which mana was produced
            ManaEvent event =
                new ManaEvent(
                    GameEvent.EventType.TAPPED_FOR_MANA, sourceId, sourceId, controllerId, mana);
            if (!game.replaceEvent(event)) {
              game.fireEvent(event);
            }
          }
          break;
        }
      }
    }
    return true;
  }
コード例 #10
0
ファイル: WarpWorld.java プロジェクト: gitter-badger/mage
  @Override
  public boolean apply(Game game, Ability source) {
    MageObject sourceObject = source.getSourceObject(game);
    if (sourceObject == null) {
      return false;
    }
    Map<UUID, Set<Card>> permanentsOwned = new HashMap<>();
    Collection<Permanent> permanents = game.getBattlefield().getAllActivePermanents();
    for (Permanent permanent : permanents) {
      Set<Card> set = permanentsOwned.get(permanent.getOwnerId());
      if (set == null) {
        set = new LinkedHashSet<>();
      }
      set.add(permanent);
      permanentsOwned.put(permanent.getOwnerId(), set);
    }

    // shuffle permanents into owner's library
    Map<UUID, Integer> permanentsCount = new HashMap<>();
    for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
      Player player = game.getPlayer(playerId);
      if (player != null) {
        Set<Card> set = permanentsOwned.remove(playerId);
        Integer count = 0;
        if (set != null) {
          count = set.size();
          player.moveCards(set, Zone.BATTLEFIELD, Zone.LIBRARY, source, game);
        }

        if (count > 0) {
          player.shuffleLibrary(game);
        }
        permanentsCount.put(playerId, count);
      }
    }

    game.applyEffects(); // so effects from creatures that were on the battlefield won't trigger
    // from draw or later put into play

    Map<UUID, CardsImpl> cardsRevealed = new HashMap<>();

    // draw cards and reveal them
    for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
      Player player = game.getPlayer(playerId);
      if (player != null) {
        Integer count = Math.min(permanentsCount.get(player.getId()), player.getLibrary().size());
        CardsImpl cards = new CardsImpl();
        for (int i = 0; i < count; i++) {
          Card card = player.getLibrary().removeFromTop(game);
          if (card != null) {
            cards.add(card);
          }
        }
        player.revealCards(sourceObject.getIdName() + " (" + player.getName() + ")", cards, game);
        cardsRevealed.put(player.getId(), cards);
      }
    }

    // put artifacts, creaturs and lands onto the battlefield
    for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
      Player player = game.getPlayer(playerId);
      if (player != null) {
        CardsImpl cards = cardsRevealed.get(player.getId());
        for (Card card : cards.getCards(game)) {
          if (card != null
              && (card.getCardType().contains(CardType.ARTIFACT)
                  || card.getCardType().contains(CardType.CREATURE)
                  || card.getCardType().contains(CardType.LAND))) {
            card.putOntoBattlefield(game, Zone.HAND, source.getSourceId(), player.getId());
            cards.remove(card);
          }
        }
      }
    }
    // put enchantments onto the battlefield
    for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
      Player player = game.getPlayer(playerId);
      if (player != null) {
        CardsImpl cards = cardsRevealed.get(player.getId());
        for (Card card : cards.getCards(game)) {
          if (card != null && card.getCardType().contains(CardType.ENCHANTMENT)) {
            card.putOntoBattlefield(game, Zone.HAND, source.getSourceId(), player.getId());
            cards.remove(card);
          }
        }
      }
    }
    // put the rest of the cards on buttom of the library
    for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
      Player player = game.getPlayer(playerId);
      if (player != null) {
        CardsImpl cards = cardsRevealed.get(player.getId());
        player.putCardsOnBottomOfLibrary(cards, game, source, false);
      }
    }
    return true;
  }