merge latest trunk

This commit is contained in:
myk
2013-03-16 16:12:07 +00:00
64 changed files with 842 additions and 597 deletions

View File

@@ -7075,6 +7075,27 @@ public class Card extends GameEntity implements Comparable<Card> {
}
}
}
} else if (property.startsWith("greatestRememberedCMC")) {
final List<Card> list = new ArrayList<Card>();
for (final Object o : source.getRemembered()) {
if (o instanceof Card) {
list.add(Singletons.getModel().getGame().getCardState((Card) o));
}
}
if (!list.contains(this)) {
return false;
}
for (final Card crd : list) {
if (crd.getRules() != null && crd.getRules().getSplitType() == CardSplitType.Split) {
if (crd.getCMC(Card.SplitCMCMode.LeftSplitCMC) > this.getCMC() || crd.getCMC(Card.SplitCMCMode.RightSplitCMC) > this.getCMC()) {
return false;
}
} else {
if (crd.getCMC() > this.getCMC()) {
return false;
}
}
}
} else if (property.startsWith("lowestCMC")) {
final List<Card> list = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield);
for (final Card crd : list) {
@@ -8067,7 +8088,7 @@ public class Card extends GameEntity implements Comparable<Card> {
for (final Card ca : Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield)) {
for (final ReplacementEffect re : ca.getReplacementEffects()) {
HashMap<String, String> params = re.getMapParams();
Map<String, String> params = re.getMapParams();
if (!"DamageDone".equals(params.get("Event")) || !params.containsKey("PreventionEffect")) {
continue;
}
@@ -8989,7 +9010,7 @@ public class Card extends GameEntity implements Comparable<Card> {
* the rE
*/
public void addReplacementEffect(final ReplacementEffect replacementEffect) {
final ReplacementEffect replacementEffectCopy = replacementEffect.getCopy();
final ReplacementEffect replacementEffectCopy = replacementEffect.getCopy(); // doubtful - every caller provides a newly parsed instance, why copy?
replacementEffectCopy.setHostCard(this);
this.getCharacteristics().getReplacementEffects().add(replacementEffectCopy);
}
@@ -9110,7 +9131,7 @@ public class Card extends GameEntity implements Comparable<Card> {
*/
public boolean hasETBReplacement() {
for (final ReplacementEffect re : getReplacementEffects()) {
final HashMap<String, String> params = re.getMapParams();
final Map<String, String> params = re.getMapParams();
if (!(re instanceof ReplaceMoved)) {
continue;
}

View File

@@ -28,6 +28,7 @@ import forge.card.MagicColor;
import forge.card.ability.AbilityUtils;
import forge.card.ability.ApiType;
import forge.card.spellability.AbilityManaPart;
import forge.card.spellability.AbilitySub;
import forge.card.spellability.SpellAbility;
import forge.game.player.Player;
import forge.game.zone.ZoneType;
@@ -269,7 +270,7 @@ public final class CardUtil {
}
}
} else if (reflectProperty.equals("Produced")) {
final String producedColors = (String) abMana.getTriggeringObject("Produced");
final String producedColors = abMana instanceof AbilitySub ? (String) abMana.getRootAbility().getTriggeringObject("Produced") : (String) abMana.getTriggeringObject("Produced");
for (final String col : Constant.Color.ONLY_COLORS) {
final String s = MagicColor.toShortString(col);
if (producedColors.contains(s)) {

View File

@@ -19,6 +19,8 @@ package forge;
import java.util.EnumSet;
import com.google.common.collect.ImmutableList;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCostBeingPaid;
@@ -46,7 +48,7 @@ public enum Color {
/** The Blue. */
Blue(16);
public static final Color[] WUBRG = new Color[] { White, Blue, Black, Red, Green };
public static final ImmutableList<Color> WUBRG = ImmutableList.of( White, Blue, Black, Red, Green );
@SuppressWarnings("unused")
private int flag = 0;
@@ -128,7 +130,7 @@ public enum Color {
final EnumSet<Color> colors = EnumSet.of(Color.Colorless);
for( int i = 0; i < MagicColor.NUMBER_OR_COLORS; i++ ) {
if( cc.hasAnyColor(MagicColor.WUBRG[i]) )
colors.add(Color.WUBRG[i]);
colors.add(Color.WUBRG.get(i));
}
if (colors.size() > 1) {
colors.remove(Color.Colorless);

View File

@@ -731,6 +731,10 @@ public class AbilityUtils {
players.add(((Card) rem).getController());
}
}
} else if (defined.equals("ImprintedController")) {
for (final Card rem : card.getImprinted()) {
players.add(rem.getController());
}
} else if (defined.startsWith("Triggered")) {
final SpellAbility root = sa.getRootAbility();
Object o = null;

View File

@@ -786,10 +786,11 @@ public class AttachAi extends SpellAbilityAi {
//only add useful keywords unless P/T bonus is significant
if (totToughness + totPower < 4 && !keywords.isEmpty()) {
final int pow = totPower;
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return containsUsefulKeyword(keywords, c, sa);
return containsUsefulKeyword(keywords, c, sa, pow);
}
});
}
@@ -805,18 +806,25 @@ public class AttachAi extends SpellAbilityAi {
if (attachSource.isAura() && !attachSource.getName().equals("Daybreak Coronet")) {
// TODO For Auras like Rancor, that aren't as likely to lead to
// card disadvantage, this check should be skipped
prefList = CardLists.filter(prefList, Predicates.not(Presets.ENCHANTED));
}
if (!grantingAbilities && keywords.isEmpty()) {
// Probably prefer to Enchant Creatures that Can Attack
// Filter out creatures that can't Attack or have Defender
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.isCreature() || CombatUtil.canAttackNextTurn(c);
return !c.isEnchanted() || c.hasKeyword("Hexproof");
}
});
}
if (!grantingAbilities) {
// Probably prefer to Enchant Creatures that Can Attack
// Filter out creatures that can't Attack or have Defender
if (keywords.isEmpty()) {
prefList = CardLists.filter(prefList, new Predicate<Card>() {
@Override
public boolean apply(final Card c) {
return !c.isCreature() || CombatUtil.canAttackNextTurn(c);
}
});
}
c = ComputerUtilCard.getBestAI(prefList);
} else {
// If we grant abilities, we may want to put it on something Weak?
@@ -955,9 +963,9 @@ public class AttachAi extends SpellAbilityAi {
* @param sa SpellAbility
* @return true, if successful
*/
private static boolean containsUsefulKeyword(final ArrayList<String> keywords, final Card card, final SpellAbility sa) {
private static boolean containsUsefulKeyword(final ArrayList<String> keywords, final Card card, final SpellAbility sa, final int powerBonus) {
for (final String keyword : keywords) {
if (isUsefulAttachKeyword(keyword, card, sa)) {
if (isUsefulAttachKeyword(keyword, card, sa, powerBonus)) {
return true;
}
}
@@ -993,7 +1001,7 @@ public class AttachAi extends SpellAbilityAi {
* @param sa SpellAbility
* @return true, if is useful keyword
*/
private static boolean isUsefulAttachKeyword(final String keyword, final Card card, final SpellAbility sa) {
private static boolean isUsefulAttachKeyword(final String keyword, final Card card, final SpellAbility sa, final int powerBonus) {
final PhaseHandler ph = Singletons.getModel().getGame().getPhaseHandler();
if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) {
return false;
@@ -1006,14 +1014,14 @@ public class AttachAi extends SpellAbilityAi {
|| keyword.equals("CARDNAME can't be blocked by more than one creature."));
// give evasive keywords to creatures that can attack and deal damage
if (evasive) {
if (card.getNetCombatDamage() <= 0
if (card.getNetCombatDamage() + powerBonus <= 0
|| !CombatUtil.canAttackNextTurn(card)
|| !CombatUtil.canBeBlocked(card)) {
return false;
}
} else if (keyword.equals("Haste")) {
if (!card.hasSickness() || !ph.isPlayerTurn(sa.getActivatingPlayer()) || card.isTapped()
|| card.getNetCombatDamage() <= 0
|| card.getNetCombatDamage() + powerBonus <= 0
|| card.hasKeyword("CARDNAME can attack as though it had haste.")
|| ph.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS)
|| !CombatUtil.canAttackNextTurn(card)) {
@@ -1022,22 +1030,22 @@ public class AttachAi extends SpellAbilityAi {
} else if (keyword.endsWith("Indestructible")) {
return true;
} else if (keyword.endsWith("Deathtouch") || keyword.endsWith("Wither")) {
if (card.getNetCombatDamage() <= 0
if (card.getNetCombatDamage() + powerBonus <= 0
|| ((!CombatUtil.canBeBlocked(card) || !CombatUtil.canAttackNextTurn(card))
&& !CombatUtil.canBlock(card, true))) {
return false;
}
} else if (keyword.equals("Double Strike") || keyword.equals("Lifelink")) {
if (card.getNetCombatDamage() <= 0
if (card.getNetCombatDamage() + powerBonus <= 0
|| (!CombatUtil.canAttackNextTurn(card) && !CombatUtil.canBlock(card, true))) {
return false;
}
} else if (keyword.equals("First Strike")) {
if (card.getNetCombatDamage() <= 0 || card.hasKeyword("Double Strike")) {
if (card.getNetCombatDamage() + powerBonus <= 0 || card.hasKeyword("Double Strike")) {
return false;
}
} else if (keyword.startsWith("Flanking")) {
if (card.getNetCombatDamage() <= 0
if (card.getNetCombatDamage() + powerBonus <= 0
|| !CombatUtil.canAttackNextTurn(card)
|| !CombatUtil.canBeBlocked(card)) {
return false;
@@ -1048,18 +1056,18 @@ public class AttachAi extends SpellAbilityAi {
return false;
}
} else if (keyword.equals("Trample")) {
if (card.getNetCombatDamage() <= 1
if (card.getNetCombatDamage() + powerBonus <= 1
|| !CombatUtil.canBeBlocked(card)
|| !CombatUtil.canAttackNextTurn(card)) {
return false;
}
} else if (keyword.equals("Infect")) {
if (card.getNetCombatDamage() <= 0
if (card.getNetCombatDamage() + powerBonus <= 0
|| !CombatUtil.canAttackNextTurn(card)) {
return false;
}
} else if (keyword.equals("Vigilance")) {
if (card.getNetCombatDamage() <= 0
if (card.getNetCombatDamage() + powerBonus <= 0
|| !CombatUtil.canAttackNextTurn(card)
|| !CombatUtil.canBlock(card, true)) {
return false;

View File

@@ -424,8 +424,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
* a {@link forge.CardList} object.
* @return a {@link forge.Card} object.
*/
private static Card basicManaFixing(final Player ai, final List<Card> list) { // Search for a
// Basic Land
private static Card basicManaFixing(final Player ai, final List<Card> list) { // Search for a Basic Land
final List<Card> combined = new ArrayList<Card>(ai.getCardsIn(ZoneType.Battlefield));
combined.addAll(ai.getCardsIn(ZoneType.Hand));
@@ -457,6 +456,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
if (minType != null) {
result = CardLists.getType(list, minType);
}
// pick dual lands if available
if (Iterables.any(result, Predicates.not(CardPredicates.Presets.BASIC_LANDS))) {
result = CardLists.filter(result, Predicates.not(CardPredicates.Presets.BASIC_LANDS));
}
return result.get(0);
}
@@ -1173,7 +1177,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
} else if (origin.contains(ZoneType.Library)
&& (type.contains("Basic") || areAllBasics(type))) {
c = basicManaFixing(ai, fetchList);
} else if (ZoneType.Hand.equals(destination) && CardLists.getNotType(fetchList, "Creature").size() == 0) {
} else if (ZoneType.Hand.equals(destination) && CardLists.getNotType(fetchList, "Creature").isEmpty()) {
c = chooseCreature(ai, fetchList);
} else if (ZoneType.Battlefield.equals(destination) || ZoneType.Graveyard.equals(destination)) {
if (!activator.equals(ai) && sa.hasParam("GainControl")) {
@@ -1190,7 +1194,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// Does AI need a land?
List<Card> hand = ai.getCardsIn(ZoneType.Hand);
if (CardLists.filter(hand, Presets.LANDS).size() == 0 && CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Presets.LANDS).size() < 4) {
if (CardLists.filter(hand, Presets.LANDS).isEmpty() && CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), Presets.LANDS).size() < 4) {
boolean canCastSomething = false;
for (Card cardInHand : hand) {
canCastSomething |= ComputerUtilMana.payManaCost(cardInHand.getFirstSpellAbility(), ai, true, 0, false);

View File

@@ -2,6 +2,9 @@ package forge.card.ability.effects;
import java.util.HashMap;
import javax.swing.JOptionPane;
import forge.Card;
import forge.Singletons;
import forge.card.ability.AbilityFactory;
import forge.card.ability.AbilityUtils;
@@ -9,6 +12,10 @@ import forge.card.ability.SpellAbilityEffect;
import forge.card.spellability.AbilitySub;
import forge.card.spellability.SpellAbility;
import forge.card.trigger.TriggerType;
import forge.game.GameAction;
import forge.game.player.Player;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
public class ClashEffect extends SpellAbilityEffect {
@@ -25,7 +32,7 @@ public class ClashEffect extends SpellAbilityEffect {
*/
@Override
public void resolve(SpellAbility sa) {
final boolean victory = sa.getSourceCard().getController().clashWithOpponent(sa.getSourceCard());
final boolean victory = clashWithOpponent(sa.getSourceCard());
// Run triggers
final HashMap<String, Object> runParams = new HashMap<String, Object>();
@@ -56,4 +63,85 @@ public class ClashEffect extends SpellAbilityEffect {
Singletons.getModel().getGame().getTriggerHandler().runTrigger(TriggerType.Clashed, runParams, false);
}
/**
* <p>
* clashWithOpponent.
* </p>
*
* @param source
* a {@link forge.Card} object.
* @return a boolean.
*/
public final boolean clashWithOpponent(final Card source) {
/*
* Each clashing player reveals the top card of his or her library, then
* puts that card on the top or bottom. A player wins if his or her card
* had a higher mana cost.
*
* Clash you win or win you don't. There is no tie.
*/
final Player player = source.getController();
final Player opponent = player.getOpponent();
final ZoneType lib = ZoneType.Library;
final PlayerZone pLib = player.getZone(lib);
final PlayerZone oLib = opponent.getZone(lib);
final StringBuilder reveal = new StringBuilder();
Card pCard = null;
Card oCard = null;
if (pLib.size() > 0) {
pCard = pLib.get(0);
}
if (oLib.size() > 0) {
oCard = oLib.get(0);
}
if ((pLib.size() == 0) && (oLib.size() == 0)) {
return false;
} else if (pLib.isEmpty()) {
clashMoveToTopOrBottom(opponent, oCard);
return false;
} else if (oLib.isEmpty()) {
clashMoveToTopOrBottom(player, pCard);
return true;
} else {
final int pCMC = pCard.getCMC();
final int oCMC = oCard.getCMC();
// TODO: Split cards will return two CMC values, so both players may become winners of clash
reveal.append(player).append(" reveals: ").append(pCard.getName()).append(". CMC = ").append(pCMC);
reveal.append("\r\n");
reveal.append(opponent).append(" reveals: ").append(oCard.getName()).append(". CMC = ").append(oCMC);
reveal.append("\r\n\r\n");
if (pCMC > oCMC) {
reveal.append(player).append(" wins clash.");
} else {
reveal.append(player).append(" loses clash.");
}
JOptionPane.showMessageDialog(null, reveal.toString(), source.getName(), JOptionPane.PLAIN_MESSAGE);
clashMoveToTopOrBottom(player, pCard);
clashMoveToTopOrBottom(opponent, oCard);
// JOptionPane.showMessageDialog(null, reveal.toString(),
// source.getName(), JOptionPane.PLAIN_MESSAGE);
return pCMC > oCMC;
}
}
public final void clashMoveToTopOrBottom(final Player p, final Card c) {
GameAction action = p.getGame().getAction();
boolean putOnTop = p.getController().willPutCardOnTop(c);
if ( putOnTop )
action.moveToLibrary(c);
else
action.moveToBottomOfLibrary(c);
// computer just puts the card back until such time it can make a smarter decision
}
}

View File

@@ -100,9 +100,7 @@ public class EffectEffect extends SpellAbilityEffect {
final Card eff = new Card();
eff.setName(name);
eff.addType("Effect"); // Or Emblem
eff.setToken(true); // Set token to true, so when leaving play it gets
// nuked
eff.addController(controller);
eff.setToken(true); // Set token to true, so when leaving play it gets nuked
eff.setOwner(controller);
eff.setImageKey(sa.hasParam("Image") ? sa.getParam("Image") : hostCard.getImageKey());
eff.setColor(hostCard.getColor());

View File

@@ -151,8 +151,6 @@ public class PlayEffect extends SpellAbilityEffect {
}
if (sa.hasParam("CopyCard")) {
tgtCard = CardDb.getCard(tgtCard).toForgeCard(sa.getActivatingPlayer());
// when copying something stolen:
tgtCard.addController(sa.getActivatingPlayer());
tgtCard.setToken(true);
tgtCard.setCopiedSpell(true);

View File

@@ -1,5 +1,6 @@
package forge.card.ability.effects;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -39,8 +40,12 @@ public class RepeatEachEffect extends SpellAbilityEffect {
List<Card> repeatCards = null;
if (sa.hasParam("RepeatCards")) {
ZoneType zone = sa.hasParam("Zone") ? ZoneType.smartValueOf(sa.getParam("Zone")) : ZoneType.Battlefield;
List<ZoneType> zone = new ArrayList<ZoneType>();
if (sa.hasParam("Zone")) {
zone = ZoneType.listValueOf(sa.getParam("Zone"));
} else {
zone.add(ZoneType.Battlefield);
}
repeatCards = CardLists.getValidCards(game.getCardsIn(zone),
sa.getParam("RepeatCards"), source.getController(), source);
loopOverCards = true;

View File

@@ -1,12 +1,19 @@
package forge.card.ability.effects;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
import forge.Card;
import forge.card.ability.AbilityUtils;
import forge.card.ability.SpellAbilityEffect;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.game.player.Player;
import forge.game.zone.PlayerZone;
import forge.game.zone.ZoneType;
public class ScryEffect extends SpellAbilityEffect {
@@ -42,7 +49,45 @@ public class ScryEffect extends SpellAbilityEffect {
for (final Player p : tgtPlayers) {
if ((tgt == null) || p.canBeTargetedBy(sa)) {
p.scry(num);
scry(p, num);
}
}
}
/**
* <p>
* scry.
* </p>
*
* @param numScry
* a int.
*/
public final void scry(Player p, int numScry) {
final List<Card> topN = new ArrayList<Card>();
final PlayerZone library = p.getZone(ZoneType.Library);
numScry = Math.min(numScry, library.size());
if ( numScry == 0 )
return;
for (int i = 0; i < numScry; i++) {
topN.add(library.get(i));
}
ImmutablePair<List<Card>, List<Card>> lists = p.getController().arrangeForScry(topN);
List<Card> toTop = lists.getLeft();
List<Card> toBottom = lists.getRight();
if ( null != toBottom) {
for(Card c : toBottom) {
p.getGame().getAction().moveToBottomOfLibrary(c);
}
}
if ( null != toTop ) {
Collections.reverse(toTop); // the last card in list will become topmost in library, have to revert thus.
for(Card c : toTop) {
p.getGame().getAction().moveToLibrary(c);
}
}
}

View File

@@ -3146,7 +3146,6 @@ public class CardFactoryUtil {
eff.addType("Effect"); // Or Emblem
eff.setToken(true); // Set token to true, so when leaving
// play it gets nuked
eff.addController(card.getController());
eff.setOwner(card.getController());
eff.setColor(card.getColor());
eff.setImmutable(true);

View File

@@ -17,7 +17,7 @@
*/
package forge.card.replacement;
import java.util.HashMap;
import java.util.Map;
import forge.Card;
import forge.card.cardfactory.CardFactoryUtil;
@@ -36,7 +36,7 @@ public class ReplaceDamage extends ReplacementEffect {
* @param map the map
* @param host the host
*/
public ReplaceDamage(HashMap<String, String> map, Card host) {
public ReplaceDamage(Map<String, String> map, Card host) {
super(map, host);
}
@@ -44,7 +44,7 @@ public class ReplaceDamage extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(HashMap<String, Object> runParams) {
public boolean canReplace(Map<String, Object> runParams) {
if (!runParams.get("Event").equals("DamageDone")) {
return false;
}
@@ -110,7 +110,7 @@ public class ReplaceDamage extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
*/
@Override
public void setReplacingObjects(HashMap<String, Object> runParams, SpellAbility sa) {
public void setReplacingObjects(Map<String, Object> runParams, SpellAbility sa) {
sa.setReplacingObject("DamageAmount", runParams.get("DamageAmount"));
sa.setReplacingObject("Target", runParams.get("Affected"));
sa.setReplacingObject("Source", runParams.get("DamageSource"));

View File

@@ -17,7 +17,7 @@
*/
package forge.card.replacement;
import java.util.HashMap;
import java.util.Map;
import forge.Card;
import forge.card.spellability.SpellAbility;
@@ -34,7 +34,7 @@ public class ReplaceDiscard extends ReplacementEffect {
* @param params the params
* @param host the host
*/
public ReplaceDiscard(final HashMap<String, String> params, final Card host) {
public ReplaceDiscard(final Map<String, String> params, final Card host) {
super(params, host);
}
@@ -42,7 +42,7 @@ public class ReplaceDiscard extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(HashMap<String, Object> runParams) {
public boolean canReplace(Map<String, Object> runParams) {
if (!runParams.get("Event").equals("Discard")) {
return false;
}
@@ -86,7 +86,7 @@ public class ReplaceDiscard extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
*/
@Override
public void setReplacingObjects(HashMap<String, Object> runParams, SpellAbility sa) {
public void setReplacingObjects(Map<String, Object> runParams, SpellAbility sa) {
sa.setReplacingObject("Card", runParams.get("Card"));
sa.setReplacingObject("Player", runParams.get("Affected"));
}

View File

@@ -17,7 +17,7 @@
*/
package forge.card.replacement;
import java.util.HashMap;
import java.util.Map;
import forge.Card;
@@ -33,7 +33,7 @@ public class ReplaceDraw extends ReplacementEffect {
* @param params the params
* @param host the host
*/
public ReplaceDraw(final HashMap<String, String> params, final Card host) {
public ReplaceDraw(final Map<String, String> params, final Card host) {
super(params, host);
}
@@ -41,7 +41,7 @@ public class ReplaceDraw extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(HashMap<String, Object> runParams) {
public boolean canReplace(Map<String, Object> runParams) {
if (!runParams.get("Event").equals("Draw")) {
return false;
}

View File

@@ -17,7 +17,7 @@
*/
package forge.card.replacement;
import java.util.HashMap;
import java.util.Map;
import forge.Card;
import forge.card.spellability.SpellAbility;
@@ -34,7 +34,7 @@ public class ReplaceGainLife extends ReplacementEffect {
* @param map the map
* @param host the host
*/
public ReplaceGainLife(HashMap<String, String> map, Card host) {
public ReplaceGainLife(Map<String, String> map, Card host) {
super(map, host);
}
@@ -42,7 +42,7 @@ public class ReplaceGainLife extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(HashMap<String, Object> runParams) {
public boolean canReplace(Map<String, Object> runParams) {
if (!runParams.get("Event").equals("GainLife")) {
return false;
}
@@ -71,7 +71,7 @@ public class ReplaceGainLife extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
*/
@Override
public void setReplacingObjects(HashMap<String, Object> runParams, SpellAbility sa) {
public void setReplacingObjects(Map<String, Object> runParams, SpellAbility sa) {
sa.setReplacingObject("LifeGained", runParams.get("LifeGained"));
}

View File

@@ -1,6 +1,6 @@
package forge.card.replacement;
import java.util.HashMap;
import java.util.Map;
import forge.Card;
@@ -16,7 +16,7 @@ public class ReplaceGameLoss extends ReplacementEffect {
* @param map the map
* @param host the host
*/
public ReplaceGameLoss(HashMap<String, String> map, Card host) {
public ReplaceGameLoss(Map<String, String> map, Card host) {
super(map, host);
}
@@ -24,7 +24,7 @@ public class ReplaceGameLoss extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(HashMap<String, Object> runParams) {
public boolean canReplace(Map<String, Object> runParams) {
if (!runParams.get("Event").equals("GameLoss")) {
return false;
}

View File

@@ -1,6 +1,6 @@
package forge.card.replacement;
import java.util.HashMap;
import java.util.Map;
import forge.Card;
import forge.card.spellability.SpellAbility;
@@ -18,7 +18,7 @@ public class ReplaceMoved extends ReplacementEffect {
* @param mapParams &emsp; HashMap<String, String>
* @param host &emsp; Card
*/
public ReplaceMoved(final HashMap<String, String> mapParams, final Card host) {
public ReplaceMoved(final Map<String, String> mapParams, final Card host) {
super(mapParams, host);
}
@@ -26,7 +26,7 @@ public class ReplaceMoved extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(HashMap<String, Object> runParams) {
public boolean canReplace(Map<String, Object> runParams) {
if (!runParams.get("Event").equals("Moved")) {
return false;
}
@@ -66,7 +66,7 @@ public class ReplaceMoved extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
*/
@Override
public void setReplacingObjects(HashMap<String, Object> runParams, SpellAbility sa) {
public void setReplacingObjects(Map<String, Object> runParams, SpellAbility sa) {
sa.setReplacingObject("Card", runParams.get("Affected"));
}

View File

@@ -17,7 +17,7 @@
*/
package forge.card.replacement;
import java.util.HashMap;
import java.util.Map;
import forge.Card;
@@ -33,7 +33,7 @@ public class ReplaceSetInMotion extends ReplacementEffect {
* @param params the params
* @param host the host
*/
public ReplaceSetInMotion(final HashMap<String, String> params, final Card host) {
public ReplaceSetInMotion(final Map<String, String> params, final Card host) {
super(params, host);
}
@@ -41,7 +41,7 @@ public class ReplaceSetInMotion extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(HashMap<String, Object> runParams) {
public boolean canReplace(Map<String, Object> runParams) {
if (!runParams.get("Event").equals("SetInMotion")) {
return false;
}

View File

@@ -1,6 +1,6 @@
package forge.card.replacement;
import java.util.HashMap;
import java.util.Map;
import forge.Card;
import forge.card.spellability.SpellAbility;
@@ -17,7 +17,7 @@ public class ReplaceTurnFaceUp extends ReplacementEffect {
* @param mapParams &emsp; HashMap<String, String>
* @param host &emsp; Card
*/
public ReplaceTurnFaceUp(final HashMap<String, String> mapParams, final Card host) {
public ReplaceTurnFaceUp(final Map<String, String> mapParams, final Card host) {
super(mapParams, host);
}
@@ -25,7 +25,7 @@ public class ReplaceTurnFaceUp extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(HashMap<String, Object> runParams) {
public boolean canReplace(Map<String, Object> runParams) {
if (!runParams.get("Event").equals("TurnFaceUp")) {
return false;
}
@@ -53,7 +53,7 @@ public class ReplaceTurnFaceUp extends ReplacementEffect {
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
*/
@Override
public void setReplacingObjects(HashMap<String, Object> runParams, SpellAbility sa) {
public void setReplacingObjects(Map<String, Object> runParams, SpellAbility sa) {
sa.setReplacingObject("Card", runParams.get("Affected"));
}

View File

@@ -118,7 +118,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
}
/** The map params, denoting what to replace. */
private HashMap<String, String> mapParams = new HashMap<String, String>();
private Map<String, String> mapParams = new HashMap<String, String>();
/**
* <p>
@@ -127,7 +127,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
*
* @return a {@link java.util.HashMap} object.
*/
public final HashMap<String, String> getMapParams() {
public final Map<String, String> getMapParams() {
return this.mapParams;
}
@@ -137,7 +137,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
* @param mapParams
* the mapParams to set
*/
public final void setMapParams(final HashMap<String, String> mapParams) {
public final void setMapParams(final Map<String, String> mapParams) {
this.mapParams = mapParams;
}
@@ -148,7 +148,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
* the run params
* @return true, if successful
*/
public abstract boolean canReplace(final HashMap<String, Object> runParams);
public abstract boolean canReplace(final Map<String, Object> runParams);
/**
* To string.
@@ -219,7 +219,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
* @param spellAbility
* the SpellAbility
*/
public void setReplacingObjects(final HashMap<String, Object> runParams, final SpellAbility spellAbility) {
public void setReplacingObjects(final Map<String, Object> runParams, final SpellAbility spellAbility) {
// Should be overridden by replacers that need it.
}
@@ -231,7 +231,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
* @param host
* the host
*/
public ReplacementEffect(final HashMap<String, String> map, final Card host) {
public ReplacementEffect(final Map<String, String> map, final Card host) {
this.setMapParams(map);
this.setHostCard(host);
}

View File

@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import forge.Card;
import forge.Singletons;
@@ -34,6 +35,7 @@ import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.gui.GuiChoose;
import forge.gui.GuiDialog;
import forge.util.FileSection;
/**
* TODO: Write javadoc for this type.
@@ -174,9 +176,9 @@ public class ReplacementHandler {
* @param replacementEffect
* the replacement effect to run
*/
private ReplacementResult executeReplacement(final HashMap<String, Object> runParams,
private ReplacementResult executeReplacement(final Map<String, Object> runParams,
final ReplacementEffect replacementEffect, final Player decider, final GameState game) {
final HashMap<String, String> mapParams = replacementEffect.getMapParams();
final Map<String, String> mapParams = replacementEffect.getMapParams();
SpellAbility effectSA = null;
@@ -257,7 +259,7 @@ public class ReplacementHandler {
* @return A finished instance
*/
public static ReplacementEffect parseReplacement(final String repParse, final Card host) {
final HashMap<String, String> mapParams = ReplacementHandler.parseParams(repParse);
final Map<String, String> mapParams = FileSection.parseToMap(repParse, "$", "|");
return ReplacementHandler.parseReplacement(mapParams, host);
}
@@ -272,7 +274,7 @@ public class ReplacementHandler {
* The card that hosts the replacement effect
* @return The finished instance
*/
public static ReplacementEffect parseReplacement(final HashMap<String, String> mapParams, final Card host) {
public static ReplacementEffect parseReplacement(final Map<String, String> mapParams, final Card host) {
ReplacementEffect ret = null;
final String eventToReplace = mapParams.get("Event");
@@ -301,45 +303,4 @@ public class ReplacementHandler {
return ret;
}
/**
* <p>
* parseParams.
* </p>
*
* @param repParse
* a {@link java.lang.String} object.
* @return a {@link java.util.HashMap} object.
*/
private static HashMap<String, String> parseParams(final String repParse) {
final HashMap<String, String> mapParams = new HashMap<String, String>();
if (repParse.length() == 0) {
throw new RuntimeException("ReplacementFactory : registerTrigger -- trigParse too short");
}
final String[] params = repParse.split("\\|");
for (int i = 0; i < params.length; i++) {
params[i] = params[i].trim();
}
for (final String param : params) {
final String[] splitParam = param.split("\\$");
for (int i = 0; i < splitParam.length; i++) {
splitParam[i] = splitParam[i].trim();
}
if (splitParam.length != 2) {
final StringBuilder sb = new StringBuilder();
sb.append("ReplacementFactory Parsing Error in registerTrigger() : Split length of ");
sb.append(param).append(" is not 2.");
throw new RuntimeException(sb.toString());
}
mapParams.put(splitParam[0], splitParam[1]);
}
return mapParams;
}
}

View File

@@ -20,6 +20,7 @@ package forge.card.spellability;
import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
@@ -498,7 +499,7 @@ public class SpellPermanent extends Spell {
for (final ReplacementEffect re : card.getReplacementEffects()) {
// These Replacements all care for ETB effects
final HashMap<String, String> params = re.getMapParams();
final Map<String, String> params = re.getMapParams();
if (!(re instanceof ReplaceMoved)) {
continue;
}

View File

@@ -406,19 +406,24 @@ public class WrappedAbility extends Ability implements ISpellAbility {
}
}
} else {
ArrayList<Object> tgts = null;
// make sure the targets won't change
if (sa.getTarget() != null && sa.getTarget().getTargetChoices() != null) {
tgts = new ArrayList<Object>(sa.getTarget().getTargetChoices().getTargets());
}
// This isn't quite right, but better than canPlayAI
if (!sa.doTrigger(this.isMandatory(), (AIPlayer)decider)) {
return;
}
if (sa.getTarget() != null && sa.getTarget().getTargetChoices() != null) {
for (Object tgt : tgts) {
sa.getTarget().getTargetChoices().clear();
sa.getTarget().getTargetChoices().addTarget(tgt);
if (triggerParams.containsKey("DelayedTrigger")) {
//TODO: The only card with an optional delayed trigger is Shirei, Shizo's Caretaker,
// needs to be expanded when a more difficult cards comes up
} else {
ArrayList<Object> tgts = null;
// make sure the targets won't change
if (sa.getTarget() != null && sa.getTarget().getTargetChoices() != null) {
tgts = new ArrayList<Object>(sa.getTarget().getTargetChoices().getTargets());
}
// This isn't quite right, but better than canPlayAI
if (!sa.doTrigger(this.isMandatory(), (AIPlayer) decider)) {
return;
}
if (sa.getTarget() != null && sa.getTarget().getTargetChoices() != null) {
for (Object tgt : tgts) {
sa.getTarget().getTargetChoices().clear();
sa.getTarget().getTargetChoices().addTarget(tgt);
}
}
}
}

View File

@@ -84,9 +84,9 @@ public class InputMulligan extends Input {
@Override
public final void selectButtonCancel() {
final Player humanPlayer = Singletons.getControl().getPlayer();
final int newHand = humanPlayer.doMulligan();
humanPlayer.doMulligan();
if (newHand == 0) {
if (humanPlayer.getCardsIn(ZoneType.Hand).isEmpty()) {
this.end();
} else {
ButtonUtil.enableAllFocusOk();

View File

@@ -332,7 +332,6 @@ public class GameNew {
for (final IPaperCard cp : cards) {
Card c = cp.toForgeCard(player);
c.setOwner(player);
c.addController(player);
bf.add(c, false);
c.setSickness(true);
c.setStartsGameInPlay(true);

View File

@@ -118,8 +118,12 @@ public class GameState {
endOfTurn = new EndOfTurn(this);
endOfCombat = new EndOfCombat(this);
if ( match0.getGameType() == GameType.Quest)
events.register(Singletons.getModel().getQuest()); // this one listens to player's mulligans ATM
events.register(Singletons.getControl().getSoundSystem());
events.register(gameLog);
}
/**

View File

@@ -23,6 +23,7 @@ import java.util.List;
import java.util.Random;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.Card;
import forge.CardLists;
@@ -1222,4 +1223,19 @@ public class ComputerUtil {
return (handList.size() > AI_MULLIGAN_THRESHOLD) && hasLittleCmc0Cards;
}
public static boolean scryWillMoveCardToBottomOfLibrary(AIPlayer player, Card c) {
boolean bottom = false;
if (c.isBasicLand()) {
List<Card> bl = player.getCardsIn(ZoneType.Battlefield);
int nBasicLands = Iterables.size(Iterables.filter(bl, CardPredicates.Presets.BASIC_LANDS));
bottom = nBasicLands > 5; // if control more than 5 Basic land, probably don't need more
} else if (c.isCreature()) {
List<Card> cl = player.getCardsIn(ZoneType.Battlefield);
cl = CardLists.filter(cl, CardPredicates.Presets.CREATURES);
bottom = cl.size() > 5; // if control more than 5 Creatures, probably don't need more
}
return bottom;
}
}

View File

@@ -124,14 +124,14 @@ public class ComputerUtilCard {
*/
public static Card getBestLandAI(final List<Card> list) {
final List<Card> land = CardLists.filter(list, CardPredicates.Presets.LANDS);
if (!(land.size() > 0)) {
if (land.isEmpty()) {
return null;
}
// prefer to target non basic lands
final List<Card> nbLand = CardLists.filter(land, Predicates.not(CardPredicates.Presets.BASIC_LANDS));
if (nbLand.size() > 0) {
if (!nbLand.isEmpty()) {
// TODO - Rank non basics?
return Aggregates.random(nbLand);
}
@@ -243,11 +243,11 @@ public class ComputerUtilCard {
public static Card getBestAI(final List<Card> list) {
// Get Best will filter by appropriate getBest list if ALL of the list
// is of that type
if (CardLists.getNotType(list, "Creature").size() == 0) {
if (CardLists.getNotType(list, "Creature").isEmpty()) {
return ComputerUtilCard.getBestCreatureAI(list);
}
if (CardLists.getNotType(list, "Land").size() == 0) {
if (CardLists.getNotType(list, "Land").isEmpty()) {
return getBestLandAI(list);
}

View File

@@ -1695,14 +1695,18 @@ public class ComputerUtilCombat {
Card lastBlocker = null;
for (final Card b : block) {
lastBlocker = b;
final int enoughDamageToKill = ComputerUtilCombat.getEnoughDamageToKill(b, dmgCanDeal, attacker, true);
if (enoughDamageToKill <= dmgCanDeal) {
damageMap.put(b, enoughDamageToKill);
dmgCanDeal -= enoughDamageToKill;
final int dmgToKill = ComputerUtilCombat.getEnoughDamageToKill(b, dmgCanDeal, attacker, true);
if (dmgToKill <= dmgCanDeal) {
damageMap.put(b, dmgToKill);
dmgCanDeal -= dmgToKill;
} else {
damageMap.put(b, dmgCanDeal);
dmgCanDeal = 0;
break;
// if it can't be killed choose the minimum damage
int dmg = Math.min(b.getLethalDamage(), dmgCanDeal);
damageMap.put(b, dmg);
dmgCanDeal -= dmg;
if (dmgCanDeal <= 0) {
break;
}
}
} // for
@@ -1829,7 +1833,7 @@ public class ComputerUtilCombat {
// Predict replacement effects
for (final Card ca : game.getCardsIn(ZoneType.Battlefield)) {
for (final ReplacementEffect re : ca.getReplacementEffects()) {
HashMap<String, String> params = re.getMapParams();
Map<String, String> params = re.getMapParams();
if (!"DamageDone".equals(params.get("Event")) || !params.containsKey("PreventionEffect")) {
continue;
}

View File

@@ -0,0 +1,15 @@
package forge.game.event;
import forge.game.player.Player;
/**
* TODO: Write javadoc for this type.
*
*/
public class MulliganEvent extends Event {
public final Player player;
public MulliganEvent(Player p) {
player = p;
}
}

View File

@@ -265,7 +265,7 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable {
if (getTurn() == 1 || this.getPlayerTurn().isSkippingDraw()) {
this.setPlayersPriorityPermission(false);
} else {
this.getPlayerTurn().drawCards(1, true);
this.getPlayerTurn().drawCard();
}
break;

View File

@@ -20,7 +20,6 @@ package forge.game.player;
import java.util.List;
import java.util.Random;
import com.google.common.collect.Iterables;
import forge.Card;
import forge.CardLists;
@@ -135,41 +134,6 @@ public class AIPlayer extends Player {
// /////////////////////////
/** {@inheritDoc} */
@Override
protected final void doScry(final List<Card> topN, final int n) {
int num = n;
for (int i = 0; i < num; i++) {
boolean bottom = false;
if (topN.get(i).isBasicLand()) {
List<Card> bl = this.getCardsIn(ZoneType.Battlefield);
int nBasicLands = Iterables.size(Iterables.filter(bl, CardPredicates.Presets.BASIC_LANDS));
bottom = nBasicLands > 5; // if control more than 5 Basic land,
// probably don't need more
} else if (topN.get(i).isCreature()) {
List<Card> cl = this.getCardsIn(ZoneType.Battlefield);
cl = CardLists.filter(cl, CardPredicates.Presets.CREATURES);
bottom = cl.size() > 5; // if control more than 5 Creatures,
// probably don't need more
}
if (bottom) {
final Card c = topN.get(i);
game.getAction().moveToBottomOfLibrary(c);
// topN.remove(c);
}
}
num = topN.size();
// put the rest on top in random order
for (int i = 0; i < num; i++) {
final Random rndm = MyRandom.getRandom();
final int r = rndm.nextInt(topN.size());
final Card c = topN.get(r);
game.getAction().moveToLibrary(c);
topN.remove(r);
}
}
/** {@inheritDoc} */
@Override
public final void sacrificePermanent(final String prompt, final List<Card> choices) {
@@ -180,25 +144,6 @@ public class AIPlayer extends Player {
}
}
/** {@inheritDoc} */
@Override
protected final void clashMoveToTopOrBottom(final Card c) {
// computer just puts the card back until such time it can make a
// smarter decision
game.getAction().moveToLibrary(c);
}
/*
* (non-Javadoc)
*
* @see forge.Player#discard_Chains_of_Mephistopheles()
*/
@Override
protected final void discardChainsOfMephistopheles() {
this.discard(1, null);
this.drawCard();
}
/* (non-Javadoc)
* @see forge.game.player.Player#getType()
*/

View File

@@ -24,13 +24,9 @@ import forge.Singletons;
import forge.card.spellability.SpellAbility;
import forge.control.input.Input;
import forge.game.GameState;
import forge.game.GameType;
import forge.game.zone.ZoneType;
import forge.gui.GuiChoose;
import forge.gui.GuiDialog;
import forge.gui.match.CMatchUI;
import forge.quest.QuestController;
import forge.quest.bazaar.QuestItemType;
/**
* <p>
@@ -103,44 +99,6 @@ public class HumanPlayer extends Player {
}
}
/*
* (non-Javadoc)
*
* @see forge.Player#discard_Chains_of_Mephistopheles()
*/
/**
*
*/
@Override
protected final void discardChainsOfMephistopheles() {
Singletons.getModel().getMatch().getInput().setInputInterrupt(PlayerUtil.inputChainsDiscard());
}
/** {@inheritDoc} */
@Override
protected final void doScry(final List<Card> topN, final int n) {
int num = n;
for (int i = 0; i < num; i++) {
final Card c = GuiChoose.oneOrNone("Put on bottom of library.", topN);
if (c != null) {
topN.remove(c);
game.getAction().moveToBottomOfLibrary(c);
} else {
// no card chosen for the bottom
break;
}
}
num = topN.size();
for (int i = 0; i < num; i++) {
final Card c = GuiChoose.one("Put on top of library.", topN);
if (c != null) {
topN.remove(c);
game.getAction().moveToLibrary(c);
}
// no else - a card must have been chosen
}
}
/** {@inheritDoc} */
@Override
public final void sacrificePermanent(final String prompt, final List<Card> choices) {
@@ -148,21 +106,6 @@ public class HumanPlayer extends Player {
Singletons.getModel().getMatch().getInput().setInput(in);
}
/** {@inheritDoc} */
@Override
protected final void clashMoveToTopOrBottom(final Card c) {
String choice = "";
final String[] choices = { "top", "bottom" };
CMatchUI.SINGLETON_INSTANCE.setCard(c);
choice = GuiChoose.one(c.getName() + " - Top or bottom of Library", choices);
if (choice.equals("bottom")) {
game.getAction().moveToBottomOfLibrary(c);
} else {
game.getAction().moveToLibrary(c);
}
}
/* (non-Javadoc)
* @see forge.game.player.Player#getType()
*/
@@ -171,19 +114,6 @@ public class HumanPlayer extends Player {
return PlayerType.HUMAN;
}
@Override
public final int doMulligan() {
int newHand = super.doMulligan();
final QuestController quest = Singletons.getModel().getQuest();
final boolean isQuest = Singletons.getModel().getMatch().getGameType().equals(GameType.Quest);
if (isQuest && quest.getAssets().hasItem(QuestItemType.SLEIGHT) && (getStats().getMulliganCount() == 1)) {
drawCard();
newHand++;
getStats().notifyOpeningHandSize(newHand);
}
return newHand;
}
/* (non-Javadoc)
* @see forge.game.player.Player#getController()
*/

View File

@@ -26,7 +26,6 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.swing.JOptionPane;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
@@ -42,6 +41,8 @@ import forge.Constant.Preferences;
import forge.CounterType;
import forge.GameEntity;
import forge.Singletons;
import forge.card.ability.AbilityFactory;
import forge.card.ability.AbilityUtils;
import forge.card.cardfactory.CardFactoryUtil;
import forge.card.cost.Cost;
import forge.card.mana.ManaCost;
@@ -50,6 +51,7 @@ import forge.card.replacement.ReplacementResult;
import forge.card.spellability.Ability;
import forge.card.spellability.Spell;
import forge.card.spellability.SpellAbility;
import forge.card.spellability.Target;
import forge.card.staticability.StaticAbility;
import forge.card.trigger.TriggerType;
import forge.game.GameActionUtil;
@@ -61,9 +63,11 @@ import forge.game.event.CardDiscardedEvent;
import forge.game.event.DrawCardEvent;
import forge.game.event.LandPlayedEvent;
import forge.game.event.LifeLossEvent;
import forge.game.event.MulliganEvent;
import forge.game.event.PoisonCounterEvent;
import forge.game.event.ShuffleEvent;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.zone.PlayerZone;
import forge.game.zone.PlayerZoneBattlefield;
import forge.game.zone.Zone;
@@ -124,6 +128,7 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
/** The num drawn this turn. */
private int numDrawnThisTurn = 0;
private int numDrawnThisDrawStep = 0;
/** The slowtrip list. */
private List<Card> slowtripList = new ArrayList<Card>();
@@ -191,6 +196,10 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
this.setName(lobbyPlayer.getName());
}
public GameState getGame() { // I'll probably regret about this
return game;
}
public final PlayerStatistics getStats() {
return stats;
}
@@ -1224,16 +1233,6 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
return this.drawCards(1);
}
/**
* <p>
* drawCards.
* </p>
*
* @return a List<Card> of cards actually drawn
*/
public final List<Card> drawCards() {
return this.drawCards(1);
}
/**
* <p>
@@ -1244,70 +1243,6 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
*/
public abstract boolean dredge();
/**
* <p>
* drawCards.
* </p>
*
* @param n
* a int.
* @return a List<Card> of cards actually drawn
*/
public final List<Card> drawCards(final int n) {
return this.drawCards(n, false);
}
/**
* <p>
* drawCards.
* </p>
*
* @param n
* a int.
* @param firstFromDraw
* true if this is the card drawn from that player's draw step
* each turn
* @return a List<Card> of cards actually drawn
*/
public final List<Card> drawCards(final int n, final boolean firstFromDraw) {
final List<Card> drawn = new ArrayList<Card>();
if (!this.canDraw()) {
return drawn;
}
for (int i = 0; i < n; i++) {
// TODO: multiple replacements need to be selected by the controller
if (this.getDredge().size() != 0) {
if (this.dredge()) {
continue;
}
}
if (!firstFromDraw && game.isCardInPlay("Chains of Mephistopheles")) {
if (!this.getZone(ZoneType.Hand).isEmpty()) {
if (this.isHuman()) {
this.discardChainsOfMephistopheles();
} else { // Computer
this.discard(1, null);
// true causes this code not to be run again
drawn.addAll(this.drawCards(1, true));
}
} else {
this.mill(1);
}
} else {
drawn.addAll(this.doDraw());
}
}
// Play the Draw sound
Singletons.getModel().getGame().getEvents().post(new DrawCardEvent());
return drawn;
}
/**
*
* TODO Write javadoc for this method.
@@ -1318,20 +1253,53 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
* a GamePlayerRating object
* @return an int
*/
public int doMulligan() {
public void doMulligan() {
final List<Card> hand = new ArrayList<Card>(getCardsIn(ZoneType.Hand));
for (final Card c : hand) {
game.getAction().moveToLibrary(c);
}
shuffle();
final int newHand = hand.size() - 1;
for (int i = 0; i < newHand; i++) {
drawCard();
}
drawCards(hand.size() - 1);
game.getEvents().post(new MulliganEvent(this)); // quest listener may interfere here
final int newHand = getCardsIn(ZoneType.Hand).size();
game.getGameLog().add("Mulligan", this + " has mulliganed down to " + newHand + " cards.", 0);
stats.notifyHasMulliganed();
stats.notifyOpeningHandSize(newHand);
return newHand;
}
/**
* <p>
* drawCards.
* </p>
*
* @param n
* a int.
* @return a List<Card> of cards actually drawn
*/
public final List<Card> drawCards(final int n) {
final List<Card> drawn = new ArrayList<Card>();
if (!this.canDraw()) {
return drawn;
}
for (int i = 0; i < n; i++) {
// TODO: multiple replacements need to be selected by the controller
if (!this.getDredge().isEmpty()) {
if (this.dredge()) {
continue;
}
}
drawn.addAll(this.doDraw());
}
// Play the Draw sound
game.getEvents().post(new DrawCardEvent());
return drawn;
}
/**
@@ -1354,7 +1322,46 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
return drawn;
}
if (library.size() != 0) {
// ======== Chains of Mephistopheles hardcode. =========
// This card requires player to either discard a card, and then he may proceed drawing, or mill 1 - then no draw will happen
// It's oracle-worded as a replacement effect ("If a player would draw a card ... discards a card instead") (rule 419.1a)
// Yet, gatherer's rulings read: The effect is cumulative. If there are two of these on the battlefield, each of them will modify each draw
// That means player isn't supposed to choose one replacement effect out of two (generated by Chains Of Mephistopheles), but both happen.
// So it's not a common replacement effect and has to handled by special code.
// This is why the code is placed after any other replacement effects could affect the draw event.
List<Card> chainsList = null;
for(Card c: game.getCardsInGame()) {
if ( c.getName().equals("Chains of Mephistopheles") ) {
if ( null == chainsList )
chainsList = new ArrayList<Card>();
chainsList.add(c);
}
}
if (chainsList != null && (numDrawnThisDrawStep > 0 || !game.getPhaseHandler().is(PhaseType.DRAW))) {
for(Card c : chainsList) {
// I have to target this player - don't know how to do it.
Target target = new Target(c, null, "");
target.addTarget(this);
if (getCardsIn(ZoneType.Hand).isEmpty()) {
SpellAbility saMill = AbilityFactory.getAbility(c.getSVar("MillOne"), c);
saMill.setActivatingPlayer(c.getController());
saMill.setTarget(target);
AbilityUtils.resolve(saMill, false);
return drawn; // Draw is cancelled
} else {
SpellAbility saDiscard = AbilityFactory.getAbility(c.getSVar("DiscardOne"), c);
saDiscard.setActivatingPlayer(c.getController());
saDiscard.setTarget(target);
AbilityUtils.resolve(saDiscard, false);
}
}
}
// End of = Chains of Mephistopheles hardcode. =========
if (!library.isEmpty()) {
Card c = library.get(0);
c = game.getAction().moveToHand(c);
@@ -1377,6 +1384,8 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
this.setLastDrawnCard(c);
c.setDrawnThisTurn(true);
this.numDrawnThisTurn++;
if ( game.getPhaseHandler().is(PhaseType.DRAW))
this.numDrawnThisDrawStep++;
// Miracle draws
if (this.numDrawnThisTurn == 1 && game.getPhaseHandler().getTurn() != 0) {
@@ -1556,6 +1565,7 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
*/
public final void resetNumDrawnThisTurn() {
this.numDrawnThisTurn = 0;
this.numDrawnThisDrawStep = 0;
}
/**
@@ -1575,11 +1585,6 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
// /
// //////////////////////////////
/**
* Discard_ chains_of_ mephistopheles.
*/
protected abstract void discardChainsOfMephistopheles();
/**
* <p>
* discard.
@@ -1843,35 +1848,7 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
// //////////////////////////////
// //////////////////////////////
/**
* <p>
* doScry.
* </p>
*
* @param topN
* a {@link forge.CardList} object.
* @param n
* a int.
*/
protected abstract void doScry(List<Card> topN, int n);
/**
* <p>
* scry.
* </p>
*
* @param numScry
* a int.
*/
public final void scry(int numScry) {
final List<Card> topN = new ArrayList<Card>();
final PlayerZone library = this.getZone(ZoneType.Library);
numScry = Math.min(numScry, library.size());
for (int i = 0; i < numScry; i++) {
topN.add(library.get(i));
}
this.doScry(topN, topN.size());
}
// /////////////////////////////
@@ -2696,87 +2673,6 @@ public abstract class Player extends GameEntity implements Comparable<Player> {
this.lifeLostThisTurn = n;
}
// //////////////////////////////
//
// Clash
//
// ///////////////////////////////
/**
* <p>
* clashWithOpponent.
* </p>
*
* @param source
* a {@link forge.Card} object.
* @return a boolean.
*/
public final boolean clashWithOpponent(final Card source) {
/*
* Each clashing player reveals the top card of his or her library, then
* puts that card on the top or bottom. A player wins if his or her card
* had a higher mana cost.
*
* Clash you win or win you don't. There is no tie.
*/
final Player player = source.getController();
final Player opponent = player.getOpponent();
final ZoneType lib = ZoneType.Library;
final PlayerZone pLib = player.getZone(lib);
final PlayerZone oLib = opponent.getZone(lib);
final StringBuilder reveal = new StringBuilder();
Card pCard = null;
Card oCard = null;
if (pLib.size() > 0) {
pCard = pLib.get(0);
}
if (oLib.size() > 0) {
oCard = oLib.get(0);
}
if ((pLib.size() == 0) && (oLib.size() == 0)) {
return false;
} else if (pLib.size() == 0) {
opponent.clashMoveToTopOrBottom(oCard);
return false;
} else if (oLib.size() == 0) {
player.clashMoveToTopOrBottom(pCard);
return true;
} else {
final int pCMC = pCard.getCMC();
final int oCMC = oCard.getCMC();
reveal.append(player).append(" reveals: ").append(pCard.getName()).append(". CMC = ").append(pCMC);
reveal.append("\r\n");
reveal.append(opponent).append(" reveals: ").append(oCard.getName()).append(". CMC = ").append(oCMC);
reveal.append("\r\n\r\n");
if (pCMC > oCMC) {
reveal.append(player).append(" wins clash.");
} else {
reveal.append(player).append(" loses clash.");
}
JOptionPane.showMessageDialog(null, reveal.toString(), source.getName(), JOptionPane.PLAIN_MESSAGE);
player.clashMoveToTopOrBottom(pCard);
opponent.clashMoveToTopOrBottom(oCard);
// JOptionPane.showMessageDialog(null, reveal.toString(),
// source.getName(), JOptionPane.PLAIN_MESSAGE);
return pCMC > oCMC;
}
}
/**
* <p>
* clashMoveToTopOrBottom.
* </p>
*
* @param c
* a {@link forge.Card} object.
*/
protected abstract void clashMoveToTopOrBottom(Card c);
/**
* a Player or Planeswalker that this Player must attack if able in an
* upcoming combat. This is cleared at the end of each combat.

View File

@@ -3,6 +3,8 @@ package forge.game.player;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.tuple.ImmutablePair;
import forge.Card;
import forge.GameEntity;
import forge.card.spellability.SpellAbility;
@@ -104,4 +106,6 @@ public abstract class PlayerController {
/** Shows the card to this player*/
public abstract void reveal(String string, List<Card> cards, ZoneType zone, Player owner);
public abstract ImmutablePair<List<Card>, List<Card>> arrangeForScry(List<Card> topN);
public abstract boolean willPutCardOnTop(Card c);
}

View File

@@ -1,8 +1,12 @@
package forge.game.player;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.tuple.ImmutablePair;
import forge.Card;
import forge.GameEntity;
import forge.card.spellability.Spell;
@@ -228,4 +232,27 @@ public class PlayerControllerAi extends PlayerController {
// We don't know how to reveal cards to AI
}
@Override
public ImmutablePair<List<Card>, List<Card>> arrangeForScry(List<Card> topN) {
List<Card> toBottom = new ArrayList<Card>();
List<Card> toTop = new ArrayList<Card>();
for (Card c: topN) {
if (ComputerUtil.scryWillMoveCardToBottomOfLibrary(player, c))
toBottom.add(c);
else
toTop.add(c);
}
// put the rest on top in random order
Collections.shuffle(toTop);
return ImmutablePair.of(toTop, toBottom);
}
@Override
public boolean willPutCardOnTop(Card c) {
return true; // AI does not know what will happen next (another clash or that would become his topdeck)
}
}

View File

@@ -8,6 +8,7 @@ import java.util.Map;
import javax.swing.JOptionPane;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import forge.Card;
import forge.GameEntity;
@@ -288,4 +289,33 @@ public class PlayerControllerHuman extends PlayerController {
message = String.format("Looking at %s's %s", owner, zone);
GuiChoose.oneOrNone(message, cards);
}
@Override
public ImmutablePair<List<Card>, List<Card>> arrangeForScry(List<Card> topN) {
List<Card> toBottom = null;
List<Card> toTop = null;
if (topN.size() == 1) {
if (willPutCardOnTop(topN.get(0)))
toTop = topN;
else
toBottom = topN;
} else {
toBottom = GuiChoose.order("Select cards to be put on the bottom of your library", "Cards to put on the bottom", -1, topN, null, null);
topN.removeAll(toBottom);
if ( topN.isEmpty() )
toTop = null;
else if ( topN.size() == 1 )
toTop = topN;
else
toTop = GuiChoose.order("Arrange cards to be put on top of your library", "Cards arranged", 0, topN, null, null);
}
return ImmutablePair.of(toTop, toBottom);
}
@Override
public boolean willPutCardOnTop(Card c) {
return GuiDialog.confirm(c, "Where will you put " + c.getName() + " in your library", new String[]{"Top", "Bottom"} );
}
}

View File

@@ -153,45 +153,6 @@ public final class PlayerUtil {
return target;
} // input_discard()
/**
* <p>
* input_chainsDiscard.
* </p>
*
* @return a {@link forge.control.input.Input} object.
*/
public static Input inputChainsDiscard() {
final Input target = new Input() {
private static final long serialVersionUID = 2856894846224546303L;
@Override
public void showMessage() {
if (Singletons.getControl().getPlayer().getZone(ZoneType.Hand).size() == 0) {
this.stop();
}
CMatchUI.SINGLETON_INSTANCE.showMessage("Chains of Mephistopheles:\n" + "Select a card to discard");
ButtonUtil.disableAll();
}
@Override
public void selectCard(final Card card) {
Zone zone = Singletons.getModel().getGame().getZoneOf(card);
if (zone.is(ZoneType.Hand)) {
card.getController().discard(card, null);
this.done();
}
}
private void done() {
this.stop();
// hack to not trigger Chains of Mephistopheles recursively
Singletons.getControl().getPlayer().drawCards(1, true);
}
};
return target;
} // input_chainsDiscard()
/**
* <p>
* input_sacrificePermanent.

View File

@@ -15,35 +15,18 @@ import forge.util.MyRandom;
*/
public class GuiDialog {
/**
* <p>
* showYesNoDialog.
* </p>
*
* @param c
* a {@link forge.Card} object.
* @param question
* a {@link java.lang.String} object.
* @return a boolean.
*/
private static final String[] defaultConfirmOptions = { "Yes", "No" };
public static boolean confirm(final Card c, final String question) {
return GuiDialog.confirm(c, question, true);
return GuiDialog.confirm(c, question, true, null);
}
/**
* <p>
* showYesNoDialog.
* </p>
*
* @param c
* a {@link forge.Card} object.
* @param question
* a {@link java.lang.String} object.
* @param defaultNo
* true if the default option should be "No", false otherwise
* @return a boolean.
*/
public static boolean confirm(final Card c, String question, final boolean defaultChoice) {
public static boolean confirm(final Card c, final String question, final boolean defaultChoice) {
return GuiDialog.confirm(c, question, defaultChoice, null);
}
public static boolean confirm(final Card c, final String question, String[] options) {
return GuiDialog.confirm(c, question, true, options);
}
public static boolean confirm(final Card c, String question, final boolean defaultIsYes, final String[] options) {
CMatchUI.SINGLETON_INSTANCE.setCard(c);
final StringBuilder title = new StringBuilder();
if ( c != null)
@@ -54,14 +37,11 @@ public class GuiDialog {
}
int answer;
if (!defaultChoice) {
final Object[] options = { "Yes", "No" };
answer = JOptionPane.showOptionDialog(null, question, title.toString(), JOptionPane.YES_NO_OPTION,
JOptionPane.PLAIN_MESSAGE, null, options, options[1]);
} else {
answer = JOptionPane.showConfirmDialog(null, question, title.toString(), JOptionPane.YES_NO_OPTION);
}
String[] opts = options == null ? defaultConfirmOptions : options;
answer = JOptionPane.showOptionDialog(null, question, title.toString(), JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opts, opts[defaultIsYes ? 0 : 1]);
return answer == JOptionPane.YES_OPTION;
}

View File

@@ -120,7 +120,6 @@ public final class GuiDisplayUtil {
final Card dummy = new Card();
final Player human = Singletons.getControl().getPlayer();
dummy.setOwner(human);
dummy.addController(human);
Map<String, String> produced = new HashMap<String, String>();
produced.put("Produced", "W W W W W W W U U U U U U U B B B B B B B G G G G G G G R R R R R R R 7");
final AbilityManaPart abMana = new AbilityManaPart(dummy, produced);

View File

@@ -21,14 +21,19 @@ import java.io.File;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.eventbus.Subscribe;
import forge.Singletons;
import forge.deck.Deck;
import forge.game.GameFormat;
import forge.game.event.Event;
import forge.game.event.MulliganEvent;
import forge.game.player.PlayerType;
import forge.item.CardPrinted;
import forge.item.PreconDeck;
import forge.properties.NewConstants;
import forge.quest.bazaar.QuestBazaarManager;
import forge.quest.bazaar.QuestItemType;
import forge.quest.bazaar.QuestPetStorage;
import forge.quest.data.GameFormatQuest;
import forge.quest.data.QuestAchievements;
@@ -434,4 +439,16 @@ public class QuestController {
return unlocksAvaliable > unlocksSpent ? Math.min(unlocksAvaliable - unlocksSpent, cntLocked) : 0;
}
@Subscribe
public void receiveGameEvent(Event ev) { // Receives events only during quest games
if ( ev instanceof MulliganEvent ) {
MulliganEvent mev = (MulliganEvent)ev;
// First mulligan is free
if (mev.player.getType() == PlayerType.HUMAN && getAssets().hasItem(QuestItemType.SLEIGHT) && mev.player.getStats().getMulliganCount() == 0) {
mev.player.drawCard();
}
}
}
}