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);
|
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
|
@Override
|
||||||
public Card chooseDungeon(Player ai, List<PaperCard> dungeonCards, String message) {
|
public Card chooseDungeon(Player ai, List<PaperCard> dungeonCards, String message) {
|
||||||
// TODO: improve the conditions that define which dungeon is a viable option to choose
|
// 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.card.mana.ManaCostParser;
|
||||||
import forge.game.GameEntity;
|
import forge.game.GameEntity;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
|
import forge.game.card.CardState;
|
||||||
import forge.game.card.CounterType;
|
import forge.game.card.CounterType;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
import forge.game.mana.ManaCostBeingPaid;
|
import forge.game.mana.ManaCostBeingPaid;
|
||||||
@@ -365,6 +366,18 @@ public abstract class SpellAbilityAi {
|
|||||||
return face == null ? "" : face.getName();
|
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) {
|
public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map<String, Object> params) {
|
||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ public enum SpellApiToAi {
|
|||||||
.put(ApiType.TwoPiles, TwoPilesAi.class)
|
.put(ApiType.TwoPiles, TwoPilesAi.class)
|
||||||
.put(ApiType.Unattach, CannotPlayAi.class)
|
.put(ApiType.Unattach, CannotPlayAi.class)
|
||||||
.put(ApiType.UnattachAll, UnattachAllAi.class)
|
.put(ApiType.UnattachAll, UnattachAllAi.class)
|
||||||
|
.put(ApiType.UnlockDoor, AlwaysPlayAi.class)
|
||||||
.put(ApiType.Untap, UntapAi.class)
|
.put(ApiType.Untap, UntapAi.class)
|
||||||
.put(ApiType.UntapAll, UntapAllAi.class)
|
.put(ApiType.UntapAll, UntapAllAi.class)
|
||||||
.put(ApiType.Venture, VentureAi.class)
|
.put(ApiType.Venture, VentureAi.class)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public enum CardStateName {
|
|||||||
RightSplit,
|
RightSplit,
|
||||||
Adventure,
|
Adventure,
|
||||||
Modal,
|
Modal,
|
||||||
|
EmptyRoom,
|
||||||
SpecializeW,
|
SpecializeW,
|
||||||
SpecializeU,
|
SpecializeU,
|
||||||
SpecializeB,
|
SpecializeB,
|
||||||
|
|||||||
@@ -190,7 +190,12 @@ public class GameAction {
|
|||||||
// Make sure the card returns from the battlefield as the original card with two halves
|
// Make sure the card returns from the battlefield as the original card with two halves
|
||||||
resetToOriginal = true;
|
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
|
// For regular splits, recreate the original state unless the card is going to stack as one half
|
||||||
resetToOriginal = true;
|
resetToOriginal = true;
|
||||||
}
|
}
|
||||||
@@ -604,6 +609,9 @@ public class GameAction {
|
|||||||
// CR 603.6b
|
// CR 603.6b
|
||||||
if (toBattlefield) {
|
if (toBattlefield) {
|
||||||
zoneTo.saveLKI(copied, lastKnownInfo);
|
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
|
// only now that the LKI preserved it
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public enum AbilityKey {
|
|||||||
Blockers("Blockers"),
|
Blockers("Blockers"),
|
||||||
CanReveal("CanReveal"),
|
CanReveal("CanReveal"),
|
||||||
Card("Card"),
|
Card("Card"),
|
||||||
|
CardState("CardState"),
|
||||||
Cards("Cards"),
|
Cards("Cards"),
|
||||||
CardsFiltered("CardsFiltered"),
|
CardsFiltered("CardsFiltered"),
|
||||||
CardLKI("CardLKI"),
|
CardLKI("CardLKI"),
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ public enum ApiType {
|
|||||||
TwoPiles (TwoPilesEffect.class),
|
TwoPiles (TwoPilesEffect.class),
|
||||||
Unattach (UnattachEffect.class),
|
Unattach (UnattachEffect.class),
|
||||||
UnattachAll (UnattachAllEffect.class),
|
UnattachAll (UnattachAllEffect.class),
|
||||||
|
UnlockDoor (UnlockDoorEffect.class),
|
||||||
Untap (UntapEffect.class),
|
Untap (UntapEffect.class),
|
||||||
UntapAll (UntapAllEffect.class),
|
UntapAll (UntapAllEffect.class),
|
||||||
Venture (VentureEffect.class),
|
Venture (VentureEffect.class),
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ public class CloneEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
final long ts = game.getNextTimestamp();
|
final long ts = game.getNextTimestamp();
|
||||||
tgtCard.addCloneState(CardFactory.getCloneStates(cardToCopy, tgtCard, sa), ts);
|
tgtCard.addCloneState(CardFactory.getCloneStates(cardToCopy, tgtCard, sa), ts);
|
||||||
|
tgtCard.updateRooms();
|
||||||
|
|
||||||
// set ETB tapped of clone
|
// set ETB tapped of clone
|
||||||
if (sa.hasParam("IntoPlayTapped")) {
|
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 boolean plotted;
|
||||||
|
|
||||||
|
private Set<CardStateName> unlockedRooms = EnumSet.noneOf(CardStateName.class);
|
||||||
|
private Map<CardStateName, SpellAbility> unlockAbilities = Maps.newEnumMap(CardStateName.class);
|
||||||
|
|
||||||
private boolean specialized;
|
private boolean specialized;
|
||||||
|
|
||||||
private int timesCrewedThisTurn = 0;
|
private int timesCrewedThisTurn = 0;
|
||||||
@@ -444,6 +447,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
if (state == CardStateName.FaceDown) {
|
if (state == CardStateName.FaceDown) {
|
||||||
return getFaceDownState();
|
return getFaceDownState();
|
||||||
}
|
}
|
||||||
|
if (state == CardStateName.EmptyRoom) {
|
||||||
|
return getEmptyRoomState();
|
||||||
|
}
|
||||||
CardCloneStates clStates = getLastClonedState();
|
CardCloneStates clStates = getLastClonedState();
|
||||||
if (clStates == null) {
|
if (clStates == null) {
|
||||||
return getOriginalState(state);
|
return getOriginalState(state);
|
||||||
@@ -452,7 +458,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasState(final CardStateName state) {
|
public boolean hasState(final CardStateName state) {
|
||||||
if (state == CardStateName.FaceDown) {
|
if (state == CardStateName.FaceDown || state == CardStateName.EmptyRoom) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
CardCloneStates clStates = getLastClonedState();
|
CardCloneStates clStates = getLastClonedState();
|
||||||
@@ -466,6 +472,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
if (state == CardStateName.FaceDown) {
|
if (state == CardStateName.FaceDown) {
|
||||||
return getFaceDownState();
|
return getFaceDownState();
|
||||||
}
|
}
|
||||||
|
if (state == CardStateName.EmptyRoom) {
|
||||||
|
return getEmptyRoomState();
|
||||||
|
}
|
||||||
return states.get(state);
|
return states.get(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,7 +501,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
boolean needsTransformAnimation = transform || rollback;
|
boolean needsTransformAnimation = transform || rollback;
|
||||||
// faceDown has higher priority over clone states
|
// faceDown has higher priority over clone states
|
||||||
// while text change states doesn't apply while the card is faceDown
|
// 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();
|
CardCloneStates cloneStates = getLastClonedState();
|
||||||
if (cloneStates != null) {
|
if (cloneStates != null) {
|
||||||
if (!cloneStates.containsKey(state)) {
|
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;
|
return alt ? StaticData.instance().getCommonCards().getName(name, true) : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final boolean hasNameOverwrite() {
|
||||||
|
return changedCardNames.values().stream().anyMatch(CardChangedName::isOverwrite);
|
||||||
|
}
|
||||||
|
|
||||||
public final boolean hasNonLegendaryCreatureNames() {
|
public final boolean hasNonLegendaryCreatureNames() {
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
for (CardChangedName change : this.changedCardNames.values()) {
|
for (CardChangedName change : this.changedCardNames.values()) {
|
||||||
@@ -1045,7 +1058,12 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isSplitCard() {
|
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() {
|
public final boolean isAdventureCard() {
|
||||||
@@ -1970,7 +1988,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
public final Integer getChosenNumber() {
|
public final Integer getChosenNumber() {
|
||||||
return chosenNumber;
|
return chosenNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setChosenNumber(final int i) { setChosenNumber(i, false); }
|
public final void setChosenNumber(final int i) { setChosenNumber(i, false); }
|
||||||
public final void setChosenNumber(final int i, final boolean secret) {
|
public final void setChosenNumber(final int i, final boolean secret) {
|
||||||
chosenNumber = i;
|
chosenNumber = i;
|
||||||
@@ -3126,7 +3144,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
} else if (keyword.startsWith("Entwine") || keyword.startsWith("Madness")
|
} else if (keyword.startsWith("Entwine") || keyword.startsWith("Madness")
|
||||||
|| keyword.startsWith("Miracle") || keyword.startsWith("Recover")
|
|| keyword.startsWith("Miracle") || keyword.startsWith("Recover")
|
||||||
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
|| keyword.startsWith("Escape") || keyword.startsWith("Foretell:")
|
||||||
|| keyword.startsWith("Disturb") || keyword.startsWith("Overload")
|
|| keyword.startsWith("Disturb") || keyword.startsWith("Overload")
|
||||||
|| keyword.startsWith("Plot")) {
|
|| keyword.startsWith("Plot")) {
|
||||||
final String[] k = keyword.split(":");
|
final String[] k = keyword.split(":");
|
||||||
final Cost cost = new Cost(k[1], false);
|
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 isOutlaw() { return getType().isOutlaw(); }
|
||||||
|
|
||||||
|
public final boolean isRoom() { return getType().hasSubtype("Room"); }
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public final int compareTo(final Card that) {
|
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);
|
boolean shares = getName(true).equals(name);
|
||||||
|
|
||||||
// Split cards has extra logic to check if it does share a name with
|
// Split cards has extra logic to check if it does share a name with
|
||||||
if (isSplitCard()) {
|
if (!shares && !hasNameOverwrite()) {
|
||||||
shares |= name.equals(getState(CardStateName.LeftSplit).getName());
|
if (isInPlay()) {
|
||||||
shares |= name.equals(getState(CardStateName.RightSplit).getName());
|
// 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()) {
|
if (!shares && hasNonLegendaryCreatureNames()) {
|
||||||
@@ -6635,7 +6667,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
if (plotted == true && !isLKI()) {
|
if (plotted == true && !isLKI()) {
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromCard(this);
|
||||||
game.getTriggerHandler().runTrigger(TriggerType.BecomesPlotted, runParams, false);
|
game.getTriggerHandler().runTrigger(TriggerType.BecomesPlotted, runParams, false);
|
||||||
}
|
}
|
||||||
return true;
|
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 (isInPlay() && isFaceDown() && oState.getType().isCreature() && oState.getManaCost() != null && !oState.getManaCost().isNoCost())
|
||||||
{
|
{
|
||||||
if (isManifested()) {
|
if (isManifested()) {
|
||||||
@@ -8075,4 +8116,107 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
|
|||||||
}
|
}
|
||||||
return StaticAbilityWitherDamage.isWitherDamage(this);
|
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);
|
final CardState original = card.getState(CardStateName.Original);
|
||||||
original.addNonManaAbilities(card.getCurrentState().getNonManaAbilities());
|
original.addNonManaAbilities(card.getCurrentState().getNonManaAbilities());
|
||||||
original.addIntrinsicKeywords(card.getCurrentState().getIntrinsicKeywords()); // Copy 'Fuse' to original side
|
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)
|
original.getSVars().putAll(card.getCurrentState().getSVars()); // Unfortunately need to copy these to (Effect looks for sVars on execute)
|
||||||
} else if (state != CardStateName.Original) {
|
} else if (state != CardStateName.Original) {
|
||||||
CardFactoryUtil.setupKeywordedAbilities(card);
|
CardFactoryUtil.setupKeywordedAbilities(card);
|
||||||
@@ -415,7 +425,11 @@ public class CardFactory {
|
|||||||
c.setAttractionLights(face.getAttractionLights());
|
c.setAttractionLights(face.getAttractionLights());
|
||||||
|
|
||||||
// SpellPermanent only for Original State
|
// 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()) {
|
if (c.isLand()) {
|
||||||
SpellAbility sa = new LandAbility(c);
|
SpellAbility sa = new LandAbility(c);
|
||||||
sa.setCardState(c.getCurrentState());
|
sa.setCardState(c.getCurrentState());
|
||||||
|
|||||||
@@ -119,6 +119,12 @@ public class CardFactoryUtil {
|
|||||||
return morphDown;
|
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>
|
* <p>
|
||||||
* abilityMorphUp.
|
* abilityMorphUp.
|
||||||
|
|||||||
@@ -215,6 +215,24 @@ public final class CardUtil {
|
|||||||
return ret;
|
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
|
// a nice entry point with minimum parameters
|
||||||
public static Set<String> getReflectableManaColors(final SpellAbility sa) {
|
public static Set<String> getReflectableManaColors(final SpellAbility sa) {
|
||||||
return getReflectableManaColors(sa, sa, Sets.newHashSet(), new CardCollection());
|
return getReflectableManaColors(sa, sa, Sets.newHashSet(), new CardCollection());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package forge.game.card;
|
package forge.game.card;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import forge.ImageKeys;
|
import forge.ImageKeys;
|
||||||
import forge.StaticData;
|
import forge.StaticData;
|
||||||
@@ -40,6 +41,14 @@ public class CardView extends GameEntityView {
|
|||||||
return s == null ? null : s.getView();
|
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() {
|
public CardView getBackup() {
|
||||||
if (get(TrackableProperty.PaperCardBackup) == null)
|
if (get(TrackableProperty.PaperCardBackup) == null)
|
||||||
return null;
|
return null;
|
||||||
@@ -795,7 +804,7 @@ public class CardView extends GameEntityView {
|
|||||||
sb.append(getOwner().getCommanderInfo(this)).append("\r\n");
|
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("(").append(getLeftSplitState().getName()).append(") ");
|
||||||
sb.append(getLeftSplitState().getAbilityText());
|
sb.append(getLeftSplitState().getAbilityText());
|
||||||
sb.append("\r\n\r\n").append("(").append(getRightSplitState().getName()).append(") ");
|
sb.append("\r\n\r\n").append("(").append(getRightSplitState().getName()).append(") ");
|
||||||
@@ -1183,6 +1192,11 @@ public class CardView extends GameEntityView {
|
|||||||
return StringUtils.EMPTY;
|
return StringUtils.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(getId(), state);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return (getName() + " (" + getDisplayId() + ")").trim();
|
return (getName() + " (" + getDisplayId() + ")").trim();
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -3954,4 +3956,12 @@ public class Player extends GameEntity implements Comparable<Player> {
|
|||||||
Map.Entry<Long, Player> e = declaresBlockers.lastEntry();
|
Map.Entry<Long, Player> e = declaresBlockers.lastEntry();
|
||||||
return e == null ? null : e.getValue();
|
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 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, 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 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);
|
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),
|
TurnBegin(TriggerTurnBegin.class),
|
||||||
TurnFaceUp(TriggerTurnFaceUp.class),
|
TurnFaceUp(TriggerTurnFaceUp.class),
|
||||||
Unattach(TriggerUnattach.class),
|
Unattach(TriggerUnattach.class),
|
||||||
|
UnlockDoor(TriggerUnlockDoor.class),
|
||||||
UntapAll(TriggerUntapAll.class),
|
UntapAll(TriggerUntapAll.class),
|
||||||
Untaps(TriggerUntaps.class),
|
Untaps(TriggerUntaps.class),
|
||||||
VisitAttraction(TriggerVisitAttraction.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
|
@Override
|
||||||
public final boolean equals(final Object o) {
|
public final boolean equals(final Object o) {
|
||||||
if (o == null) { return false; }
|
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
|
// 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;
|
nameCost = name;
|
||||||
} else {
|
} else {
|
||||||
final String manaCost;
|
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();
|
manaCost = card.getLeftSplitState().getManaCost() + " // " + card.getAlternateState().getManaCost();
|
||||||
} else {
|
} else {
|
||||||
manaCost = state.getManaCost().toString();
|
manaCost = state.getManaCost().toString();
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import java.util.regex.Pattern;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import forge.card.CardRarity;
|
import forge.card.CardRarity;
|
||||||
|
import forge.card.CardStateName;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
import forge.game.card.CardView;
|
import forge.game.card.CardView;
|
||||||
import forge.game.card.CardView.CardStateView;
|
import forge.game.card.CardView.CardStateView;
|
||||||
@@ -748,8 +749,11 @@ public class FCardImageRenderer {
|
|||||||
//draw type
|
//draw type
|
||||||
x += padding;
|
x += padding;
|
||||||
w -= padding;
|
w -= padding;
|
||||||
String typeLine = CardDetailUtil.formatCardType(state, true).replace(" - ", " — ");
|
// check for shared type line
|
||||||
drawVerticallyCenteredString(g, typeLine, new Rectangle(x, y, w, h), TYPE_FONT, TYPE_SIZE);
|
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) {
|
private void displayIconOverlay(final Graphics g, final boolean canShow) {
|
||||||
if (canShow && showCardManaCostOverlay() && cardWidth < 200) {
|
if (canShow && showCardManaCostOverlay() && cardWidth < 200) {
|
||||||
final boolean showSplitMana = card.isSplitCard();
|
final boolean showSplitMana = card.isSplitCard() && card.getZone() != ZoneType.Battlefield;
|
||||||
if (!showSplitMana) {
|
if (!showSplitMana) {
|
||||||
drawManaCost(g, card.getCurrentState().getManaCost(), 0);
|
drawManaCost(g, card.getCurrentState().getManaCost(), 0);
|
||||||
} else {
|
} else {
|
||||||
if (!card.isFaceDown()) { // no need to draw mana symbols on face down split cards (e.g. manifested)
|
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;
|
int ofs = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH) ? -12 : 12;
|
||||||
|
|
||||||
drawManaCost(g, card.getLeftSplitState().getManaCost(), ofs);
|
drawManaCost(g, card.getLeftSplitState().getManaCost(), ofs);
|
||||||
|
|||||||
@@ -727,6 +727,18 @@ public class PlayerControllerForTests extends PlayerController {
|
|||||||
return null;
|
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
|
@Override
|
||||||
public Card chooseDungeon(Player player, List<PaperCard> dungeonCards, String message) {
|
public Card chooseDungeon(Player player, List<PaperCard> dungeonCards, String message) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ public class CardImageRenderer {
|
|||||||
if (!noText && state != null) {
|
if (!noText && state != null) {
|
||||||
//draw mana cost for card
|
//draw mana cost for card
|
||||||
ManaCost mainManaCost = state.getManaCost();
|
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
|
//handle rendering both parts of split card
|
||||||
mainManaCost = card.getLeftSplitState().getManaCost();
|
mainManaCost = card.getLeftSplitState().getManaCost();
|
||||||
ManaCost otherManaCost = card.getRightSplitState().getManaCost();
|
ManaCost otherManaCost = card.getRightSplitState().getManaCost();
|
||||||
@@ -1112,7 +1112,7 @@ public class CardImageRenderer {
|
|||||||
float manaCostWidth = 0;
|
float manaCostWidth = 0;
|
||||||
if (canShow) {
|
if (canShow) {
|
||||||
ManaCost mainManaCost = state.getManaCost();
|
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
|
//handle rendering both parts of split card
|
||||||
mainManaCost = card.getLeftSplitState().getManaCost();
|
mainManaCost = card.getLeftSplitState().getManaCost();
|
||||||
ManaCost otherManaCost = card.getAlternateState().getManaCost();
|
ManaCost otherManaCost = card.getAlternateState().getManaCost();
|
||||||
|
|||||||
@@ -852,19 +852,17 @@ public class CardRenderer {
|
|||||||
}
|
}
|
||||||
if (showCardManaCostOverlay(card)) {
|
if (showCardManaCostOverlay(card)) {
|
||||||
float manaSymbolSize = w / 4.5f;
|
float manaSymbolSize = w / 4.5f;
|
||||||
if (card.isSplitCard() && card.hasAlternateState()) {
|
if (card.isSplitCard() && card.hasAlternateState() && !card.isFaceDown() && card.getZone() != ZoneType.Stack && card.getZone() != ZoneType.Battlefield) {
|
||||||
if (!card.isFaceDown()) { // no need to draw mana symbols on face down split cards (e.g. manifested)
|
if (isChoiceList) {
|
||||||
if (isChoiceList) {
|
if (card.getRightSplitState().getName().equals(details.getName()))
|
||||||
if (card.getRightSplitState().getName().equals(details.getName()))
|
drawManaCost(g, card.getRightSplitState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
|
||||||
drawManaCost(g, card.getRightSplitState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
|
else
|
||||||
else
|
drawManaCost(g, card.getLeftSplitState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
|
||||||
drawManaCost(g, card.getLeftSplitState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
|
} else {
|
||||||
} else {
|
ManaCost leftManaCost = card.getLeftSplitState().getManaCost();
|
||||||
ManaCost leftManaCost = card.getLeftSplitState().getManaCost();
|
ManaCost rightManaCost = card.getRightSplitState().getManaCost();
|
||||||
ManaCost rightManaCost = card.getRightSplitState().getManaCost();
|
drawManaCost(g, leftManaCost, x - padding, y-(manaSymbolSize/1.5f), w + 2 * padding, h, manaSymbolSize);
|
||||||
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);
|
||||||
drawManaCost(g, rightManaCost, x - padding, y+(manaSymbolSize/1.5f), w + 2 * padding, h, manaSymbolSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
drawManaCost(g, showAltState ? card.getAlternateState().getManaCost() : card.getCurrentState().getManaCost(), x - padding, y, w + 2 * padding, h, manaSymbolSize);
|
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.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
|
import forge.game.card.CardView.CardStateView;
|
||||||
import forge.game.card.token.TokenInfo;
|
import forge.game.card.token.TokenInfo;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
@@ -1829,6 +1830,11 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
return StaticData.instance().getCommonCards().getFaceByName(cardFaceView.getOracleName());
|
return StaticData.instance().getCommonCards().getFaceByName(cardFaceView.getOracleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICardFace chooseSingleCardFace(SpellAbility sa, List<ICardFace> faces, String message) {
|
||||||
|
return getGui().one(message, faces);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CounterType chooseCounterType(final List<CounterType> options, final SpellAbility sa, final String prompt,
|
public CounterType chooseCounterType(final List<CounterType> options, final SpellAbility sa, final String prompt,
|
||||||
Map<String, Object> params) {
|
Map<String, Object> params) {
|
||||||
@@ -1838,6 +1844,16 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
return getGui().one(prompt, options);
|
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
|
@Override
|
||||||
public String chooseKeywordForPump(final List<String> options, final SpellAbility sa, final String prompt, final Card tgtCard) {
|
public String chooseKeywordForPump(final List<String> options, final SpellAbility sa, final String prompt, final Card tgtCard) {
|
||||||
if (options.size() <= 1) {
|
if (options.size() <= 1) {
|
||||||
@@ -3216,7 +3232,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String chooseCardName(SpellAbility sa, List<ICardFace> faces, String message) {
|
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();
|
return face == null ? "" : face.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user