mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 19:58:00 +00:00
Merge branch 'master' into mutate
This commit is contained in:
@@ -26,7 +26,7 @@ public final class CardRelationMatrixGenerator {
|
||||
|
||||
public static HashMap<String,HashMap<String,List<Map.Entry<PaperCard,Integer>>>> cardPools = new HashMap<>();
|
||||
|
||||
public static Map<String, Map<String,List<List<String>>>> ldaPools = new HashMap();
|
||||
public static Map<String, Map<String,List<List<String>>>> ldaPools = new HashMap<>();
|
||||
/**
|
||||
To ensure that only cards with at least 14 connections (as 14*4+4=60) are included in the card based deck
|
||||
generation pools
|
||||
|
||||
@@ -270,7 +270,7 @@ public abstract class GuiDownloadService implements Runnable {
|
||||
String decodedKey = decodeURL(kv.getKey());
|
||||
File fileDest = new File(decodedKey);
|
||||
final String filePath = fileDest.getPath();
|
||||
final String subLastIndex = filePath.contains("pics") ? "\\pics\\" : "\\db\\";
|
||||
final String subLastIndex = filePath.contains("pics") ? "\\pics\\" : filePath.contains("skins") ? "\\"+FileUtil.getParent(filePath)+"\\" : "\\db\\";
|
||||
|
||||
System.out.println(count + "/" + totalCount + " - .." + filePath.substring(filePath.lastIndexOf(subLastIndex)+1));
|
||||
|
||||
@@ -409,8 +409,11 @@ public abstract class GuiDownloadService implements Runnable {
|
||||
protected abstract Map<String, String> getNeededFiles();
|
||||
|
||||
protected static void addMissingItems(Map<String, String> list, String nameUrlFile, String dir) {
|
||||
addMissingItems(list, nameUrlFile, dir, false);
|
||||
}
|
||||
protected static void addMissingItems(Map<String, String> list, String nameUrlFile, String dir, boolean includeParent) {
|
||||
for (Pair<String, String> nameUrlPair : FileUtil.readNameUrlFile(nameUrlFile)) {
|
||||
File f = new File(dir, decodeURL(nameUrlPair.getLeft()));
|
||||
File f = new File(includeParent? dir+FileUtil.getParent(nameUrlPair.getRight()) : dir , decodeURL(nameUrlPair.getLeft()));
|
||||
//System.out.println(f.getAbsolutePath());
|
||||
if (!f.exists()) {
|
||||
list.put(f.getAbsolutePath(), nameUrlPair.getRight());
|
||||
|
||||
23
forge-gui/src/main/java/forge/download/GuiDownloadSkins.java
Normal file
23
forge-gui/src/main/java/forge/download/GuiDownloadSkins.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package forge.download;
|
||||
|
||||
import forge.properties.ForgeConstants;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class GuiDownloadSkins extends GuiDownloadService {
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Download Skins";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Map<String, String> getNeededFiles() {
|
||||
// read all card names and urls
|
||||
final Map<String, String> urls = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
addMissingItems(urls, ForgeConstants.SKINS_LIST_FILE, ForgeConstants.CACHE_SKINS_DIR, true);
|
||||
|
||||
return urls;
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ public interface IGuiBase {
|
||||
String showFileDialog(String title, String defaultDir);
|
||||
File getSaveFile(File defaultFile);
|
||||
void download(GuiDownloadService service, Callback<Boolean> callback);
|
||||
void refreshSkin();
|
||||
void showCardList(String title, String message, List<PaperCard> list);
|
||||
boolean showBoxedProduct(String title, String message, List<PaperCard> list);
|
||||
PaperCard chooseCard(String title, String message, List<PaperCard> list);
|
||||
|
||||
@@ -230,6 +230,11 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
|
||||
case Meld:
|
||||
case Modal:
|
||||
return true;
|
||||
case Adventure:
|
||||
if (cv.isFaceDown()) {
|
||||
return getCurrentPlayer() == null || cv.canFaceDownBeShownToAny(getLocalPlayers());
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package forge.match.input;
|
||||
import java.util.*;
|
||||
|
||||
import forge.GuiBase;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.spellability.SpellAbilityView;
|
||||
import forge.util.TextUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -18,15 +16,11 @@ import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaAtom;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.PlayerView;
|
||||
import forge.game.replacement.ReplacementEffect;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.player.HumanPlay;
|
||||
import forge.player.PlayerControllerHuman;
|
||||
@@ -77,7 +71,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
// Mobile Forge allows to tap cards underneath the current card even if the current one is tapped
|
||||
if (otherCardsToSelect != null) {
|
||||
for (Card c : otherCardsToSelect) {
|
||||
for (SpellAbility sa : c.getManaAbilities()) {
|
||||
for (SpellAbility sa : getAllManaAbilities(c)) {
|
||||
if (sa.canPlay()) {
|
||||
delaySelectCards.add(c);
|
||||
break;
|
||||
@@ -85,16 +79,17 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!card.getManaAbilities().isEmpty() && activateManaAbility(card)) {
|
||||
if (!getAllManaAbilities(card).isEmpty() && activateManaAbility(card)) {
|
||||
return true;
|
||||
}
|
||||
return activateDelayedCard();
|
||||
} else {
|
||||
List<SpellAbility> manaAbilities = getAllManaAbilities(card);
|
||||
// Desktop Forge floating menu functionality
|
||||
if (card.getManaAbilities().size() == 1) {
|
||||
activateManaAbility(card, card.getManaAbilities().get(0));
|
||||
if (manaAbilities.size() == 1) {
|
||||
activateManaAbility(card, manaAbilities.get(0));
|
||||
} else {
|
||||
SpellAbility spellAbility = getController().getAbilityToPlay(card, Lists.newArrayList(card.getManaAbilities()), triggerEvent);
|
||||
SpellAbility spellAbility = getController().getAbilityToPlay(card, manaAbilities, triggerEvent);
|
||||
if (spellAbility != null) {
|
||||
activateManaAbility(card, spellAbility);
|
||||
}
|
||||
@@ -103,9 +98,29 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
}
|
||||
}
|
||||
|
||||
protected List<SpellAbility> getAllManaAbilities(Card card) {
|
||||
List<SpellAbility> result = Lists.newArrayList();
|
||||
for (SpellAbility sa : card.getManaAbilities()) {
|
||||
result.add(sa);
|
||||
result.addAll(GameActionUtil.getAlternativeCosts(sa, player));
|
||||
}
|
||||
final Collection<SpellAbility> toRemove = Lists.newArrayListWithCapacity(result.size());
|
||||
for (final SpellAbility sa : result) {
|
||||
sa.setActivatingPlayer(player);
|
||||
// fix things like retrace
|
||||
// check only if SA can't be cast normally
|
||||
if (sa.canPlay(true)) {
|
||||
continue;
|
||||
}
|
||||
toRemove.add(sa);
|
||||
}
|
||||
result.removeAll(toRemove);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivateAction(Card card) {
|
||||
for (SpellAbility sa : card.getManaAbilities()) {
|
||||
for (SpellAbility sa : getAllManaAbilities(card)) {
|
||||
if (sa.canPlay()) {
|
||||
return "pay mana with card";
|
||||
}
|
||||
@@ -135,6 +150,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public List<SpellAbility> getUsefulManaAbilities(Card card) {
|
||||
List<SpellAbility> abilities = new ArrayList<>();
|
||||
|
||||
@@ -151,7 +167,7 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
if (manaCost.isAnyPartPayableWith((byte) ManaAtom.GENERIC, player.getManaPool())) {
|
||||
colorCanUse |= ManaAtom.GENERIC;
|
||||
}
|
||||
if (colorCanUse == 0) { // no mana cost or something
|
||||
if (colorCanUse == 0) { // no mana cost or something
|
||||
return abilities;
|
||||
}
|
||||
|
||||
@@ -160,15 +176,10 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
return abilities;
|
||||
}
|
||||
|
||||
for (SpellAbility ma : card.getManaAbilities()) {
|
||||
for (SpellAbility ma : getAllManaAbilities(card)) {
|
||||
ma.setActivatingPlayer(player);
|
||||
AbilityManaPart m = ma.getManaPartRecursive();
|
||||
if (m == null || !ma.canPlay()) { continue; }
|
||||
if (!abilityProducesManaColor(ma, m, colorCanUse)) { continue; }
|
||||
if (ma.isAbility() && ma.getRestrictions().isInstantSpeed()) { continue; }
|
||||
if (!m.meetsManaRestrictions(saPaidFor)) { continue; }
|
||||
|
||||
abilities.add(ma);
|
||||
if (ma.isManaAbilityFor(saPaidFor, colorCanUse))
|
||||
abilities.add(ma);
|
||||
}
|
||||
return abilities;
|
||||
}
|
||||
@@ -189,11 +200,8 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
System.err.print("Should wait till previous call to playAbility finishes.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// make sure computer's lands aren't selected
|
||||
if (card.getController() != player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte colorCanUse = 0;
|
||||
byte colorNeeded = 0;
|
||||
@@ -206,106 +214,96 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
colorCanUse |= ManaAtom.GENERIC;
|
||||
}
|
||||
|
||||
if (colorCanUse == 0) { // no mana cost or something
|
||||
if (colorCanUse == 0) { // no mana cost or something
|
||||
return false;
|
||||
}
|
||||
|
||||
HashMap<SpellAbilityView, SpellAbility> abilitiesMap = new HashMap<>();
|
||||
// you can't remove unneeded abilities inside a for (am:abilities) loop :(
|
||||
|
||||
final String typeRes = manaCost.getSourceRestriction();
|
||||
if (StringUtils.isNotBlank(typeRes) && !card.getType().hasStringType(typeRes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean guessAbilityWithRequiredColors = true;
|
||||
int amountOfMana = -1;
|
||||
for (SpellAbility ma : card.getManaAbilities()) {
|
||||
ma.setActivatingPlayer(player);
|
||||
|
||||
AbilityManaPart m = ma.getManaPartRecursive();
|
||||
if (m == null || !ma.canPlay()) { continue; }
|
||||
if (!abilityProducesManaColor(ma, m, colorCanUse)) { continue; }
|
||||
if (ma.isAbility() && ma.getRestrictions().isInstantSpeed()) { continue; }
|
||||
if (!m.meetsManaRestrictions(saPaidFor)) { continue; }
|
||||
|
||||
// If Mana Abilities produce differing amounts of mana, let the player choose
|
||||
int maAmount = GameActionUtil.amountOfManaGenerated(ma, true);
|
||||
if (amountOfMana == -1) {
|
||||
amountOfMana = maAmount;
|
||||
} else {
|
||||
if (amountOfMana != maAmount) {
|
||||
guessAbilityWithRequiredColors = false;
|
||||
}
|
||||
}
|
||||
|
||||
abilitiesMap.put(ma.getView(), ma);
|
||||
|
||||
// skip express mana if the ability is not undoable or reusable
|
||||
if (!ma.isUndoable() || !ma.getPayCosts().isRenewableResource() || ma.getSubAbility() != null) {
|
||||
guessAbilityWithRequiredColors = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (abilitiesMap.isEmpty() || (chosenAbility != null && !abilitiesMap.containsKey(chosenAbility.getView()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store some information about color costs to help with any mana choices
|
||||
if (colorNeeded == 0) { // only colorless left
|
||||
if (saPaidFor.getHostCard() != null && saPaidFor.getHostCard().hasSVar("ManaNeededToAvoidNegativeEffect")) {
|
||||
String[] negEffects = saPaidFor.getHostCard().getSVar("ManaNeededToAvoidNegativeEffect").split(",");
|
||||
for (String negColor : negEffects) {
|
||||
byte col = ManaAtom.fromName(negColor);
|
||||
colorCanUse |= col;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the card has any ability that tracks mana spent, skip express Mana choice
|
||||
if (saPaidFor.tracksManaSpent()) {
|
||||
colorCanUse = ColorSet.ALL_COLORS.getColor();
|
||||
guessAbilityWithRequiredColors = false;
|
||||
}
|
||||
|
||||
boolean choice = true;
|
||||
boolean isPayingGeneric = false;
|
||||
if (guessAbilityWithRequiredColors) {
|
||||
// express Mana Choice
|
||||
if (colorNeeded == 0) {
|
||||
choice = false;
|
||||
//avoid unnecessary prompt by pretending we need White
|
||||
//for the sake of "Add one mana of any color" effects
|
||||
colorNeeded = MagicColor.WHITE;
|
||||
isPayingGeneric = true; // for further processing
|
||||
}
|
||||
else {
|
||||
final HashMap<SpellAbilityView, SpellAbility> colorMatches = new HashMap<>();
|
||||
for (SpellAbility sa : abilitiesMap.values()) {
|
||||
if (abilityProducesManaColor(sa, sa.getManaPartRecursive(), colorNeeded)) {
|
||||
colorMatches.put(sa.getView(), sa);
|
||||
}
|
||||
}
|
||||
|
||||
if (colorMatches.isEmpty()) {
|
||||
// can only match colorless just grab the first and move on.
|
||||
// This is wrong. Sometimes all abilities aren't created equal
|
||||
choice = false;
|
||||
}
|
||||
else if (colorMatches.size() < abilitiesMap.size()) {
|
||||
// leave behind only color matches
|
||||
abilitiesMap = colorMatches;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exceptions for cards that have conditional abilities which are better handled manually
|
||||
if (card.getName().equals("Cavern of Souls") && isPayingGeneric) {
|
||||
choice = true;
|
||||
}
|
||||
|
||||
final SpellAbility chosen;
|
||||
if (chosenAbility == null) {
|
||||
HashMap<SpellAbilityView, SpellAbility> abilitiesMap = new HashMap<>();
|
||||
// you can't remove unneeded abilities inside a for (am:abilities) loop :(
|
||||
|
||||
final String typeRes = manaCost.getSourceRestriction();
|
||||
if (StringUtils.isNotBlank(typeRes) && !card.getType().hasStringType(typeRes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean guessAbilityWithRequiredColors = true;
|
||||
int amountOfMana = -1;
|
||||
for (SpellAbility ma : getAllManaAbilities(card)) {
|
||||
ma.setActivatingPlayer(player);
|
||||
|
||||
if (!ma.isManaAbilityFor(saPaidFor, colorCanUse)) { continue; }
|
||||
|
||||
// If Mana Abilities produce differing amounts of mana, let the player choose
|
||||
int maAmount = ma.totalAmountOfManaGenerated(saPaidFor, true);
|
||||
if (amountOfMana == -1) {
|
||||
amountOfMana = maAmount;
|
||||
} else {
|
||||
if (amountOfMana != maAmount) {
|
||||
guessAbilityWithRequiredColors = false;
|
||||
}
|
||||
}
|
||||
|
||||
abilitiesMap.put(ma.getView(), ma);
|
||||
|
||||
// skip express mana if the ability is not undoable or reusable
|
||||
if (!ma.isUndoable() || !ma.getPayCosts().isRenewableResource() || ma.getSubAbility() != null
|
||||
|| ma.isManaCannotCounter(saPaidFor)) {
|
||||
guessAbilityWithRequiredColors = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (abilitiesMap.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store some information about color costs to help with any mana choices
|
||||
if (colorNeeded == 0) { // only colorless left
|
||||
if (saPaidFor.getHostCard() != null && saPaidFor.getHostCard().hasSVar("ManaNeededToAvoidNegativeEffect")) {
|
||||
String[] negEffects = saPaidFor.getHostCard().getSVar("ManaNeededToAvoidNegativeEffect").split(",");
|
||||
for (String negColor : negEffects) {
|
||||
byte col = ManaAtom.fromName(negColor);
|
||||
colorCanUse |= col;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the card has any ability that tracks mana spent, skip express Mana choice
|
||||
if (saPaidFor.tracksManaSpent()) {
|
||||
colorCanUse = ColorSet.ALL_COLORS.getColor();
|
||||
guessAbilityWithRequiredColors = false;
|
||||
}
|
||||
|
||||
boolean choice = true;
|
||||
if (guessAbilityWithRequiredColors) {
|
||||
// express Mana Choice
|
||||
if (colorNeeded == 0) {
|
||||
choice = false;
|
||||
//avoid unnecessary prompt by pretending we need White
|
||||
//for the sake of "Add one mana of any color" effects
|
||||
colorNeeded = MagicColor.WHITE;
|
||||
}
|
||||
else {
|
||||
final HashMap<SpellAbilityView, SpellAbility> colorMatches = new HashMap<>();
|
||||
for (SpellAbility sa : abilitiesMap.values()) {
|
||||
if (sa.isManaAbilityFor(saPaidFor, colorNeeded)) {
|
||||
colorMatches.put(sa.getView(), sa);
|
||||
}
|
||||
}
|
||||
|
||||
if (colorMatches.isEmpty()) {
|
||||
// can only match colorless just grab the first and move on.
|
||||
// This is wrong. Sometimes all abilities aren't created equal
|
||||
choice = false;
|
||||
}
|
||||
else if (colorMatches.size() < abilitiesMap.size()) {
|
||||
// leave behind only color matches
|
||||
abilitiesMap = colorMatches;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<SpellAbilityView> choices = new ArrayList<>(abilitiesMap.keySet());
|
||||
chosen = abilitiesMap.size() > 1 && choice ? abilitiesMap.get(getController().getGui().one(Localizer.getInstance().getMessage("lblChooseManaAbility"), choices)) : abilitiesMap.get(choices.get(0));
|
||||
} else {
|
||||
@@ -317,14 +315,13 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
// Filter the colors for the express choice so that only actually producible colors can be chosen
|
||||
int producedColorMask = 0;
|
||||
for (final byte color : ManaAtom.MANATYPES) {
|
||||
if (chosen.getManaPartRecursive().getOrigProduced().contains(MagicColor.toShortString(color))
|
||||
&& colors.hasAnyColor(color)) {
|
||||
if (chosen.canProduce(MagicColor.toShortString(color)) && colors.hasAnyColor(color)) {
|
||||
producedColorMask |= color;
|
||||
}
|
||||
}
|
||||
ColorSet producedAndNeededColors = ColorSet.fromMask(producedColorMask);
|
||||
|
||||
chosen.getManaPartRecursive().setExpressChoice(producedAndNeededColors);
|
||||
chosen.setManaExpressChoice(producedAndNeededColors);
|
||||
|
||||
// System.out.println("Chosen sa=" + chosen + " of " + chosen.getHostCard() + " to pay mana");
|
||||
|
||||
@@ -344,62 +341,6 @@ public abstract class InputPayMana extends InputSyncronizedBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean abilityProducesManaColor(final SpellAbility am, AbilityManaPart m, final byte neededColor) {
|
||||
if (0 != (neededColor & ManaAtom.GENERIC)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m.isAnyMana()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for produce mana replacement effects - they mess this up, so just use the mana ability
|
||||
final Card source = am.getHostCard();
|
||||
final Player activator = am.getActivatingPlayer();
|
||||
final Game g = source.getGame();
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.newMap();
|
||||
repParams.put(AbilityKey.Mana, m.getOrigProduced());
|
||||
repParams.put(AbilityKey.Affected, source);
|
||||
repParams.put(AbilityKey.Player, activator);
|
||||
repParams.put(AbilityKey.AbilityMana, am);
|
||||
|
||||
for (final Player p : g.getPlayers()) {
|
||||
for (final Card crd : p.getAllCards()) {
|
||||
for (final ReplacementEffect replacementEffect : crd.getReplacementEffects()) {
|
||||
if (replacementEffect.requirementsCheck(g)
|
||||
&& replacementEffect.getMode() == ReplacementType.ProduceMana
|
||||
&& replacementEffect.canReplace(repParams)
|
||||
&& replacementEffect.hasParam("ManaReplacement")
|
||||
&& replacementEffect.zonesCheck(g.getZoneOf(crd))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (am.getApi() == ApiType.ManaReflected) {
|
||||
final Iterable<String> reflectableColors = CardUtil.getReflectableManaColors(am);
|
||||
for (final String color : reflectableColors) {
|
||||
if (0 != (neededColor & ManaAtom.fromName(color))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// treat special mana if it always can be paid
|
||||
if (m.isSpecialMana()) {
|
||||
return true;
|
||||
}
|
||||
String colorsProduced = m.isComboMana() ? m.getComboColors() : m.mana();
|
||||
for (final String color : colorsProduced.split(" ")) {
|
||||
if (0 != (neededColor & ManaAtom.fromName(color))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isAlreadyPaid() {
|
||||
if (manaCost.isPaid()) {
|
||||
bPaid = true;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package forge.match.input;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.FThreads;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameObject;
|
||||
@@ -22,6 +25,7 @@ import forge.util.ITriggerEvent;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -33,21 +37,25 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
||||
private final Map<GameEntity, Integer> targetDepth = new HashMap<>();
|
||||
private final TargetRestrictions tgt;
|
||||
private final SpellAbility sa;
|
||||
private final Collection<Integer> divisionValues;
|
||||
private Card lastTarget = null;
|
||||
private boolean bCancel = false;
|
||||
private boolean bOk = false;
|
||||
private final boolean mandatory;
|
||||
private Predicate<GameObject> filter;
|
||||
private static final long serialVersionUID = -1091595663541356356L;
|
||||
|
||||
public final boolean hasCancelled() { return bCancel; }
|
||||
public final boolean hasPressedOk() { return bOk; }
|
||||
|
||||
public InputSelectTargets(final PlayerControllerHuman controller, final List<Card> choices, final SpellAbility sa, final boolean mandatory) {
|
||||
public InputSelectTargets(final PlayerControllerHuman controller, final List<Card> choices, final SpellAbility sa, final boolean mandatory, Collection<Integer> divisionValues, Predicate<GameObject> filter) {
|
||||
super(controller);
|
||||
this.choices = choices;
|
||||
this.tgt = sa.getTargetRestrictions();
|
||||
this.sa = sa;
|
||||
this.mandatory = mandatory;
|
||||
this.divisionValues = divisionValues;
|
||||
this.filter = filter;
|
||||
controller.getGui().setSelectables(CardView.getCollection(choices));
|
||||
final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
|
||||
for (final Card c : choices) {
|
||||
@@ -97,7 +105,7 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
||||
sb.append(sa.getUniqueTargets());
|
||||
}
|
||||
|
||||
final int maxTargets = tgt.getMaxTargets(sa.getHostCard(), sa);
|
||||
final int maxTargets = sa.getMaxTargets();
|
||||
final int targeted = sa.getTargets().size();
|
||||
if(maxTargets > 1) {
|
||||
sb.append(TextUtil.concatNoSpace("\n(", String.valueOf(maxTargets - targeted), " more can be targeted)"));
|
||||
@@ -108,10 +116,10 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
||||
"(Targeting ERROR)", "");
|
||||
showMessage(message, sa.getView());
|
||||
|
||||
if (tgt.isDividedAsYouChoose() && tgt.getMinTargets(sa.getHostCard(), sa) == 0 && sa.getTargets().size() == 0) {
|
||||
if (sa.isDividedAsYouChoose() && sa.getMinTargets() == 0 && sa.getTargets().size() == 0) {
|
||||
// extra logic for Divided with min targets = 0, should only work if num targets are 0 too
|
||||
getController().getGui().updateButtons(getOwner(), true, true, false);
|
||||
} else if (!tgt.isMinTargetsChosen(sa.getHostCard(), sa) || tgt.isDividedAsYouChoose()) {
|
||||
} else if (!sa.isMinTargetChosen() || sa.isDividedAsYouChoose()) {
|
||||
// If reached Minimum targets, enable OK button
|
||||
if (mandatory && tgt.hasCandidates(sa, true)) {
|
||||
// Player has to click on a target
|
||||
@@ -239,40 +247,11 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tgt.isDividedAsYouChoose()) {
|
||||
final int stillToDivide = tgt.getStillToDivide();
|
||||
int allocatedPortion = 0;
|
||||
// allow allocation only if the max targets isn't reached and there are more candidates
|
||||
if ((sa.getTargets().size() + 1 < tgt.getMaxTargets(sa.getHostCard(), sa))
|
||||
&& (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
|
||||
final ImmutableList.Builder<Integer> choices = ImmutableList.builder();
|
||||
for (int i = 1; i <= stillToDivide; i++) {
|
||||
choices.add(Integer.valueOf(i));
|
||||
}
|
||||
String apiBasedMessage = "Distribute how much to ";
|
||||
if (sa.getApi() == ApiType.DealDamage) {
|
||||
apiBasedMessage = "Select how much damage to deal to ";
|
||||
}
|
||||
else if (sa.getApi() == ApiType.PreventDamage) {
|
||||
apiBasedMessage = "Select how much damage to prevent to ";
|
||||
}
|
||||
else if (sa.getApi() == ApiType.PutCounter) {
|
||||
apiBasedMessage = "Select how many counters to distribute to ";
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(apiBasedMessage);
|
||||
sb.append(card.toString());
|
||||
final Integer chosen = getController().getGui().oneOrNone(sb.toString(), choices.build());
|
||||
if (chosen == null) {
|
||||
return true; //still return true since there was a valid choice
|
||||
}
|
||||
allocatedPortion = chosen;
|
||||
if (sa.isDividedAsYouChoose()) {
|
||||
Boolean val = onDividedAsYouChoose(card);
|
||||
if (val != null) {
|
||||
return val;
|
||||
}
|
||||
else { // otherwise assign the rest of the damage/protection
|
||||
allocatedPortion = stillToDivide;
|
||||
}
|
||||
tgt.setStillToDivide(stillToDivide - allocatedPortion);
|
||||
tgt.addDividedAllocation(card, allocatedPortion);
|
||||
}
|
||||
addTarget(card);
|
||||
return true;
|
||||
@@ -305,12 +284,49 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
||||
showMessage(sa.getHostCard() + " - Cannot target this player (Hexproof? Protection? Restrictions?).");
|
||||
return;
|
||||
}
|
||||
if (filter != null && !filter.apply(player)) {
|
||||
showMessage(sa.getHostCard() + " - Cannot target this player (Hexproof? Protection? Restrictions?).");
|
||||
return;
|
||||
}
|
||||
|
||||
if (tgt.isDividedAsYouChoose()) {
|
||||
final int stillToDivide = tgt.getStillToDivide();
|
||||
if (sa.isDividedAsYouChoose()) {
|
||||
Boolean val = onDividedAsYouChoose(player);
|
||||
if (val != null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
addTarget(player);
|
||||
}
|
||||
|
||||
protected Boolean onDividedAsYouChoose(GameObject go) {
|
||||
if (divisionValues != null) {
|
||||
if (divisionValues.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String apiBasedMessage = "Distribute how much to ";
|
||||
if (sa.getApi() == ApiType.DealDamage) {
|
||||
apiBasedMessage = "Select how much damage to deal to ";
|
||||
}
|
||||
else if (sa.getApi() == ApiType.PreventDamage) {
|
||||
apiBasedMessage = "Select how much damage to prevent to ";
|
||||
}
|
||||
else if (sa.getApi() == ApiType.PutCounter) {
|
||||
apiBasedMessage = "Select how many counters to distribute to ";
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(apiBasedMessage);
|
||||
sb.append(go.toString());
|
||||
final Integer chosen = getController().getGui().oneOrNone(sb.toString(), Lists.newArrayList(divisionValues));
|
||||
if (chosen == null) {
|
||||
return true; //still return true since there was a valid choice
|
||||
}
|
||||
divisionValues.remove(chosen);
|
||||
sa.addDividedAllocation(go, chosen);
|
||||
} else {
|
||||
final int stillToDivide = sa.getStillToDivide();
|
||||
int allocatedPortion = 0;
|
||||
// allow allocation only if the max targets isn't reached and there are more candidates
|
||||
if ((sa.getTargets().size() + 1 < tgt.getMaxTargets(sa.getHostCard(), sa)) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
|
||||
if ((sa.getTargets().size() + 1 < sa.getMaxTargets()) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) {
|
||||
final ImmutableList.Builder<Integer> choices = ImmutableList.builder();
|
||||
for (int i = 1; i <= stillToDivide; i++) {
|
||||
choices.add(Integer.valueOf(i));
|
||||
@@ -323,19 +339,18 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(apiBasedMessage);
|
||||
sb.append(player.getName());
|
||||
sb.append(go.toString());
|
||||
final Integer chosen = getController().getGui().oneOrNone(sb.toString(), choices.build());
|
||||
if (null == chosen) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
allocatedPortion = chosen;
|
||||
} else { // otherwise assign the rest of the damage/protection
|
||||
allocatedPortion = stillToDivide;
|
||||
}
|
||||
tgt.setStillToDivide(stillToDivide - allocatedPortion);
|
||||
tgt.addDividedAllocation(player, allocatedPortion);
|
||||
sa.addDividedAllocation(go, allocatedPortion);
|
||||
}
|
||||
addTarget(player);
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addTarget(final GameEntity ge) {
|
||||
@@ -366,7 +381,7 @@ public final class InputSelectTargets extends InputSyncronizedBase {
|
||||
}
|
||||
|
||||
private boolean hasAllTargets() {
|
||||
return tgt.isMaxTargetsChosen(sa.getHostCard(), sa) || ( tgt.getStillToDivide() == 0 && tgt.isDividedAsYouChoose());
|
||||
return sa.isMaxTargetChosen() || (sa.isDividedAsYouChoose() && sa.getStillToDivide() == 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,6 +35,7 @@ import forge.game.player.Player;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.spellability.*;
|
||||
import forge.game.zone.Zone;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -207,6 +208,12 @@ public class HumanPlaySpellAbility {
|
||||
oldCard.getZone().remove(oldCard);
|
||||
fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null);
|
||||
ability.setHostCard(oldCard);
|
||||
// better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost)
|
||||
for (SpellAbility sa : oldCard.getSpells()) {
|
||||
sa.setHostCard(oldCard);
|
||||
}
|
||||
//for Chorus of the Conclave
|
||||
ability.rollback();
|
||||
|
||||
oldCard.setBackSide(false);
|
||||
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
||||
|
||||
@@ -1064,19 +1064,20 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
* SpellAbility, forge.card.spellability.SpellAbilityStackInstance)
|
||||
*/
|
||||
@Override
|
||||
public TargetChoices chooseNewTargetsFor(final SpellAbility ability) {
|
||||
public TargetChoices chooseNewTargetsFor(final SpellAbility ability, Predicate<GameObject> filter, boolean optional) {
|
||||
final SpellAbility sa = ability.isWrapper() ? ((WrappedAbility) ability).getWrappedAbility() : ability;
|
||||
if (sa.getTargetRestrictions() == null) {
|
||||
if (!sa.usesTargeting()) {
|
||||
return null;
|
||||
}
|
||||
final TargetChoices oldTarget = sa.getTargets();
|
||||
final TargetSelection select = new TargetSelection(this, sa);
|
||||
sa.resetTargets();
|
||||
if (select.chooseTargets(oldTarget.size())) {
|
||||
if (select.chooseTargets(oldTarget.size(), Lists.newArrayList(oldTarget.getDividedValues()), filter, optional)) {
|
||||
return sa.getTargets();
|
||||
} else {
|
||||
sa.setTargets(oldTarget);
|
||||
// Return old target, since we had to reset them above
|
||||
return oldTarget;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1798,11 +1799,8 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
player.getGame().getStackZone().add(next.getHostCard());
|
||||
}
|
||||
// TODO check if static abilities needs to be run for things affecting the copy?
|
||||
if (next.isMayChooseNewTargets() && !next.setupTargets()) {
|
||||
// if targets can't be done, remove copy from existence
|
||||
if (next.isSpell()) {
|
||||
next.getHostCard().ceaseToExist();
|
||||
}
|
||||
if (next.isMayChooseNewTargets()) {
|
||||
next.setupNewTargets(player);
|
||||
}
|
||||
}
|
||||
player.getGame().getStack().add(next);
|
||||
@@ -1823,7 +1821,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
@Override
|
||||
public boolean chooseTargetsFor(final SpellAbility currentAbility) {
|
||||
final TargetSelection select = new TargetSelection(this, currentAbility);
|
||||
return select.chooseTargets(null);
|
||||
return select.chooseTargets(null, null, null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package forge.player;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import forge.game.Game;
|
||||
@@ -25,6 +27,7 @@ import forge.game.GameEntityView;
|
||||
import forge.game.GameEntityViewMap;
|
||||
import forge.game.GameObject;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CardView;
|
||||
import forge.game.player.PlayerView;
|
||||
@@ -38,6 +41,7 @@ import forge.match.input.InputSelectTargets;
|
||||
import forge.util.Aggregates;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -70,15 +74,15 @@ public class TargetSelection {
|
||||
return ability.isTrigger() || getTgt().getMandatory();
|
||||
}
|
||||
|
||||
public final boolean chooseTargets(Integer numTargets) {
|
||||
public final boolean chooseTargets(Integer numTargets, Collection<Integer> divisionValues, Predicate<GameObject> filter, boolean optional) {
|
||||
if (!ability.usesTargeting()) {
|
||||
throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability);
|
||||
}
|
||||
final TargetRestrictions tgt = getTgt();
|
||||
|
||||
// Number of targets is explicitly set only if spell is being redirected (ex. Swerve or Redirect)
|
||||
final int minTargets = numTargets != null ? numTargets.intValue() : tgt.getMinTargets(ability.getHostCard(), ability);
|
||||
final int maxTargets = numTargets != null ? numTargets.intValue() : tgt.getMaxTargets(ability.getHostCard(), ability);
|
||||
final int minTargets = numTargets != null ? numTargets.intValue() : ability.getMinTargets();
|
||||
final int maxTargets = numTargets != null ? numTargets.intValue() : ability.getMaxTargets();
|
||||
//final int maxTotalCMC = tgt.getMaxTotalCMC(ability.getHostCard(), ability);
|
||||
final int numTargeted = ability.getTargets().size();
|
||||
final boolean isSingleZone = ability.getTargetRestrictions().isSingleZone();
|
||||
@@ -92,7 +96,7 @@ public class TargetSelection {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.bTargetingDone && hasEnoughTargets || hasAllTargets || tgt.isDividedAsYouChoose() && tgt.getStillToDivide() == 0) {
|
||||
if (this.bTargetingDone && hasEnoughTargets || hasAllTargets || ability.isDividedAsYouChoose() && divisionValues == null && ability.getStillToDivide() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -107,11 +111,10 @@ public class TargetSelection {
|
||||
}
|
||||
|
||||
final List<ZoneType> zones = tgt.getZone();
|
||||
final boolean mandatory = isMandatory() && hasCandidates;
|
||||
final boolean mandatory = isMandatory() && hasCandidates && !optional;
|
||||
|
||||
final boolean choiceResult;
|
||||
final boolean random = tgt.isRandomTarget();
|
||||
if (random) {
|
||||
if (tgt.isRandomTarget() && numTargets == null) {
|
||||
final List<GameEntity> candidates = tgt.getAllCandidates(this.ability, true);
|
||||
final GameObject choice = Aggregates.random(candidates);
|
||||
return ability.getTargets().add(choice);
|
||||
@@ -122,7 +125,11 @@ public class TargetSelection {
|
||||
return this.chooseCardFromStack(mandatory);
|
||||
}
|
||||
else {
|
||||
final List<Card> validTargets = CardUtil.getValidCardsToTarget(tgt, ability);
|
||||
List<Card> validTargets = CardUtil.getValidCardsToTarget(tgt, ability);
|
||||
if (filter != null) {
|
||||
validTargets = new CardCollection(Iterables.filter(validTargets, filter));
|
||||
}
|
||||
|
||||
// single zone
|
||||
if (isSingleZone) {
|
||||
final List<Card> removeCandidates = new ArrayList<>();
|
||||
@@ -149,8 +156,8 @@ public class TargetSelection {
|
||||
//if only one valid target card for triggered ability, auto-target that card
|
||||
//only do this for triggered abilities to prevent auto-targeting when user chooses
|
||||
//to play a spell or activat an ability
|
||||
if (tgt.isDividedAsYouChoose()) {
|
||||
tgt.addDividedAllocation(validTargets.get(0), tgt.getStillToDivide());
|
||||
if (ability.isDividedAsYouChoose()) {
|
||||
ability.addDividedAllocation(validTargets.get(0), ability.getStillToDivide());
|
||||
}
|
||||
return ability.getTargets().add(validTargets.get(0));
|
||||
}
|
||||
@@ -162,7 +169,7 @@ public class TargetSelection {
|
||||
PlayerView playerView = controller.getLocalPlayerView();
|
||||
PlayerZoneUpdates playerZoneUpdates = controller.getGui().openZones(playerView, zones, playersWithValidTargets);
|
||||
if (!zones.contains(ZoneType.Stack)) {
|
||||
InputSelectTargets inp = new InputSelectTargets(controller, validTargets, ability, mandatory);
|
||||
InputSelectTargets inp = new InputSelectTargets(controller, validTargets, ability, mandatory, divisionValues, filter);
|
||||
inp.showAndWait();
|
||||
choiceResult = !inp.hasCancelled();
|
||||
bTargetingDone = inp.hasPressedOk();
|
||||
@@ -174,7 +181,7 @@ public class TargetSelection {
|
||||
}
|
||||
}
|
||||
// some inputs choose cards one-by-one and need to be called again
|
||||
return choiceResult && chooseTargets(numTargets);
|
||||
return choiceResult && chooseTargets(numTargets, divisionValues, filter, optional);
|
||||
}
|
||||
|
||||
private final boolean chooseCardFromList(final List<Card> choices, final boolean targeted, final boolean mandatory) {
|
||||
@@ -232,7 +239,7 @@ public class TargetSelection {
|
||||
}
|
||||
|
||||
final String msgDone = "[FINISH TARGETING]";
|
||||
if (this.getTgt().isMinTargetsChosen(this.ability.getHostCard(), this.ability)) {
|
||||
if (ability.isMinTargetChosen()) {
|
||||
// is there a more elegant way of doing this?
|
||||
choicesFiltered.add(msgDone);
|
||||
}
|
||||
@@ -282,12 +289,12 @@ public class TargetSelection {
|
||||
}
|
||||
|
||||
while(!bTargetingDone) {
|
||||
if (tgt.isMaxTargetsChosen(this.ability.getHostCard(), this.ability)) {
|
||||
if (ability.isMaxTargetChosen()) {
|
||||
bTargetingDone = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!selectOptions.contains("[FINISH TARGETING]") && tgt.isMinTargetsChosen(this.ability.getHostCard(), this.ability)) {
|
||||
if (!selectOptions.contains("[FINISH TARGETING]") && ability.isMinTargetChosen()) {
|
||||
selectOptions.add("[FINISH TARGETING]");
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ public final class ForgeConstants {
|
||||
public static final String NET_DECKS_COMMANDER_LIST_FILE = LISTS_DIR + "net-decks-commander.txt";
|
||||
public static final String NET_DECKS_BRAWL_LIST_FILE = LISTS_DIR + "net-decks-brawl.txt";
|
||||
public static final String BORDERLESS_CARD_LIST_FILE = LISTS_DIR + "borderlessCardList.txt";
|
||||
public static final String SKINS_LIST_FILE = LISTS_DIR + "skinsList.txt";
|
||||
|
||||
|
||||
public static final String CHANGES_FILE = ASSETS_DIR + "README.txt";
|
||||
@@ -89,9 +90,9 @@ public final class ForgeConstants {
|
||||
private static final String CONQUEST_DIR = RES_DIR + "conquest" + PATH_SEPARATOR;
|
||||
public static final String CONQUEST_PLANES_DIR = CONQUEST_DIR + "planes" + PATH_SEPARATOR;
|
||||
|
||||
public static final String SKINS_DIR = RES_DIR + "skins" + PATH_SEPARATOR;
|
||||
public static final String BASE_SKINS_DIR = RES_DIR + "skins" + PATH_SEPARATOR;
|
||||
public static final String COMMON_FONTS_DIR = RES_DIR + "fonts" + PATH_SEPARATOR;
|
||||
public static final String DEFAULT_SKINS_DIR = SKINS_DIR + "default" + PATH_SEPARATOR;
|
||||
public static final String DEFAULT_SKINS_DIR = BASE_SKINS_DIR + "default" + PATH_SEPARATOR;
|
||||
//don't associate these skin files with a directory since skin directory will be determined later
|
||||
public static final String SPRITE_ICONS_FILE = "sprite_icons.png";
|
||||
public static final String SPRITE_FOILS_FILE = "sprite_foils.png";
|
||||
@@ -258,6 +259,7 @@ public final class ForgeConstants {
|
||||
private static final String PICS_DIR = CACHE_DIR + "pics" + PATH_SEPARATOR;
|
||||
public static final String DB_DIR = CACHE_DIR + "db" + PATH_SEPARATOR;
|
||||
public static final String FONTS_DIR = CACHE_DIR + "fonts" + PATH_SEPARATOR;
|
||||
public static final String CACHE_SKINS_DIR = CACHE_DIR + "skins" + PATH_SEPARATOR;
|
||||
public static final String CACHE_TOKEN_PICS_DIR = PICS_DIR + "tokens" + PATH_SEPARATOR;
|
||||
public static final String CACHE_ICON_PICS_DIR = PICS_DIR + "icons" + PATH_SEPARATOR;
|
||||
public static final String CACHE_SYMBOLS_DIR = PICS_DIR + "symbols" + PATH_SEPARATOR;
|
||||
|
||||
@@ -33,6 +33,7 @@ import forge.game.event.GameEventTokenCreated;
|
||||
import forge.game.event.GameEventTurnEnded;
|
||||
import forge.game.event.GameEventZone;
|
||||
import forge.game.event.IGameEventVisitor;
|
||||
import forge.game.spellability.AbilityManaPart;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.TextUtil;
|
||||
@@ -179,10 +180,10 @@ public class EventVisualizer extends IGameEventVisitor.Base<SoundEffectType> imp
|
||||
@Override
|
||||
public SoundEffectType visit(final GameEventLandPlayed event) {
|
||||
SoundEffectType resultSound = null;
|
||||
return resultSound;
|
||||
return resultSound;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public SoundEffectType visit(GameEventZone event) {
|
||||
Card card = event.card;
|
||||
@@ -208,10 +209,13 @@ public class EventVisualizer extends IGameEventVisitor.Base<SoundEffectType> imp
|
||||
// I want to get all real colors this land can produce - no interest in colorless or devoid
|
||||
StringBuilder fullManaColors = new StringBuilder();
|
||||
for (final SpellAbility sa : land.getManaAbilities()) {
|
||||
String currManaColor = sa.getManaPartRecursive().getOrigProduced();
|
||||
if(!"C".equals(currManaColor)) {
|
||||
fullManaColors.append(currManaColor);
|
||||
for (AbilityManaPart mp : sa.getAllManaParts()) {
|
||||
String currManaColor = mp.getOrigProduced();
|
||||
if(!"C".equals(currManaColor)) {
|
||||
fullManaColors.append(currManaColor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// No interest if "colors together" or "alternative colors" - only interested in colors themselves
|
||||
fullManaColors = new StringBuilder(TextUtil.fastReplace(fullManaColors.toString()," ", ""));
|
||||
|
||||
Reference in New Issue
Block a user