mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58:00 +00:00
1) Added Valakut, the Molten Pinnacle, and Expedition Map.
2) Tweaked the land-searching functions to reuse code for Expedition Map. 3) Initial fix for the human player for Cascade and Burst Lightning. A larger-scope fix including all Kicker cards, Buyback cards, and the AI will be added later.
This commit is contained in:
@@ -38,6 +38,8 @@ snow_covered_mountain.jpg http://www.wizards.com/global/images/magic/gene
|
||||
snow_covered_mountain1.jpg http://www.wizards.com/global/images/magic/general/snow_covered_mountain.jpg
|
||||
snow_covered_mountain2.jpg http://www.magickartenmarkt.de/img/cards/Ice_Age/snow_covered_mountain.jpg
|
||||
snow_covered_mountain3.jpg http://www.magickartenmarkt.de/img/cards/Ice_Age/snow_covered_mountain.jpg
|
||||
expedition_map.jpg http://www.wizards.com/global/images/magic/general/expedition_map.jpg
|
||||
valakut_the_molten_pinnacle.jpg http://www.wizards.com/global/images/magic/general/valakut_the_molten_pinnacle.jpg
|
||||
power_matrix.jpg http://www.wizards.com/global/images/magic/general/power_matrix.jpg
|
||||
scornful_aether_lich.jpg http://www.wizards.com/global/images/magic/general/scornful_aether_lich.jpg
|
||||
recall.jpg http://www.wizards.com/global/images/magic/general/recall.jpg
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
Expedition Map
|
||||
1
|
||||
Artifact
|
||||
no text
|
||||
|
||||
Valakut, the Molten Pinnacle
|
||||
no cost
|
||||
Land
|
||||
Whenever a Mountain enters the battlefield under your control, if you control at least five other Mountains, you may have Valakut, the Molten Pinnacle deal 3 damage to target creature or player.
|
||||
Valakut, the Molten Pinnacle enters the battlefield tapped.
|
||||
tap: add R
|
||||
|
||||
Serpent of the Endless Sea
|
||||
4 U
|
||||
Creature Serpent
|
||||
|
||||
@@ -291,6 +291,18 @@ public class AllZoneUtil {
|
||||
return cards;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a list of all cards of a certain type that a given player has in his library
|
||||
* @param player the player to check for cards in play
|
||||
* @param cardType the card type to check for
|
||||
* @return a CardList with all cards of a certain type the player has in his library
|
||||
*/
|
||||
public static CardList getPlayerTypeInLibrary(final String player, final String cardType) {
|
||||
CardList cards = getPlayerCardsInLibrary(player);
|
||||
cards = cards.getType(cardType);
|
||||
return cards;
|
||||
}
|
||||
|
||||
////////////// cardListFilter for different types
|
||||
public static CardListFilter artifacts = new CardListFilter() {
|
||||
public boolean addCard(Card c) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
package forge;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -18121,8 +18121,6 @@ public class CardFactory implements NewConstants {
|
||||
|
||||
@Override
|
||||
public void resolve() {
|
||||
PlayerZone grave = AllZone.getZone(Constant.Zone.Graveyard, card.getController());
|
||||
PlayerZone removed = AllZone.getZone(Constant.Zone.Removed_From_Play, card.getController());
|
||||
|
||||
if(getTargetCard() != null) {
|
||||
if(AllZone.GameAction.isCardInPlay(getTargetCard())
|
||||
@@ -18132,12 +18130,12 @@ public class CardFactory implements NewConstants {
|
||||
}
|
||||
} else AllZone.GameAction.getPlayerLife(getTargetPlayer()).subtractLife(damage);
|
||||
|
||||
grave.remove(card);
|
||||
removed.add(card);
|
||||
|
||||
card.setKicked(true);
|
||||
}
|
||||
};//flashback
|
||||
kicker.setManaCost("4 R");
|
||||
kicker.setManaCost("R 4");
|
||||
kicker.setAdditionalManaCost("4");
|
||||
kicker.setKickerAbility(true);
|
||||
kicker.setBeforePayMana(CardFactoryUtil.input_targetCreaturePlayer(kicker, true, false));
|
||||
kicker.setDescription("Kicker: 4");
|
||||
|
||||
@@ -19840,6 +19838,31 @@ public class CardFactory implements NewConstants {
|
||||
};//Input
|
||||
spell.setBeforePayMana(target);
|
||||
}//*************** END ************ END **************************
|
||||
//*************** START *********** START **************************
|
||||
else if(cardName.equals("Expedition Map")) {
|
||||
final Ability_Tap ability = new Ability_Tap(card, "2") {
|
||||
|
||||
private static final long serialVersionUID = -5796728507926918991L;
|
||||
|
||||
@Override
|
||||
public boolean canPlayAI() {
|
||||
return AllZoneUtil.getPlayerTypeInLibrary(Constant.Player.Computer,
|
||||
"Land").size() >= 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve() {
|
||||
AllZone.GameAction.searchLibraryLand("Land",
|
||||
card.getController(), Constant.Zone.Hand, false);
|
||||
AllZone.GameAction.sacrifice(card);
|
||||
}
|
||||
};//ability
|
||||
|
||||
ability.setDescription("2, tap, sacrifice Expedition Map: Search your library for a land card, reveal it, and put it into your hand. Then shuffle your library.");
|
||||
ability.setStackDescription("Sacrifice Expedition Map: search your library for a land and put it into your hand.");
|
||||
ability.setManaCost("2");
|
||||
card.addSpellAbility(ability);
|
||||
}//*************** END ************ END **************************
|
||||
|
||||
//*************** START *********** START **************************
|
||||
else if(cardName.equals("Recall")) {
|
||||
|
||||
@@ -5,6 +5,7 @@ package forge;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
@@ -1415,7 +1416,7 @@ public class GameAction {
|
||||
playSpellAbilityForFree(sa);
|
||||
}
|
||||
|
||||
public void playSpellAbilityForFree(SpellAbility sa) {
|
||||
public void playSpellAbilityForFree(final SpellAbility sa) {
|
||||
|
||||
|
||||
if(sa.getBeforePayMana() == null) {
|
||||
@@ -1423,6 +1424,17 @@ public class GameAction {
|
||||
if (sa.getSourceCard().getManaCost().contains("X"))
|
||||
x = true;
|
||||
|
||||
if (sa.isKickerAbility()) {
|
||||
Command paid1 = new Command() {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public void execute() {
|
||||
AllZone.Stack.add(sa);
|
||||
}
|
||||
};
|
||||
AllZone.InputControl.setInput(new Input_PayManaCost_Ability(sa.getAdditionalManaCost(),paid1));
|
||||
}
|
||||
AllZone.Stack.add(sa, x);
|
||||
|
||||
/*
|
||||
@@ -1435,7 +1447,12 @@ public class GameAction {
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
sa.getBeforePayMana().setFree(true);
|
||||
if (sa.isKickerAbility()) {
|
||||
sa.getBeforePayMana().setFree(false);
|
||||
sa.setManaCost(sa.getAdditionalManaCost());
|
||||
} else {
|
||||
sa.getBeforePayMana().setFree(true);
|
||||
}
|
||||
AllZone.InputControl.setInput(sa.getBeforePayMana());
|
||||
}
|
||||
}
|
||||
@@ -1718,31 +1735,70 @@ public class GameAction {
|
||||
|
||||
}
|
||||
|
||||
public void searchLibraryBasicLand(String player, String Zone1, boolean tapLand) {
|
||||
searchLibraryTwoBasicLand(player, Zone1, tapLand, "", false);
|
||||
public void searchLibraryLand(String type, String player, String Zone1, boolean tapLand) {
|
||||
searchLibraryTwoLand(type, player, Zone1, tapLand, "", false);
|
||||
}
|
||||
|
||||
public void searchLibraryTwoBasicLand(String player,
|
||||
String Zone1, boolean tapFirstLand,
|
||||
String Zone2, boolean tapSecondLand) {
|
||||
|
||||
|
||||
public void searchLibraryBasicLand(String player, String Zone1, boolean tapLand) {
|
||||
searchLibraryTwoLand("Basic", player, Zone1, tapLand, "", false);
|
||||
}
|
||||
|
||||
public void searchLibraryTwoLand(String type, String player,
|
||||
String Zone1, boolean tapFirstLand,
|
||||
String Zone2, boolean tapSecondLand) {
|
||||
if(player.equals(Constant.Player.Human)) {
|
||||
humanSearchTwoBasicLand(Zone1, tapFirstLand, Zone2, tapSecondLand);
|
||||
humanSearchTwoLand(type, Zone1, tapFirstLand, Zone2, tapSecondLand);
|
||||
} else {
|
||||
aiSearchTwoBasicLand(Zone1, tapFirstLand, Zone2, tapSecondLand);
|
||||
aiSearchTwoLand(type, Zone1, tapFirstLand, Zone2, tapSecondLand);
|
||||
}
|
||||
|
||||
AllZone.GameAction.shuffle(player);
|
||||
|
||||
}
|
||||
public void searchLibraryTwoBasicLand(String player,
|
||||
String Zone1, boolean tapFirstLand,
|
||||
String Zone2, boolean tapSecondLand) {
|
||||
searchLibraryTwoLand("Basic", player, Zone1, tapFirstLand, Zone2, tapSecondLand);
|
||||
}
|
||||
|
||||
private void aiSearchTwoBasicLand(String Zone1, boolean tapFirstLand,
|
||||
|
||||
private void aiSearchTwoLand(String type, String Zone1, boolean tapFirstLand,
|
||||
String Zone2, boolean tapSecondLand) {
|
||||
CardList land = new CardList(AllZone.Computer_Library.getCards());
|
||||
land = land.getType("Basic");
|
||||
land = land.getType(type);
|
||||
PlayerZone firstZone = AllZone.getZone(Zone1, Constant.Player.Computer);
|
||||
//just to make the computer a little less predictable
|
||||
land.shuffle();
|
||||
|
||||
if (type.contains("Basic")) {
|
||||
// No need for special sorting for basic land
|
||||
// just shuffle to make the computer a little less predictable
|
||||
land.shuffle();
|
||||
} else {
|
||||
Comparator<Card> aiLandComparator = new Comparator<Card>()
|
||||
{
|
||||
private int scoreLand(Card a) {
|
||||
String valakutName = "Valakut, the Molten Pinnacle";
|
||||
|
||||
int theScore = 0;
|
||||
if (!a.isBasicLand()) {
|
||||
// favor non-basic land
|
||||
theScore++;
|
||||
if (a.getName().contains(valakutName)) {
|
||||
// TODO: Add names of other special lands
|
||||
theScore++;
|
||||
}
|
||||
}
|
||||
return theScore;
|
||||
}
|
||||
public int compare(Card a, Card b)
|
||||
{
|
||||
int aScore = scoreLand(a);
|
||||
int bScore = scoreLand(b);
|
||||
return bScore - aScore;
|
||||
} // compare
|
||||
};//Comparator
|
||||
|
||||
// Prioritize the land somewhat
|
||||
land.sort(aiLandComparator);
|
||||
}
|
||||
//3 branches: 1-no land in deck, 2-one land in deck, 3-two or more land in deck
|
||||
if(land.size() != 0) {
|
||||
//branch 2 - at least 1 land in library
|
||||
@@ -1765,20 +1821,28 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
private void humanSearchTwoBasicLand(String Zone1, boolean tapFirstLand, String Zone2, boolean tapSecondLand) {
|
||||
private void humanSearchTwoLand(String type, String Zone1, boolean tapFirstLand, String Zone2, boolean tapSecondLand) {
|
||||
PlayerZone firstZone = AllZone.getZone(Zone1, Constant.Player.Human);
|
||||
PlayerZone library = AllZone.getZone(Constant.Zone.Library, Constant.Player.Human);
|
||||
|
||||
CardList list = new CardList(library.getCards());
|
||||
list = list.getType("Basic");
|
||||
list = list.getType(type);
|
||||
|
||||
//3 branches: 1-no land in deck, 2-one land in deck, 3-two or more land in deck
|
||||
|
||||
//branch 1
|
||||
if(list.size() == 0) return;
|
||||
|
||||
// Check whether we were only asked for one land, and adjust the prompt accordingly
|
||||
boolean onlyOneLand = (Zone2.trim().length() == 0);
|
||||
String firstPrompt;
|
||||
if (onlyOneLand)
|
||||
firstPrompt = new String("Choose a land");
|
||||
else
|
||||
firstPrompt = new String("Choose first land");
|
||||
|
||||
//branch 2
|
||||
Object o = AllZone.Display.getChoiceOptional("Choose first land", list.toArray());
|
||||
Object o = AllZone.Display.getChoiceOptional(firstPrompt, list.toArray());
|
||||
if(o != null) {
|
||||
Card c = (Card) o;
|
||||
list.remove(c);
|
||||
@@ -1789,7 +1853,7 @@ public class GameAction {
|
||||
firstZone.add(c);
|
||||
|
||||
}//if
|
||||
if ((list.size() == 0) || Zone2.trim().length() == 0) return;
|
||||
if ((list.size() == 0) || onlyOneLand) return;
|
||||
//branch 3
|
||||
o = AllZone.Display.getChoiceOptional("Choose second land", list.toArray());
|
||||
if(o != null) {
|
||||
|
||||
@@ -3742,6 +3742,118 @@ public class GameActionUtil {
|
||||
else if(c.getName().equals("Bloodghast")) landfall_Bloodghast(c);
|
||||
else if(c.getName().equals("Avenger of Zendikar")) landfall_Avenger_of_Zendikar(c);
|
||||
}
|
||||
|
||||
private static boolean checkValakutCondition(Card valakutCard, Card mtn) {
|
||||
// Get a list of all mountains
|
||||
CardList mountainList = AllZoneUtil.getPlayerTypeInPlay(valakutCard.getController(),
|
||||
"Mountain");
|
||||
// Don't count the one that just came into play
|
||||
if (mountainList.contains(mtn))
|
||||
mountainList.remove(mtn);
|
||||
|
||||
// Do not activate if at least 5 other mountains are not present.
|
||||
if (mountainList.size() < 5)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
|
||||
}
|
||||
// Returns true if the routine found enough mountains to activate the effect
|
||||
// Returns false otherwise
|
||||
// This lets the calling routine break if a player has multiple Valakut in play
|
||||
public static boolean executeValakutEffect(final Card valakutCard, final Card mtn) {
|
||||
|
||||
if (!checkValakutCondition(valakutCard, mtn))
|
||||
return false; // Tell the calling routine there aren't enough mountains, don't call again
|
||||
|
||||
SpellAbility DamageTgt = new Spell(valakutCard) {
|
||||
|
||||
private static final long serialVersionUID = -7360567876931046530L;
|
||||
|
||||
public boolean canPlayAI() {
|
||||
return getCreature().size() != 0 || AllZone.Human_Life.getLife() < 10;
|
||||
}
|
||||
|
||||
public boolean canPlay() {
|
||||
return true;
|
||||
}
|
||||
|
||||
CardList getCreature() {
|
||||
//toughness of 3
|
||||
CardList list = CardFactoryUtil.AI_getHumanCreature(3, valakutCard, true);
|
||||
list = list.filter(new CardListFilter() {
|
||||
public boolean addCard(Card c) {
|
||||
//only get 1/1 flyers or 2/1 or bigger creatures
|
||||
return (2 <= c.getNetAttack()) || c.getKeyword().contains("Flying");
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}//getCreature()
|
||||
|
||||
@Override
|
||||
public void chooseTargetAI() {
|
||||
boolean targetHuman;
|
||||
// Get a list of all creatures Valakut could destroy
|
||||
CardList list = getCreature();
|
||||
|
||||
CardList listValakut = list.filter(new CardListFilter() {
|
||||
public boolean addCard(Card c) {
|
||||
return c.getName().contains("Valakut, the Molten Pinnacle");
|
||||
}
|
||||
});
|
||||
|
||||
int lifeThreshold = Math.max( 3 * listValakut.size(), 6);
|
||||
if ( (AllZone.Human_Life.getLife() < lifeThreshold) || list.isEmpty()) {
|
||||
targetHuman = true;
|
||||
} else {
|
||||
// Remove any creatures that have been targeted by other Valakuts
|
||||
for (int ix = 0; ix < AllZone.Stack.size(); ix++) {
|
||||
SpellAbility sa = AllZone.Stack.peek(ix);
|
||||
if (sa.getSourceCard().getName().contains("Valakut, the Molten Pinnacle")) {
|
||||
Card target = sa.getTargetCard();
|
||||
if ((target != null) && list.contains(target)) {
|
||||
list.remove(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
targetHuman = true;
|
||||
} else {
|
||||
targetHuman = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(targetHuman) setTargetPlayer(Constant.Player.Human);
|
||||
else {
|
||||
list.shuffle();
|
||||
setTargetCard(list.get(0));
|
||||
}
|
||||
}//chooseTargetAI()
|
||||
|
||||
@Override
|
||||
public void resolve() {
|
||||
if (!checkValakutCondition(valakutCard, mtn))
|
||||
return;
|
||||
if(getTargetCard() != null) {
|
||||
if(AllZone.GameAction.isCardInPlay(getTargetCard())
|
||||
&& CardFactoryUtil.canTarget(valakutCard, getTargetCard())) getTargetCard().addDamage(3,
|
||||
valakutCard);
|
||||
} else AllZone.GameAction.getPlayerLife(getTargetPlayer()).subtractLife(3);
|
||||
}//resolve()
|
||||
|
||||
};
|
||||
DamageTgt.setManaCost(new String("0"));
|
||||
DamageTgt.setStackDescription("Valakut, the Molten Pinnacle deals 3 damage to target creature or player.");
|
||||
if (valakutCard.getController() == Constant.Player.Human) {
|
||||
AllZone.InputControl.setInput(CardFactoryUtil.input_targetCreaturePlayer(DamageTgt, true, true));
|
||||
} else {
|
||||
DamageTgt.chooseTargetAI();
|
||||
AllZone.Stack.add(DamageTgt);
|
||||
}
|
||||
return true; // Tell the calling routine it's okay to call again if there are other Valakuts in play
|
||||
}
|
||||
|
||||
|
||||
private static boolean showLandfallDialog(Card c) {
|
||||
String[] choices = {"Yes", "No"};
|
||||
|
||||
@@ -75,6 +75,12 @@ public class PlayerZone_ComesIntoPlay extends DefaultPlayerZone {
|
||||
CardList list = new CardList(play.getCards());
|
||||
CardList graveList = new CardList(grave.getCards());
|
||||
|
||||
CardList listValakut = list.filter(new CardListFilter() {
|
||||
public boolean addCard(Card c) {
|
||||
return c.getName().contains("Valakut, the Molten Pinnacle");
|
||||
}
|
||||
});
|
||||
|
||||
list = list.filter(new CardListFilter() {
|
||||
public boolean addCard(Card c) {
|
||||
return c.getKeyword().contains("Landfall") ||
|
||||
@@ -96,6 +102,16 @@ public class PlayerZone_ComesIntoPlay extends DefaultPlayerZone {
|
||||
GameActionUtil.executeLandfallEffects(list.get(i));
|
||||
}
|
||||
|
||||
// Check for a mountain
|
||||
if (!listValakut.isEmpty() && c.getType().contains("Mountain") ) {
|
||||
for (int i = 0; i < listValakut.size(); i++) {
|
||||
boolean b = GameActionUtil.executeValakutEffect(listValakut.get(i),c);
|
||||
if (!b) {
|
||||
// Not enough mountains to activate Valakut -- stop the loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}//isLand()
|
||||
|
||||
//hack to make tokens trigger ally effects:
|
||||
|
||||
@@ -31,6 +31,7 @@ public abstract class SpellAbility {
|
||||
private boolean flashBackAbility = false;
|
||||
private boolean multiKicker = false;
|
||||
private boolean xCost = false;
|
||||
private boolean kickerAbility = false;
|
||||
|
||||
private Input beforePayMana;
|
||||
private Input afterResolve;
|
||||
@@ -296,7 +297,12 @@ public abstract class SpellAbility {
|
||||
public boolean isFlashBackAbility() {
|
||||
return flashBackAbility;
|
||||
}
|
||||
|
||||
public void setKickerAbility(boolean kab) {
|
||||
this.kickerAbility=kab;
|
||||
}
|
||||
public boolean isKickerAbility() {
|
||||
return kickerAbility;
|
||||
}
|
||||
// Only used by Ability_Reflected_Mana, because the user has an option to cancel the input.
|
||||
// Most spell abilities and even most mana abilities do not need to use this.
|
||||
public boolean wasCancelled() {
|
||||
|
||||
Reference in New Issue
Block a user