mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Room: First Spell Part (#6044)
* Room: First Spell ---- Co-authored-by: tool4ever <therealtoolkit@hotmail.com> Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59> Co-authored-by: TRT <> Co-authored-by: Anthony Calosa <anthonycalosa@gmail.com>
This commit is contained in:
@@ -1530,6 +1530,24 @@ public class PlayerControllerAi extends PlayerController {
|
||||
return SpellApiToAi.Converter.get(api).chooseCardName(player, sa, faces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICardFace chooseSingleCardFace(SpellAbility sa, List<ICardFace> faces, String message) {
|
||||
ApiType api = sa.getApi();
|
||||
if (null == api) {
|
||||
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||
}
|
||||
return SpellApiToAi.Converter.get(api).chooseCardFace(player, sa, faces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardState chooseSingleCardState(SpellAbility sa, List<CardState> states, String message, Map<String, Object> params) {
|
||||
ApiType api = sa.getApi();
|
||||
if (null == api) {
|
||||
throw new InvalidParameterException("SA is not api-based, this is not supported yet");
|
||||
}
|
||||
return SpellApiToAi.Converter.get(api).chooseCardState(player, sa, states, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseDungeon(Player ai, List<PaperCard> dungeonCards, String message) {
|
||||
// TODO: improve the conditions that define which dungeon is a viable option to choose
|
||||
|
||||
@@ -8,6 +8,7 @@ import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.cost.Cost;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
@@ -365,6 +366,18 @@ public abstract class SpellAbilityAi {
|
||||
return face == null ? "" : face.getName();
|
||||
}
|
||||
|
||||
public ICardFace chooseCardFace(Player ai, SpellAbility sa, List<ICardFace> faces) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseCardFace is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
|
||||
return Iterables.getFirst(faces, null);
|
||||
}
|
||||
|
||||
public CardState chooseCardState(Player ai, SpellAbility sa, List<CardState> faces, Map<String, Object> params) {
|
||||
System.err.println("Warning: default (ie. inherited from base class) implementation of chooseCardState is used for " + this.getClass().getName() + ". Consider declaring an overloaded method");
|
||||
|
||||
return Iterables.getFirst(faces, null);
|
||||
}
|
||||
|
||||
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||
return max;
|
||||
}
|
||||
|
||||
@@ -190,6 +190,7 @@ public enum SpellApiToAi {
|
||||
.put(ApiType.TwoPiles, TwoPilesAi.class)
|
||||
.put(ApiType.Unattach, CannotPlayAi.class)
|
||||
.put(ApiType.UnattachAll, UnattachAllAi.class)
|
||||
.put(ApiType.UnlockDoor, AlwaysPlayAi.class)
|
||||
.put(ApiType.Untap, UntapAi.class)
|
||||
.put(ApiType.UntapAll, UntapAllAi.class)
|
||||
.put(ApiType.Venture, VentureAi.class)
|
||||
|
||||
@@ -12,6 +12,7 @@ public enum CardStateName {
|
||||
RightSplit,
|
||||
Adventure,
|
||||
Modal,
|
||||
EmptyRoom,
|
||||
SpecializeW,
|
||||
SpecializeU,
|
||||
SpecializeB,
|
||||
|
||||
@@ -190,7 +190,12 @@ public class GameAction {
|
||||
// Make sure the card returns from the battlefield as the original card with two halves
|
||||
resetToOriginal = true;
|
||||
}
|
||||
} else if (!zoneTo.is(ZoneType.Stack)) {
|
||||
} else if (zoneTo.is(ZoneType.Battlefield) && c.isRoom()) {
|
||||
if (c.getCastSA() == null) {
|
||||
// need to set as empty room
|
||||
c.updateRooms();
|
||||
}
|
||||
} else if (!zoneTo.is(ZoneType.Stack) && !zoneTo.is(ZoneType.Battlefield)) {
|
||||
// For regular splits, recreate the original state unless the card is going to stack as one half
|
||||
resetToOriginal = true;
|
||||
}
|
||||
@@ -604,6 +609,9 @@ public class GameAction {
|
||||
// CR 603.6b
|
||||
if (toBattlefield) {
|
||||
zoneTo.saveLKI(copied, lastKnownInfo);
|
||||
if (copied.isRoom() && copied.getCastSA() != null) {
|
||||
copied.unlockRoom(copied.getCastSA().getActivatingPlayer(), copied.getCastSA().getCardStateName());
|
||||
}
|
||||
}
|
||||
|
||||
// only now that the LKI preserved it
|
||||
|
||||
@@ -30,6 +30,7 @@ public enum AbilityKey {
|
||||
Blockers("Blockers"),
|
||||
CanReveal("CanReveal"),
|
||||
Card("Card"),
|
||||
CardState("CardState"),
|
||||
Cards("Cards"),
|
||||
CardsFiltered("CardsFiltered"),
|
||||
CardLKI("CardLKI"),
|
||||
|
||||
@@ -194,6 +194,7 @@ public enum ApiType {
|
||||
TwoPiles (TwoPilesEffect.class),
|
||||
Unattach (UnattachEffect.class),
|
||||
UnattachAll (UnattachAllEffect.class),
|
||||
UnlockDoor (UnlockDoorEffect.class),
|
||||
Untap (UntapEffect.class),
|
||||
UntapAll (UntapAllEffect.class),
|
||||
Venture (VentureEffect.class),
|
||||
|
||||
@@ -144,6 +144,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
||||
|
||||
final long ts = game.getNextTimestamp();
|
||||
tgtCard.addCloneState(CardFactory.getCloneStates(cardToCopy, tgtCard, sa), ts);
|
||||
tgtCard.updateRooms();
|
||||
|
||||
// set ETB tapped of clone
|
||||
if (sa.hasParam("IntoPlayTapped")) {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import forge.card.CardStateName;
|
||||
import forge.game.Game;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardLists;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Localizer;
|
||||
|
||||
public class UnlockDoorEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final Game game = source.getGame();
|
||||
final Player activator = sa.getActivatingPlayer();
|
||||
|
||||
CardCollection list;
|
||||
|
||||
if (sa.hasParam("Choices")) {
|
||||
Player chooser = activator;
|
||||
String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChoose") + " ";
|
||||
|
||||
CardCollection choices = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("Choices"), activator, source, sa);
|
||||
|
||||
Card c = chooser.getController().chooseSingleEntityForEffect(choices, sa, title, Maps.newHashMap());
|
||||
if (c == null) {
|
||||
return;
|
||||
}
|
||||
list = new CardCollection(c);
|
||||
} else {
|
||||
list = getTargetCards(sa);
|
||||
}
|
||||
|
||||
for (Card c : list) {
|
||||
Map<String, Object> params = Maps.newHashMap();
|
||||
params.put("Object", c);
|
||||
switch (sa.getParamOrDefault("Mode", "ThisDoor")) {
|
||||
case "ThisDoor":
|
||||
c.unlockRoom(activator, sa.getCardStateName());
|
||||
break;
|
||||
case "Unlock":
|
||||
List<CardState> states = c.getLockedRooms().stream().map(stateName -> c.getState(stateName)).collect(Collectors.toList());
|
||||
|
||||
// need to choose Room Name
|
||||
CardState chosen = activator.getController().chooseSingleCardState(sa, states, "Choose Room to unlock", params);
|
||||
if (chosen == null) {
|
||||
continue;
|
||||
}
|
||||
c.unlockRoom(activator, chosen.getStateName());
|
||||
break;
|
||||
case "LockOrUnlock":
|
||||
switch (c.getLockedRooms().size()) {
|
||||
case 0:
|
||||
// no locked, all unlocked, can only lock door
|
||||
List<CardState> unlockStates = c.getUnlockedRooms().stream().map(stateName -> c.getState(stateName)).collect(Collectors.toList());
|
||||
CardState chosenUnlock = activator.getController().chooseSingleCardState(sa, unlockStates, "Choose Room to lock", params);
|
||||
if (chosenUnlock == null) {
|
||||
continue;
|
||||
}
|
||||
c.lockRoom(activator, chosenUnlock.getStateName());
|
||||
break;
|
||||
case 1:
|
||||
// TODO check for Lock vs Unlock first?
|
||||
List<CardState> bothStates = Lists.newArrayList();
|
||||
bothStates.add(c.getState(CardStateName.LeftSplit));
|
||||
bothStates.add(c.getState(CardStateName.RightSplit));
|
||||
CardState chosenBoth = activator.getController().chooseSingleCardState(sa, bothStates, "Choose Room to lock or unlock", params);
|
||||
if (chosenBoth == null) {
|
||||
continue;
|
||||
}
|
||||
if (c.getLockedRooms().contains(chosenBoth.getStateName())) {
|
||||
c.unlockRoom(activator, chosenBoth.getStateName());
|
||||
} else {
|
||||
c.lockRoom(activator, chosenBoth.getStateName());
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
List<CardState> lockStates = c.getLockedRooms().stream().map(stateName -> c.getState(stateName)).collect(Collectors.toList());
|
||||
|
||||
// need to choose Room Name
|
||||
CardState chosenLock = activator.getController().chooseSingleCardState(sa, lockStates, "Choose Room to unlock", params);
|
||||
if (chosenLock == null) {
|
||||
continue;
|
||||
}
|
||||
c.unlockRoom(activator, chosenLock.getStateName());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -215,6 +215,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
private boolean plotted;
|
||||
|
||||
private Set<CardStateName> unlockedRooms = EnumSet.noneOf(CardStateName.class);
|
||||
private Map<CardStateName, SpellAbility> unlockAbilities = Maps.newEnumMap(CardStateName.class);
|
||||
|
||||
private boolean specialized;
|
||||
|
||||
private int timesCrewedThisTurn = 0;
|
||||
@@ -444,6 +447,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
if (state == CardStateName.FaceDown) {
|
||||
return getFaceDownState();
|
||||
}
|
||||
if (state == CardStateName.EmptyRoom) {
|
||||
return getEmptyRoomState();
|
||||
}
|
||||
CardCloneStates clStates = getLastClonedState();
|
||||
if (clStates == null) {
|
||||
return getOriginalState(state);
|
||||
@@ -452,7 +458,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
public boolean hasState(final CardStateName state) {
|
||||
if (state == CardStateName.FaceDown) {
|
||||
if (state == CardStateName.FaceDown || state == CardStateName.EmptyRoom) {
|
||||
return true;
|
||||
}
|
||||
CardCloneStates clStates = getLastClonedState();
|
||||
@@ -466,6 +472,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
if (state == CardStateName.FaceDown) {
|
||||
return getFaceDownState();
|
||||
}
|
||||
if (state == CardStateName.EmptyRoom) {
|
||||
return getEmptyRoomState();
|
||||
}
|
||||
return states.get(state);
|
||||
}
|
||||
|
||||
@@ -492,7 +501,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
boolean needsTransformAnimation = transform || rollback;
|
||||
// faceDown has higher priority over clone states
|
||||
// while text change states doesn't apply while the card is faceDown
|
||||
if (state != CardStateName.FaceDown) {
|
||||
if (state != CardStateName.FaceDown && state != CardStateName.EmptyRoom) {
|
||||
CardCloneStates cloneStates = getLastClonedState();
|
||||
if (cloneStates != null) {
|
||||
if (!cloneStates.containsKey(state)) {
|
||||
@@ -934,6 +943,10 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
return alt ? StaticData.instance().getCommonCards().getName(name, true) : name;
|
||||
}
|
||||
|
||||
public final boolean hasNameOverwrite() {
|
||||
return changedCardNames.values().stream().anyMatch(CardChangedName::isOverwrite);
|
||||
}
|
||||
|
||||
public final boolean hasNonLegendaryCreatureNames() {
|
||||
boolean result = false;
|
||||
for (CardChangedName change : this.changedCardNames.values()) {
|
||||
@@ -1045,7 +1058,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
|
||||
public final boolean isSplitCard() {
|
||||
return getRules() != null && getRules().getSplitType() == CardSplitType.Split;
|
||||
// Normal Split Cards, these need to return true before Split States are added
|
||||
if (getRules() != null && getRules().getSplitType() == CardSplitType.Split) {
|
||||
return true;
|
||||
};
|
||||
// in case or clones or copies
|
||||
return hasState(CardStateName.LeftSplit);
|
||||
}
|
||||
|
||||
public final boolean isAdventureCard() {
|
||||
@@ -1970,7 +1988,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
public final Integer getChosenNumber() {
|
||||
return chosenNumber;
|
||||
}
|
||||
|
||||
|
||||
public final void setChosenNumber(final int i) { setChosenNumber(i, false); }
|
||||
public final void setChosenNumber(final int i, final boolean secret) {
|
||||
chosenNumber = i;
|
||||
@@ -3126,7 +3144,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
} else if (keyword.startsWith("Entwine") || keyword.startsWith("Madness")
|
||||
|| keyword.startsWith("Miracle") || keyword.startsWith("Recover")
|
||||
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
||||
|| keyword.startsWith("Disturb") || keyword.startsWith("Overload")
|
||||
|| keyword.startsWith("Disturb") || keyword.startsWith("Overload")
|
||||
|| keyword.startsWith("Plot")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final Cost cost = new Cost(k[1], false);
|
||||
@@ -5575,6 +5593,8 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
|
||||
public final boolean isOutlaw() { return getType().isOutlaw(); }
|
||||
|
||||
public final boolean isRoom() { return getType().hasSubtype("Room"); }
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public final int compareTo(final Card that) {
|
||||
@@ -5985,9 +6005,21 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
boolean shares = getName(true).equals(name);
|
||||
|
||||
// Split cards has extra logic to check if it does share a name with
|
||||
if (isSplitCard()) {
|
||||
shares |= name.equals(getState(CardStateName.LeftSplit).getName());
|
||||
shares |= name.equals(getState(CardStateName.RightSplit).getName());
|
||||
if (!shares && !hasNameOverwrite()) {
|
||||
if (isInPlay()) {
|
||||
// split cards in play are only rooms
|
||||
for (String door : getUnlockedRoomNames()) {
|
||||
shares |= name.equals(door);
|
||||
}
|
||||
} else { // not on the battlefield
|
||||
if (hasState(CardStateName.LeftSplit)) {
|
||||
shares |= name.equals(getState(CardStateName.LeftSplit).getName());
|
||||
}
|
||||
if (hasState(CardStateName.RightSplit)) {
|
||||
shares |= name.equals(getState(CardStateName.RightSplit).getName());
|
||||
}
|
||||
}
|
||||
// TODO does it need extra check for stack?
|
||||
}
|
||||
|
||||
if (!shares && hasNonLegendaryCreatureNames()) {
|
||||
@@ -6635,7 +6667,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
if (plotted == true && !isLKI()) {
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.BecomesPlotted, runParams, false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7474,6 +7506,15 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
}
|
||||
|
||||
if (isInPlay() && !isPhasedOut() && player.canCastSorcery()) {
|
||||
if (getCurrentStateName() == CardStateName.RightSplit || getCurrentStateName() == CardStateName.EmptyRoom) {
|
||||
abilities.add(getUnlockAbility(CardStateName.LeftSplit));
|
||||
}
|
||||
if (getCurrentStateName() == CardStateName.LeftSplit || getCurrentStateName() == CardStateName.EmptyRoom) {
|
||||
abilities.add(getUnlockAbility(CardStateName.RightSplit));
|
||||
}
|
||||
}
|
||||
|
||||
if (isInPlay() && isFaceDown() && oState.getType().isCreature() && oState.getManaCost() != null && !oState.getManaCost().isNoCost())
|
||||
{
|
||||
if (isManifested()) {
|
||||
@@ -8075,4 +8116,107 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
||||
}
|
||||
return StaticAbilityWitherDamage.isWitherDamage(this);
|
||||
}
|
||||
|
||||
public Set<CardStateName> getUnlockedRooms() {
|
||||
return this.unlockedRooms;
|
||||
}
|
||||
public void setUnlockedRooms(Set<CardStateName> set) {
|
||||
this.unlockedRooms = set;
|
||||
}
|
||||
|
||||
public List<String> getUnlockedRoomNames() {
|
||||
List<String> result = Lists.newArrayList();
|
||||
for (CardStateName stateName : unlockedRooms) {
|
||||
if (this.hasState(stateName)) {
|
||||
result.add(this.getState(stateName).getName());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Set<CardStateName> getLockedRooms() {
|
||||
Set<CardStateName> result = Sets.newHashSet(CardStateName.LeftSplit, CardStateName.RightSplit);
|
||||
result.removeAll(this.unlockedRooms);
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<String> getLockedRoomNames() {
|
||||
List<String> result = Lists.newArrayList();
|
||||
for (CardStateName stateName : getLockedRooms()) {
|
||||
if (this.hasState(stateName)) {
|
||||
result.add(this.getState(stateName).getName());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean unlockRoom(Player p, CardStateName stateName) {
|
||||
if (unlockedRooms.contains(stateName) || (stateName != CardStateName.LeftSplit && stateName != CardStateName.RightSplit)) {
|
||||
return false;
|
||||
}
|
||||
unlockedRooms.add(stateName);
|
||||
|
||||
updateRooms();
|
||||
|
||||
Map<AbilityKey, Object> unlockParams = AbilityKey.mapFromPlayer(p);
|
||||
unlockParams.put(AbilityKey.Card, this);
|
||||
unlockParams.put(AbilityKey.CardState, getState(stateName));
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.UnlockDoor, unlockParams, true);
|
||||
|
||||
// fully unlock
|
||||
if (unlockedRooms.size() > 1) {
|
||||
Map<AbilityKey, Object> fullyUnlockParams = AbilityKey.mapFromPlayer(p);
|
||||
fullyUnlockParams.put(AbilityKey.Card, this);
|
||||
|
||||
getGame().getTriggerHandler().runTrigger(TriggerType.FullyUnlock, fullyUnlockParams, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean lockRoom(Player p, CardStateName stateName) {
|
||||
if (!unlockedRooms.contains(stateName) || (stateName != CardStateName.LeftSplit && stateName != CardStateName.RightSplit)) {
|
||||
return false;
|
||||
}
|
||||
unlockedRooms.remove(stateName);
|
||||
|
||||
updateRooms();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void updateRooms() {
|
||||
if (!this.isRoom()) {
|
||||
return;
|
||||
}
|
||||
if (this.isFaceDown()) {
|
||||
return;
|
||||
}
|
||||
if (unlockedRooms.isEmpty()) {
|
||||
this.setState(CardStateName.EmptyRoom, true);
|
||||
} else if (unlockedRooms.size() > 1) {
|
||||
this.setState(CardStateName.Original, true);
|
||||
} else { // we already know the set is only one
|
||||
for (CardStateName name : unlockedRooms) {
|
||||
this.setState(name, true);
|
||||
}
|
||||
}
|
||||
// update trigger after state change
|
||||
getGame().getTriggerHandler().clearActiveTriggers(this, null);
|
||||
getGame().getTriggerHandler().registerActiveTrigger(this, false);
|
||||
}
|
||||
|
||||
public CardState getEmptyRoomState() {
|
||||
if (!states.containsKey(CardStateName.EmptyRoom)) {
|
||||
states.put(CardStateName.EmptyRoom, CardUtil.getEmptyRoomCharacteristic(this));
|
||||
}
|
||||
return states.get(CardStateName.EmptyRoom);
|
||||
}
|
||||
|
||||
public SpellAbility getUnlockAbility(CardStateName state) {
|
||||
if (!unlockAbilities.containsKey(state)) {
|
||||
unlockAbilities.put(state, CardFactoryUtil.abilityUnlockRoom(getState(state)));
|
||||
}
|
||||
return unlockAbilities.get(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +258,16 @@ public class CardFactory {
|
||||
final CardState original = card.getState(CardStateName.Original);
|
||||
original.addNonManaAbilities(card.getCurrentState().getNonManaAbilities());
|
||||
original.addIntrinsicKeywords(card.getCurrentState().getIntrinsicKeywords()); // Copy 'Fuse' to original side
|
||||
for (Trigger t : card.getCurrentState().getTriggers()) {
|
||||
if (t.isIntrinsic()) {
|
||||
original.addTrigger(t.copy(card, false));
|
||||
}
|
||||
}
|
||||
for (StaticAbility st : card.getCurrentState().getStaticAbilities()) {
|
||||
if (st.isIntrinsic()) {
|
||||
original.addStaticAbility(st.copy(card, false));
|
||||
}
|
||||
}
|
||||
original.getSVars().putAll(card.getCurrentState().getSVars()); // Unfortunately need to copy these to (Effect looks for sVars on execute)
|
||||
} else if (state != CardStateName.Original) {
|
||||
CardFactoryUtil.setupKeywordedAbilities(card);
|
||||
@@ -415,7 +425,11 @@ public class CardFactory {
|
||||
c.setAttractionLights(face.getAttractionLights());
|
||||
|
||||
// SpellPermanent only for Original State
|
||||
if (c.getCurrentStateName() == CardStateName.Original || c.getCurrentStateName() == CardStateName.Modal || c.getCurrentStateName().toString().startsWith("Specialize")) {
|
||||
if (c.getCurrentStateName() == CardStateName.Original ||
|
||||
c.getCurrentStateName() == CardStateName.LeftSplit ||
|
||||
c.getCurrentStateName() == CardStateName.RightSplit ||
|
||||
c.getCurrentStateName() == CardStateName.Modal ||
|
||||
c.getCurrentStateName().toString().startsWith("Specialize")) {
|
||||
if (c.isLand()) {
|
||||
SpellAbility sa = new LandAbility(c);
|
||||
sa.setCardState(c.getCurrentState());
|
||||
|
||||
@@ -119,6 +119,12 @@ public class CardFactoryUtil {
|
||||
return morphDown;
|
||||
}
|
||||
|
||||
public static SpellAbility abilityUnlockRoom(CardState cardState) {
|
||||
String unlockStr = "ST$ UnlockDoor | Cost$ " + cardState.getManaCost().getShortString() + " | Unlock$ True | SpellDescription$ Unlock " + cardState.getName();
|
||||
|
||||
return AbilityFactory.getAbility(unlockStr, cardState);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* abilityMorphUp.
|
||||
|
||||
@@ -215,6 +215,24 @@ public final class CardUtil {
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static CardState getEmptyRoomCharacteristic(Card c) {
|
||||
return getEmptyRoomCharacteristic(c, CardStateName.EmptyRoom);
|
||||
}
|
||||
public static CardState getEmptyRoomCharacteristic(Card c, CardStateName state) {
|
||||
final CardType type = new CardType(false);
|
||||
type.add("Enchantment");
|
||||
type.add("Room");
|
||||
final CardState ret = new CardState(c, state);
|
||||
|
||||
ret.setName("");
|
||||
ret.setType(type);
|
||||
|
||||
// find new image key for empty room
|
||||
ret.setImageKey(c.getImageKey());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// a nice entry point with minimum parameters
|
||||
public static Set<String> getReflectableManaColors(final SpellAbility sa) {
|
||||
return getReflectableManaColors(sa, sa, Sets.newHashSet(), new CardCollection());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forge.game.card;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import forge.ImageKeys;
|
||||
import forge.StaticData;
|
||||
@@ -40,6 +41,14 @@ public class CardView extends GameEntityView {
|
||||
return s == null ? null : s.getView();
|
||||
}
|
||||
|
||||
public static Map<CardStateView, CardState> getStateMap(Iterable<CardState> states) {
|
||||
Map<CardStateView, CardState> stateViewCache = Maps.newLinkedHashMap();
|
||||
for (CardState state : states) {
|
||||
stateViewCache.put(state.getView(), state);
|
||||
}
|
||||
return stateViewCache;
|
||||
}
|
||||
|
||||
public CardView getBackup() {
|
||||
if (get(TrackableProperty.PaperCardBackup) == null)
|
||||
return null;
|
||||
@@ -795,7 +804,7 @@ public class CardView extends GameEntityView {
|
||||
sb.append(getOwner().getCommanderInfo(this)).append("\r\n");
|
||||
}
|
||||
|
||||
if (isSplitCard() && !isFaceDown() && getZone() != ZoneType.Stack) {
|
||||
if (isSplitCard() && !isFaceDown() && getZone() != ZoneType.Stack && getZone() != ZoneType.Battlefield) {
|
||||
sb.append("(").append(getLeftSplitState().getName()).append(") ");
|
||||
sb.append(getLeftSplitState().getAbilityText());
|
||||
sb.append("\r\n\r\n").append("(").append(getRightSplitState().getName()).append(") ");
|
||||
@@ -1183,6 +1192,11 @@ public class CardView extends GameEntityView {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getId(), state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (getName() + " (" + getDisplayId() + ")").trim();
|
||||
|
||||
@@ -64,6 +64,8 @@ import org.apache.commons.lang3.tuple.Pair;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -3954,4 +3956,12 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
Map.Entry<Long, Player> e = declaresBlockers.lastEntry();
|
||||
return e == null ? null : e.getValue();
|
||||
}
|
||||
|
||||
public List<String> getUnlockedDoors() {
|
||||
return StreamSupport.stream(getCardsIn(ZoneType.Battlefield).spliterator(), false)
|
||||
.filter(Card::isRoom)
|
||||
.map(Card::getUnlockedRoomNames)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +243,8 @@ public abstract class PlayerController {
|
||||
public abstract byte chooseColorAllowColorless(String message, Card c, ColorSet colors);
|
||||
|
||||
public abstract ICardFace chooseSingleCardFace(SpellAbility sa, String message, Predicate<ICardFace> cpp, String name);
|
||||
public abstract ICardFace chooseSingleCardFace(SpellAbility sa, List<ICardFace> faces, String message);
|
||||
public abstract CardState chooseSingleCardState(SpellAbility sa, List<CardState> states, String message, Map<String, Object> params);
|
||||
public abstract List<String> chooseColors(String message, SpellAbility sa, int min, int max, List<String> options);
|
||||
|
||||
public abstract CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, String prompt, Map<String, Object> params);
|
||||
|
||||
@@ -144,6 +144,7 @@ public enum TriggerType {
|
||||
TurnBegin(TriggerTurnBegin.class),
|
||||
TurnFaceUp(TriggerTurnFaceUp.class),
|
||||
Unattach(TriggerUnattach.class),
|
||||
UnlockDoor(TriggerUnlockDoor.class),
|
||||
UntapAll(TriggerUntapAll.class),
|
||||
Untaps(TriggerUntaps.class),
|
||||
VisitAttraction(TriggerVisitAttraction.class),
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package forge.game.trigger;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardState;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Localizer;
|
||||
|
||||
public class TriggerUnlockDoor extends Trigger {
|
||||
|
||||
public TriggerUnlockDoor(final Map<String, String> params, final Card host, final boolean intrinsic) {
|
||||
super(params, host, intrinsic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performTest(Map<AbilityKey, Object> runParams) {
|
||||
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Card))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Player))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasParam("ThisDoor")) {
|
||||
CardState state = (CardState) runParams.get(AbilityKey.CardState);
|
||||
// This Card
|
||||
if (!getHostCard().equals(state.getCard())) {
|
||||
return false;
|
||||
}
|
||||
// This Face
|
||||
if (!getCardStateName().equals(state.getStateName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTriggeringObjects(SpellAbility sa, Map<AbilityKey, Object> runParams) {
|
||||
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Card, AbilityKey.Player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImportantStackObjects(SpellAbility sa) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(Localizer.getInstance().getMessage("lblPlayer")).append(": ").append(sa.getTriggeringObject(AbilityKey.Player));
|
||||
sb.append(", ").append(Localizer.getInstance().getMessage("lblCard")).append(": ").append(sa.getTriggeringObject(AbilityKey.Card));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public abstract class TrackableObject implements IIdentifiable, Serializable {
|
||||
@Override
|
||||
public final boolean equals(final Object o) {
|
||||
if (o == null) { return false; }
|
||||
return o.hashCode() == id && o.getClass().equals(getClass());
|
||||
return o.hashCode() == hashCode() && o.getClass().equals(getClass());
|
||||
}
|
||||
|
||||
// don't know if this is really needed, but don't know a better way
|
||||
|
||||
@@ -198,7 +198,7 @@ public class CardDetailPanel extends SkinnedPanel {
|
||||
nameCost = name;
|
||||
} else {
|
||||
final String manaCost;
|
||||
if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack) { //only display current state's mana cost when on stack
|
||||
if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack && card.getZone() != ZoneType.Battlefield) { //only display current state's mana cost when on stack
|
||||
manaCost = card.getLeftSplitState().getManaCost() + " // " + card.getAlternateState().getManaCost();
|
||||
} else {
|
||||
manaCost = state.getManaCost().toString();
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.regex.Pattern;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import forge.card.CardRarity;
|
||||
import forge.card.CardStateName;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.game.card.CardView;
|
||||
import forge.game.card.CardView.CardStateView;
|
||||
@@ -748,8 +749,11 @@ public class FCardImageRenderer {
|
||||
//draw type
|
||||
x += padding;
|
||||
w -= padding;
|
||||
String typeLine = CardDetailUtil.formatCardType(state, true).replace(" - ", " — ");
|
||||
drawVerticallyCenteredString(g, typeLine, new Rectangle(x, y, w, h), TYPE_FONT, TYPE_SIZE);
|
||||
// check for shared type line
|
||||
if (!state.getType().hasStringType("Room") || state.getState() != CardStateName.RightSplit) {
|
||||
String typeLine = CardDetailUtil.formatCardType(state, true).replace(" - ", " — ");
|
||||
drawVerticallyCenteredString(g, typeLine, new Rectangle(x, y, w, h), TYPE_FONT, TYPE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -479,12 +479,15 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl
|
||||
|
||||
private void displayIconOverlay(final Graphics g, final boolean canShow) {
|
||||
if (canShow && showCardManaCostOverlay() && cardWidth < 200) {
|
||||
final boolean showSplitMana = card.isSplitCard();
|
||||
final boolean showSplitMana = card.isSplitCard() && card.getZone() != ZoneType.Battlefield;
|
||||
if (!showSplitMana) {
|
||||
drawManaCost(g, card.getCurrentState().getManaCost(), 0);
|
||||
} else {
|
||||
if (!card.isFaceDown()) { // no need to draw mana symbols on face down split cards (e.g. manifested)
|
||||
PaperCard pc = StaticData.instance().getCommonCards().getCard(card.getName());
|
||||
PaperCard pc = null;
|
||||
if (!card.getName().isEmpty()) {
|
||||
pc = StaticData.instance().getCommonCards().getCard(card.getName());
|
||||
}
|
||||
int ofs = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH) ? -12 : 12;
|
||||
|
||||
drawManaCost(g, card.getLeftSplitState().getManaCost(), ofs);
|
||||
|
||||
@@ -727,6 +727,18 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICardFace chooseSingleCardFace(SpellAbility sa, List<ICardFace> faces, String message) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardState chooseSingleCardState(SpellAbility sa, List<CardState> states, String message, Map<String, Object> params) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card chooseDungeon(Player player, List<PaperCard> dungeonCards, String message) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
@@ -277,7 +277,7 @@ public class CardImageRenderer {
|
||||
if (!noText && state != null) {
|
||||
//draw mana cost for card
|
||||
ManaCost mainManaCost = state.getManaCost();
|
||||
if (card.isSplitCard() && card.getAlternateState() != null) {
|
||||
if (card.isSplitCard() && card.getAlternateState() != null && !card.isFaceDown() && card.getZone() != ZoneType.Stack && card.getZone() != ZoneType.Battlefield) {
|
||||
//handle rendering both parts of split card
|
||||
mainManaCost = card.getLeftSplitState().getManaCost();
|
||||
ManaCost otherManaCost = card.getRightSplitState().getManaCost();
|
||||
@@ -1112,7 +1112,7 @@ public class CardImageRenderer {
|
||||
float manaCostWidth = 0;
|
||||
if (canShow) {
|
||||
ManaCost mainManaCost = state.getManaCost();
|
||||
if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack) { //only display current state's mana cost when on stack
|
||||
if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack && card.getZone() != ZoneType.Battlefield) { //only display current state's mana cost when on stack
|
||||
//handle rendering both parts of split card
|
||||
mainManaCost = card.getLeftSplitState().getManaCost();
|
||||
ManaCost otherManaCost = card.getAlternateState().getManaCost();
|
||||
|
||||
@@ -852,19 +852,17 @@ public class CardRenderer {
|
||||
}
|
||||
if (showCardManaCostOverlay(card)) {
|
||||
float manaSymbolSize = w / 4.5f;
|
||||
if (card.isSplitCard() && card.hasAlternateState()) {
|
||||
if (!card.isFaceDown()) { // no need to draw mana symbols on face down split cards (e.g. manifested)
|
||||
if (isChoiceList) {
|
||||
if (card.getRightSplitState().getName().equals(details.getName()))
|
||||
drawManaCost(g, card.getRightSplitState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
|
||||
else
|
||||
drawManaCost(g, card.getLeftSplitState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
|
||||
} else {
|
||||
ManaCost leftManaCost = card.getLeftSplitState().getManaCost();
|
||||
ManaCost rightManaCost = card.getRightSplitState().getManaCost();
|
||||
drawManaCost(g, leftManaCost, x - padding, y-(manaSymbolSize/1.5f), w + 2 * padding, h, manaSymbolSize);
|
||||
drawManaCost(g, rightManaCost, x - padding, y+(manaSymbolSize/1.5f), w + 2 * padding, h, manaSymbolSize);
|
||||
}
|
||||
if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack && card.getZone() != ZoneType.Battlefield) {
|
||||
if (isChoiceList) {
|
||||
if (card.getRightSplitState().getName().equals(details.getName()))
|
||||
drawManaCost(g, card.getRightSplitState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
|
||||
else
|
||||
drawManaCost(g, card.getLeftSplitState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
|
||||
} else {
|
||||
ManaCost leftManaCost = card.getLeftSplitState().getManaCost();
|
||||
ManaCost rightManaCost = card.getRightSplitState().getManaCost();
|
||||
drawManaCost(g, leftManaCost, x - padding, y-(manaSymbolSize/1.5f), w + 2 * padding, h, manaSymbolSize);
|
||||
drawManaCost(g, rightManaCost, x - padding, y+(manaSymbolSize/1.5f), w + 2 * padding, h, manaSymbolSize);
|
||||
}
|
||||
} else {
|
||||
drawManaCost(g, showAltState ? card.getAlternateState().getManaCost() : card.getCurrentState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
Name:Dollmaker's Shop
|
||||
ManaCost:1 W
|
||||
Types:Enchantment Room
|
||||
T:Mode$ AttackersDeclaredOneTarget | Execute$ TrigToken | AttackedTarget$ Player | ValidAttackers$ Creature.YouCtrl+!Toy | TriggerZones$ Battlefield | AttackingPlayer$ You | TriggerDescription$ Whenever one or more non-Toy creatures you control attack a player, create a 1/1 white Toy artifact creature token.
|
||||
SVar:TrigToken:DB$ Token | TokenScript$ w_1_1_a_toy
|
||||
AlternateMode:Split
|
||||
Oracle:(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)\nWhenever one or more non-Toy creatures you control attack a player, create a 1/1 white Toy artifact creature token.
|
||||
|
||||
ALTERNATE
|
||||
|
||||
Name:Porcelain Gallery
|
||||
ManaCost:4 W W
|
||||
Types:Enchantment Room
|
||||
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AffectedZone$ Battlefield | SetPower$ X | SetToughness$ X | Description$ Creatures you control have base power and toughness each equal to the number of creatures you control.
|
||||
SVar:X:Count$Valid Creature.YouCtrl
|
||||
Oracle:(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)\nCreatures you control have base power and toughness each equal to the number of creatures you control.
|
||||
8
forge-gui/res/cardsfolder/upcoming/ghostly_keybearer.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/ghostly_keybearer.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Name:Ghostly Keybearer
|
||||
ManaCost:3 U
|
||||
Types:Creature Spirit
|
||||
PT:3/3
|
||||
K:Flying
|
||||
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ TrigUnlock | CombatDamage$ True | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, unlock a locked door of up to one target Room you control.
|
||||
SVar:TrigUnlock:DB$ UnlockDoor | Mode$ Unlock | ValidTgts$ Room.YouCtrl | TgtPrompt$ Choose target Room you control | TargetMin$ 0 | TargetMax$ 1
|
||||
Oracle:Flying\nWhenever Ghostly Keybearer deals combat damage to a player, unlock a locked door of up to one target Room you control.
|
||||
@@ -0,0 +1,16 @@
|
||||
Name:Glassworks
|
||||
ManaCost:2 R
|
||||
Types:Enchantment Room
|
||||
T:Mode$ UnlockDoor | ValidPlayer$ You | ValidCard$ Card.Self | ThisDoor$ True | Execute$ TrigDamage | TriggerDescription$ When you unlock this door, this Room deals 4 damage to target creature an opponent controls.
|
||||
SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ 4
|
||||
AlternateMode:Split
|
||||
Oracle:(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)\nWhen you unlock this door, this Room deals 4 damage to target creature an opponent controls.
|
||||
|
||||
ALTERNATE
|
||||
|
||||
Name:Shattered Yard
|
||||
ManaCost:4 R R
|
||||
Types:Enchantment Room
|
||||
T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigAllDamage | TriggerDescription$ At the beginning of your end step, this Room deals 1 damage to each opponent.
|
||||
SVar:TrigAllDamage:DB$ DamageAll | ValidPlayers$ Player.Opponent | NumDmg$ 1
|
||||
Oracle:(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)\nAt the beginning of your end step, this Room deals 1 damage to each opponent.
|
||||
6
forge-gui/res/tokenscripts/w_1_1_a_toy.txt
Normal file
6
forge-gui/res/tokenscripts/w_1_1_a_toy.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Name:Toy Token
|
||||
ManaCost:no cost
|
||||
Types:Artifact Creature Toy
|
||||
Colors:white
|
||||
PT:1/1
|
||||
Oracle:
|
||||
@@ -19,6 +19,7 @@ import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardView.CardStateView;
|
||||
import forge.game.card.token.TokenInfo;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.combat.CombatUtil;
|
||||
@@ -1829,6 +1830,11 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
return StaticData.instance().getCommonCards().getFaceByName(cardFaceView.getOracleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICardFace chooseSingleCardFace(SpellAbility sa, List<ICardFace> faces, String message) {
|
||||
return getGui().one(message, faces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CounterType chooseCounterType(final List<CounterType> options, final SpellAbility sa, final String prompt,
|
||||
Map<String, Object> params) {
|
||||
@@ -1838,6 +1844,16 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
return getGui().one(prompt, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardState chooseSingleCardState(SpellAbility sa, List<CardState> states, String message, Map<String, Object> params) {
|
||||
if (states.size() <= 1) {
|
||||
return Iterables.getFirst(states, null);
|
||||
}
|
||||
Map<CardStateView, CardState> cache = CardView.getStateMap(states);
|
||||
CardStateView chosen = getGui().one(message, Lists.newArrayList(cache.keySet()));
|
||||
return cache.get(chosen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseKeywordForPump(final List<String> options, final SpellAbility sa, final String prompt, final Card tgtCard) {
|
||||
if (options.size() <= 1) {
|
||||
@@ -3216,7 +3232,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
|
||||
@Override
|
||||
public String chooseCardName(SpellAbility sa, List<ICardFace> faces, String message) {
|
||||
ICardFace face = getGui().one(message, faces);
|
||||
ICardFace face = chooseSingleCardFace(sa, faces, message);
|
||||
return face == null ? "" : face.getName();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user