mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
I think I see why nobody wanted to do this
This commit is contained in:
@@ -158,6 +158,16 @@ public class GameAction {
|
||||
}
|
||||
}
|
||||
|
||||
//717.6. If a card with an Astrotorium card back would be put into a zone other than the battlefield, exile,
|
||||
//or the command zone from anywhere, instead its owner puts it into the junkyard.
|
||||
if (c.isAttractionCard() && !toBattlefield && !zoneTo.is(ZoneType.AttractionDeck)
|
||||
&& !zoneTo.is(ZoneType.Junkyard) && !zoneTo.is(ZoneType.Exile) && !zoneTo.is(ZoneType.Command)) {
|
||||
//This should technically be a replacement effect, but with the "can apply more than once to the same event"
|
||||
//clause, this seems sufficient for now.
|
||||
//TODO: Figure out what on earth happens if you animate an attraction, mutate a creature/commander/token onto it, and it dies...
|
||||
return moveToJunkyard(c, cause, params);
|
||||
}
|
||||
|
||||
boolean suppress = !c.isToken() && zoneFrom.equals(zoneTo);
|
||||
|
||||
Card copied = null;
|
||||
@@ -449,7 +459,8 @@ public class GameAction {
|
||||
}
|
||||
game.getCombat().removeFromCombat(c);
|
||||
}
|
||||
if ((zoneFrom.is(ZoneType.Library) || zoneFrom.is(ZoneType.PlanarDeck) || zoneFrom.is(ZoneType.SchemeDeck))
|
||||
if ((zoneFrom.is(ZoneType.Library) || zoneFrom.is(ZoneType.PlanarDeck)
|
||||
|| zoneFrom.is(ZoneType.SchemeDeck) || zoneFrom.is(ZoneType.AttractionDeck))
|
||||
&& zoneFrom == zoneTo && position.equals(zoneFrom.size()) && position != 0) {
|
||||
position--;
|
||||
}
|
||||
@@ -509,7 +520,7 @@ public class GameAction {
|
||||
if (card.isRealCommander()) {
|
||||
card.setMoveToCommandZone(true);
|
||||
}
|
||||
// 723.3e & 903.9a
|
||||
// 727.3e & 903.9a
|
||||
if (wasToken && !card.isRealToken() || card.isRealCommander()) {
|
||||
Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(card);
|
||||
repParams.put(AbilityKey.CardLKI, card);
|
||||
@@ -756,6 +767,8 @@ public class GameAction {
|
||||
case Stack: return moveToStack(c, cause, params);
|
||||
case PlanarDeck: return moveToVariantDeck(c, ZoneType.PlanarDeck, libPosition, cause, params);
|
||||
case SchemeDeck: return moveToVariantDeck(c, ZoneType.SchemeDeck, libPosition, cause, params);
|
||||
case AttractionDeck: return moveToVariantDeck(c, ZoneType.AttractionDeck, libPosition, cause, params);
|
||||
case Junkyard: return moveToJunkyard(c, cause, params);
|
||||
default: // sideboard will also get there
|
||||
return moveTo(c.getOwner().getZone(name), c, cause);
|
||||
}
|
||||
@@ -901,6 +914,11 @@ public class GameAction {
|
||||
}
|
||||
return changeZone(game.getZoneOf(c), deck, c, deckPosition, cause, params);
|
||||
}
|
||||
|
||||
public final Card moveToJunkyard(Card c, SpellAbility cause, Map<AbilityKey, Object> params) {
|
||||
final PlayerZone junkyard = c.getOwner().getZone(ZoneType.Junkyard);
|
||||
return moveTo(junkyard, c, cause, params);
|
||||
}
|
||||
|
||||
public final CardCollection exile(final CardCollection cards, SpellAbility cause, Map<AbilityKey, Object> params) {
|
||||
CardCollection result = new CardCollection();
|
||||
|
||||
@@ -124,6 +124,7 @@ public enum ApiType {
|
||||
Mutate (MutateEffect.class),
|
||||
NameCard (ChooseCardNameEffect.class),
|
||||
NoteCounters (CountersNoteEffect.class),
|
||||
OpenAttraction (OpenAttractionEffect.class),
|
||||
PeekAndReveal (PeekAndRevealEffect.class),
|
||||
PermanentCreature (PermanentCreatureEffect.class),
|
||||
PermanentNoncreature (PermanentNoncreatureEffect.class),
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardZoneTable;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.PlayerZone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class OpenAttractionEffect extends SpellAbilityEffect {
|
||||
@Override
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
|
||||
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa) : 1;
|
||||
|
||||
if(tgtPlayers.isEmpty())
|
||||
return "";
|
||||
|
||||
sb.append(Lang.joinHomogenous(tgtPlayers));
|
||||
|
||||
if (tgtPlayers.size() > 1) {
|
||||
sb.append(" each");
|
||||
}
|
||||
sb.append(Lang.joinVerb(tgtPlayers, " open")).append(" ");
|
||||
sb.append(amount == 1 ? "an Attraction." : (Lang.getNumeral(amount) + " Attractions."));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Card source = sa.getHostCard();
|
||||
final List<Player> tgtPlayers = getDefinedPlayersOrTargeted(sa);
|
||||
int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa) : 1;
|
||||
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
final CardZoneTable triggerList = AbilityKey.addCardZoneTableParams(moveParams, sa);
|
||||
|
||||
for (Player p : tgtPlayers) {
|
||||
if (!p.isInGame())
|
||||
continue;
|
||||
final PlayerZone attractionDeck = p.getZone(ZoneType.AttractionDeck);
|
||||
for (int i = 0; i < amount; i++) {
|
||||
if(attractionDeck.isEmpty())
|
||||
continue;
|
||||
Card attraction = attractionDeck.get(0);
|
||||
attraction = p.getGame().getAction().moveToPlay(attraction, sa, moveParams);
|
||||
if (sa.hasParam("Remember")) {
|
||||
source.addRemembered(attraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
triggerList.triggerChangesZoneAll(sa.getHostCard().getGame(), sa);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import forge.game.player.PlayerCollection;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.util.Lang;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.MyRandom;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -44,6 +45,13 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
protected String getStackDescription(SpellAbility sa) {
|
||||
final PlayerCollection player = getTargetPlayers(sa);
|
||||
|
||||
if(sa.hasParam("ToVisitYourAttractions")) {
|
||||
if (player.size() == 1 && player.get(0).equals(sa.getActivatingPlayer()))
|
||||
return "Roll to Visit Your Attractions.";
|
||||
else
|
||||
return String.format("%s %s to visit their Attractions.", Lang.joinHomogenous(player), Lang.joinVerb(player, "roll"));
|
||||
}
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (player.size() == 1 && player.get(0).equals(sa.getActivatingPlayer())) {
|
||||
stringBuilder.append("Roll ");
|
||||
@@ -121,8 +129,9 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
//Notify of results
|
||||
if (amount > 0) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(Localizer.getInstance().getMessage("lblPlayerRolledResult", player,
|
||||
StringUtils.join(naturalRolls, ", ")));
|
||||
String rollResults = StringUtils.join(naturalRolls, ", ");
|
||||
String resultMessage = sa.hasParam("ToVisitYourAttractions") ? "lblAttractionRollResult" : "lblPlayerRolledResult";
|
||||
sb.append(Localizer.getInstance().getMessage(resultMessage, player, rollResults));
|
||||
if (!ignored.isEmpty()) {
|
||||
sb.append("\r\n").append(Localizer.getInstance().getMessage("lblIgnoredRolls",
|
||||
StringUtils.join(ignored, ", ")));
|
||||
@@ -278,6 +287,9 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
} else {
|
||||
int result = rollDice(sa, player, amount, sides);
|
||||
results.add(result);
|
||||
if (sa.hasParam("ToVisitYourAttractions")) {
|
||||
player.visitAttractions(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rememberHighest) {
|
||||
|
||||
@@ -141,12 +141,16 @@ public class SubgameEffect extends SpellAbilityEffect {
|
||||
// Planes
|
||||
setCardsInZone(player, ZoneType.PlanarDeck, maingamePlayer.getCardsIn(ZoneType.PlanarDeck), false);
|
||||
|
||||
// Attractions
|
||||
setCardsInZone(player, ZoneType.AttractionDeck, maingamePlayer.getCardsIn(ZoneType.AttractionDeck), false);
|
||||
|
||||
// Vanguard and Commanders
|
||||
initVariantsZonesSubgame(subgame, maingamePlayer, player);
|
||||
|
||||
player.shuffle(null);
|
||||
player.getZone(ZoneType.SchemeDeck).shuffle();
|
||||
player.getZone(ZoneType.PlanarDeck).shuffle();
|
||||
player.getZone(ZoneType.AttractionDeck).shuffle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +253,7 @@ public class SubgameEffect extends SpellAbilityEffect {
|
||||
player.shuffle(sa);
|
||||
player.getZone(ZoneType.SchemeDeck).shuffle();
|
||||
player.getZone(ZoneType.PlanarDeck).shuffle();
|
||||
player.getZone(ZoneType.AttractionDeck).shuffle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -262,6 +262,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
private boolean isImmutable = false;
|
||||
private boolean isEmblem = false;
|
||||
private boolean isBoon = false;
|
||||
private boolean isAttractionCard = false;
|
||||
|
||||
private int exertThisTurn = 0;
|
||||
private PlayerCollection exertedByPlayer = new PlayerCollection();
|
||||
@@ -4229,6 +4230,14 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
currentState.setBaseDefense(Integer.toString(n));
|
||||
}
|
||||
|
||||
public final Set<Integer> getAttractionLights() {
|
||||
return currentState.getAttractionLights();
|
||||
}
|
||||
public final void setAttractionLights(Set<Integer> attractionLights) {
|
||||
currentState.setAttractionLights(attractionLights);
|
||||
}
|
||||
|
||||
|
||||
public final int getBasePower() {
|
||||
return currentState.getBasePower();
|
||||
}
|
||||
@@ -5461,6 +5470,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|
||||
public final boolean isEquipment() { return getType().isEquipment(); }
|
||||
public final boolean isFortification() { return getType().isFortification(); }
|
||||
public final boolean isAttraction() { return getType().isAttraction(); }
|
||||
public final boolean isCurse() { return getType().hasSubtype("Curse"); }
|
||||
public final boolean isAura() { return getType().isAura(); }
|
||||
public final boolean isShrine() { return getType().hasSubtype("Shrine"); }
|
||||
@@ -5837,6 +5847,16 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
view.updateBoon(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this is physically an Attraction card with an Astrotorium card back. False otherwise.
|
||||
*/
|
||||
public final boolean isAttractionCard() {
|
||||
return this.isAttractionCard;
|
||||
}
|
||||
public final void setAttractionCard(boolean isAttractionCard) {
|
||||
this.isAttractionCard = isAttractionCard;
|
||||
}
|
||||
|
||||
/*
|
||||
* there are easy checkers for Color. The CardUtil functions should be made
|
||||
* part of the Card class, so calling out is not necessary
|
||||
|
||||
@@ -44,9 +44,7 @@ import forge.item.IPaperCard;
|
||||
import forge.util.CardTranslation;
|
||||
import forge.util.TextUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
@@ -190,6 +188,8 @@ public class CardFactory {
|
||||
c.setImageKey(originalPicture);
|
||||
c.setToken(cp.isToken());
|
||||
|
||||
c.setAttractionCard(cardRules.getType().isAttraction());
|
||||
|
||||
if (c.hasAlternateState()) {
|
||||
if (c.isFlipCard()) {
|
||||
c.setState(CardStateName.Flipped, false);
|
||||
@@ -393,6 +393,8 @@ public class CardFactory {
|
||||
c.setBaseToughnessString(face.getToughness());
|
||||
}
|
||||
|
||||
c.setAttractionLights(face.getAttractionLights());
|
||||
|
||||
// SpellPermanent only for Original State
|
||||
if (c.getCurrentStateName() == CardStateName.Original || c.getCurrentStateName() == CardStateName.Modal || c.getCurrentStateName().toString().startsWith("Specialize")) {
|
||||
// this is the "default" spell for permanents like creatures and artifacts
|
||||
@@ -409,6 +411,73 @@ public class CardFactory {
|
||||
}
|
||||
|
||||
CardFactoryUtil.addAbilityFactoryAbilities(c, face.getAbilities());
|
||||
|
||||
if (face.hasFunctionalVariants()) {
|
||||
applyFunctionalVariant(c, face);
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyFunctionalVariant(Card c, ICardFace originalFace) {
|
||||
String variantName = c.getPaperCard().getFunctionalVariant();
|
||||
if (IPaperCard.NO_FUNCTIONAL_VARIANT.equals(variantName))
|
||||
return;
|
||||
ICardFace variant = originalFace.getFunctionalVariant(variantName);
|
||||
if (variant == null) {
|
||||
System.out.printf("Tried to apply unknown or unsupported variant - Card: \"%s\"; Variant: %s\n", originalFace.getName(), variantName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (variant.getVariables() != null)
|
||||
for (Entry<String, String> v : variant.getVariables())
|
||||
c.setSVar(v.getKey(), v.getValue());
|
||||
if (variant.getReplacements() != null)
|
||||
for (String r : variant.getReplacements())
|
||||
c.addReplacementEffect(ReplacementHandler.parseReplacement(r, c, true, c.getCurrentState()));
|
||||
if (variant.getStaticAbilities() != null)
|
||||
for (String s : variant.getStaticAbilities())
|
||||
c.addStaticAbility(s);
|
||||
if (variant.getTriggers() != null)
|
||||
for (String t : variant.getTriggers())
|
||||
c.addTrigger(TriggerHandler.parseTrigger(t, c, true, c.getCurrentState()));
|
||||
|
||||
if (variant.getKeywords() != null)
|
||||
c.addIntrinsicKeywords(variant.getKeywords(), false);
|
||||
|
||||
if (variant.getManaCost() != ManaCost.NO_COST)
|
||||
c.setManaCost(variant.getManaCost());
|
||||
if (variant.getNonAbilityText() != null)
|
||||
c.setText(variant.getNonAbilityText());
|
||||
|
||||
if (!"".equals(variant.getInitialLoyalty()))
|
||||
c.getCurrentState().setBaseLoyalty(variant.getInitialLoyalty());
|
||||
if (!"".equals(variant.getDefense()))
|
||||
c.getCurrentState().setBaseDefense(variant.getDefense());
|
||||
|
||||
if (variant.getOracleText() != null)
|
||||
c.setOracleText(variant.getOracleText());
|
||||
|
||||
if (variant.getType() != null) {
|
||||
for(String type : variant.getType())
|
||||
c.addType(type);
|
||||
}
|
||||
|
||||
if (variant.getColor() != null)
|
||||
c.setColor(variant.getColor().getColor());
|
||||
|
||||
if (variant.getIntPower() != Integer.MAX_VALUE) {
|
||||
c.setBasePower(variant.getIntPower());
|
||||
c.setBasePowerString(variant.getPower());
|
||||
}
|
||||
if (variant.getIntToughness() != Integer.MAX_VALUE) {
|
||||
c.setBaseToughness(variant.getIntToughness());
|
||||
c.setBaseToughnessString(variant.getToughness());
|
||||
}
|
||||
|
||||
if (variant.getAttractionLights() != null)
|
||||
c.setAttractionLights(variant.getAttractionLights());
|
||||
|
||||
if (variant.getAbilities() != null)
|
||||
CardFactoryUtil.addAbilityFactoryAbilities(c, variant.getAbilities());
|
||||
}
|
||||
|
||||
public static void copySpellAbility(SpellAbility from, SpellAbility to, final Card host, final Player p, final boolean lki, final boolean keepTextChanges) {
|
||||
|
||||
@@ -1964,6 +1964,20 @@ public class CardFactoryUtil {
|
||||
|
||||
inst.addTrigger(parsedUpkeepTrig);
|
||||
inst.addTrigger(parsedSacTrigger);
|
||||
} else if (keyword.startsWith("Visit")) {
|
||||
final String[] k = keyword.split(":");
|
||||
//final String dbVar = card.getSVar(k[1]);
|
||||
|
||||
SpellAbility sa = AbilityFactory.getAbility(card, k[1]);
|
||||
String descStr = "Visit — " + sa.getDescription();
|
||||
|
||||
final String trigStr = "Mode$ VisitAttraction | TriggerZones$ Battlefield | ValidCard$ Card.Self" +
|
||||
"| TriggerDescription$ " + descStr;
|
||||
|
||||
final Trigger t = TriggerHandler.parseTrigger(trigStr, card, intrinsic);
|
||||
t.setOverridingAbility(sa);
|
||||
inst.addTrigger(t);
|
||||
|
||||
} else if (keyword.startsWith("Dungeon")) {
|
||||
final List<String> abs = Arrays.asList(keyword.substring("Dungeon:".length()).split(","));
|
||||
final Map<String, SpellAbility> saMap = new LinkedHashMap<>();
|
||||
|
||||
@@ -532,6 +532,10 @@ public final class CardPredicates {
|
||||
};
|
||||
}
|
||||
|
||||
public static Predicate<Card> isAttractionWithLight(int light) {
|
||||
return c -> c.isAttraction() && c.getAttractionLights().contains(light);
|
||||
}
|
||||
|
||||
public static class Presets {
|
||||
|
||||
/**
|
||||
@@ -768,6 +772,7 @@ public final class CardPredicates {
|
||||
return c.canBeDestroyed();
|
||||
}
|
||||
};
|
||||
public static final Predicate<Card> ATTRACTIONS = Card::isAttraction;
|
||||
}
|
||||
|
||||
public static class Accessors {
|
||||
|
||||
@@ -20,6 +20,7 @@ package forge.game.card;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -64,6 +65,7 @@ public class CardState extends GameObject implements IHasSVars {
|
||||
private String baseLoyalty = "";
|
||||
private String baseDefense = "";
|
||||
private KeywordCollection intrinsicKeywords = new KeywordCollection();
|
||||
private Set<Integer> attractionLights = null;
|
||||
|
||||
private final FCollection<SpellAbility> nonManaAbilities = new FCollection<>();
|
||||
private final FCollection<SpellAbility> manaAbilities = new FCollection<>();
|
||||
@@ -240,6 +242,15 @@ public class CardState extends GameObject implements IHasSVars {
|
||||
view.updateDefense(this);
|
||||
}
|
||||
|
||||
public Set<Integer> getAttractionLights() {
|
||||
return this.attractionLights;
|
||||
}
|
||||
|
||||
public final void setAttractionLights(Set<Integer> attractionLights) {
|
||||
this.attractionLights = attractionLights;
|
||||
view.updateAttractionLights(this);
|
||||
}
|
||||
|
||||
public final Collection<KeywordInterface> getCachedKeywords() {
|
||||
return cachedKeywords.getValues();
|
||||
}
|
||||
@@ -588,6 +599,7 @@ public class CardState extends GameObject implements IHasSVars {
|
||||
setBaseToughness(source.getBaseToughness());
|
||||
setBaseLoyalty(source.getBaseLoyalty());
|
||||
setBaseDefense(source.getBaseDefense());
|
||||
setAttractionLights(source.getAttractionLights());
|
||||
setSVars(source.getSVars());
|
||||
|
||||
manaAbilities.clear();
|
||||
|
||||
@@ -570,6 +570,7 @@ public class CardView extends GameEntityView {
|
||||
case Graveyard:
|
||||
case Flashback:
|
||||
case Stack:
|
||||
case Junkyard:
|
||||
//cards in these zones are visible to all
|
||||
return true;
|
||||
case Exile:
|
||||
@@ -592,6 +593,7 @@ public class CardView extends GameEntityView {
|
||||
return true;
|
||||
case Library:
|
||||
case PlanarDeck:
|
||||
case AttractionDeck:
|
||||
//cards in these zones are hidden to all unless they specify otherwise
|
||||
break;
|
||||
case SchemeDeck:
|
||||
@@ -795,6 +797,12 @@ public class CardView extends GameEntityView {
|
||||
sb.append(nonAbilityText.replaceAll("CARDNAME", getName()));
|
||||
}
|
||||
|
||||
Set<Integer> attractionLights = get(TrackableProperty.AttractionLights);
|
||||
if (attractionLights != null && !attractionLights.isEmpty()) {
|
||||
sb.append("\r\n\r\nLights: ");
|
||||
sb.append(StringUtils.join(attractionLights, ", "));
|
||||
}
|
||||
|
||||
sb.append(getRemembered());
|
||||
|
||||
Direction chosenDirection = getChosenDirection();
|
||||
@@ -1010,6 +1018,8 @@ public class CardView extends GameEntityView {
|
||||
currentState.getView().updateKeywords(c, currentState); //update keywords even if state doesn't change
|
||||
currentState.getView().setOriginalColors(c); //set original Colors
|
||||
|
||||
currentStateView.updateAttractionLights(currentState);
|
||||
|
||||
CardState alternateState = isSplitCard && isFaceDown() ? c.getState(CardStateName.RightSplit) : c.getAlternateState();
|
||||
|
||||
if (isSplitCard && isFaceDown()) {
|
||||
@@ -1419,6 +1429,13 @@ public class CardView extends GameEntityView {
|
||||
updateDefense("0");
|
||||
}
|
||||
|
||||
public Set<Integer> getAttractionLights() {
|
||||
return get(TrackableProperty.AttractionLights);
|
||||
}
|
||||
void updateAttractionLights(CardState c) {
|
||||
set(TrackableProperty.AttractionLights, c.getAttractionLights());
|
||||
}
|
||||
|
||||
public String getSetCode() {
|
||||
return get(TrackableProperty.SetCode);
|
||||
}
|
||||
@@ -1697,6 +1714,9 @@ public class CardView extends GameEntityView {
|
||||
return false;
|
||||
return Iterables.size(getType().getCoreTypes()) > 1;
|
||||
}
|
||||
public boolean isAttraction() {
|
||||
return getType().isAttraction();
|
||||
}
|
||||
}
|
||||
|
||||
//special methods for updating card and player properties as needed and returning the new collection
|
||||
|
||||
@@ -281,12 +281,16 @@ public class PhaseHandler implements java.io.Serializable {
|
||||
playerTurn.setSchemeInMotion(null);
|
||||
}
|
||||
GameEntityCounterTable table = new GameEntityCounterTable();
|
||||
// all Saga get Lore counter at the begin of pre combat
|
||||
// all Sagas get a Lore counter at the beginning of pre combat
|
||||
for (Card c : playerTurn.getCardsIn(ZoneType.Battlefield)) {
|
||||
if (c.isSaga()) {
|
||||
c.addCounter(CounterEnumType.LORE, 1, playerTurn, table);
|
||||
}
|
||||
}
|
||||
// roll for attractions if we have any
|
||||
if (CardLists.count(playerTurn.getCardsIn(ZoneType.Battlefield), Presets.ATTRACTIONS) > 0) {
|
||||
playerTurn.rollToVisitAttractions();
|
||||
}
|
||||
table.replaceCounterEffect(game, null, false);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -61,6 +61,7 @@ import forge.item.PaperCard;
|
||||
import forge.util.*;
|
||||
import forge.util.collect.FCollection;
|
||||
import forge.util.collect.FCollectionView;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
@@ -78,7 +79,8 @@ import java.util.Map.Entry;
|
||||
public class Player extends GameEntity implements Comparable<Player> {
|
||||
public static final List<ZoneType> ALL_ZONES = Collections.unmodifiableList(Arrays.asList(ZoneType.Battlefield,
|
||||
ZoneType.Library, ZoneType.Graveyard, ZoneType.Hand, ZoneType.Exile, ZoneType.Command, ZoneType.Ante,
|
||||
ZoneType.Sideboard, ZoneType.PlanarDeck, ZoneType.SchemeDeck, ZoneType.Merged, ZoneType.Subgame, ZoneType.None));
|
||||
ZoneType.Sideboard, ZoneType.PlanarDeck, ZoneType.SchemeDeck, ZoneType.AttractionDeck, ZoneType.Junkyard,
|
||||
ZoneType.Merged, ZoneType.Subgame, ZoneType.None));
|
||||
|
||||
private final Map<Card, Integer> commanderDamage = Maps.newHashMap();
|
||||
|
||||
@@ -162,6 +164,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private CardCollection currentPlanes = new CardCollection();
|
||||
private CardCollection planeswalkedToThisTurn = new CardCollection();
|
||||
|
||||
private int attractionsVisitedThisTurn = 0; //TODO: Is "number of attractions you visited this turn" supposed to mean unique ones or just total visits?
|
||||
|
||||
private PlayerStatistics stats = new PlayerStatistics();
|
||||
private PlayerController controller;
|
||||
|
||||
@@ -1947,6 +1951,12 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
public final List<Card> getPlaneswalkedToThisTurn() {
|
||||
return planeswalkedToThisTurn;
|
||||
}
|
||||
public final void incrementAttractionsVisitedThisTurn() {
|
||||
this.attractionsVisitedThisTurn++;
|
||||
}
|
||||
public final int getAttractionsVisitedThisTurn() {
|
||||
return attractionsVisitedThisTurn;
|
||||
}
|
||||
|
||||
public final void altWinBySpellEffect(final String sourceName) {
|
||||
if (cantWin()) {
|
||||
@@ -2517,6 +2527,8 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
damageReceivedThisTurn.clear();
|
||||
planeswalkedToThisTurn.clear();
|
||||
|
||||
attractionsVisitedThisTurn = 0;
|
||||
|
||||
// set last turn nr
|
||||
if (game.getPhaseHandler().isPlayerTurn(this)) {
|
||||
setBeenDealtCombatDamageSinceLastTurn(false);
|
||||
@@ -2952,6 +2964,14 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
com.add(conspire);
|
||||
}
|
||||
|
||||
// Attractions
|
||||
PlayerZone attractionDeck = getZone(ZoneType.AttractionDeck);
|
||||
for (IPaperCard cp : registeredPlayer.getAttractions()) {
|
||||
attractionDeck.add(Card.fromPaperCard(cp, this));
|
||||
}
|
||||
if (!attractionDeck.isEmpty())
|
||||
attractionDeck.shuffle();
|
||||
|
||||
// Adventure Mode items
|
||||
Iterable<? extends IPaperCard> adventureItemCards = registeredPlayer.getExtraCardsInCommandZone();
|
||||
if (adventureItemCards != null) {
|
||||
@@ -3802,4 +3822,81 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
public void setCommitedCrimeThisTurn(int v) {
|
||||
committedCrimeThisTurn = v;
|
||||
}
|
||||
|
||||
public void visitAttractions(int light) {
|
||||
CardCollection attractions = CardLists.filter(getCardsIn(ZoneType.Battlefield), CardPredicates.isAttractionWithLight(light));
|
||||
if(attractions.isEmpty())
|
||||
return;
|
||||
for (Card c : attractions) {
|
||||
incrementAttractionsVisitedThisTurn();
|
||||
|
||||
final Map<AbilityKey, Object> runParams = AbilityKey.newMap();
|
||||
runParams.put(AbilityKey.Card, c);
|
||||
runParams.put(AbilityKey.Player, this);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.VisitAttraction, runParams, false);
|
||||
}
|
||||
}
|
||||
public void rollToVisitAttractions() {
|
||||
//Essentially a retread of RollDiceEffect.rollDiceForPlayer, but without the parts that require a spell ability.
|
||||
int amount = 1, sides = 6, ignore = 0;
|
||||
Map<Player, Integer> ignoreChosenMap = Maps.newHashMap();
|
||||
|
||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(this);
|
||||
repParams.put(AbilityKey.Number, amount);
|
||||
repParams.put(AbilityKey.Ignore, ignore);
|
||||
repParams.put(AbilityKey.IgnoreChosen, ignoreChosenMap);
|
||||
|
||||
if(getGame().getReplacementHandler().run(ReplacementType.RollDice, repParams) == ReplacementResult.Updated) {
|
||||
amount = (int) repParams.get(AbilityKey.Number);
|
||||
ignore = (int) repParams.get(AbilityKey.Ignore);
|
||||
//noinspection unchecked
|
||||
ignoreChosenMap = (Map<Player, Integer>) repParams.get(AbilityKey.IgnoreChosen);
|
||||
}
|
||||
if (amount == 0)
|
||||
return;
|
||||
int total = 0;
|
||||
List<Integer> naturalRolls = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < amount; i++) {
|
||||
int roll = MyRandom.getRandom().nextInt(sides) + 1;
|
||||
// Play the die roll sound
|
||||
getGame().fireEvent(new GameEventRollDie());
|
||||
roll();
|
||||
naturalRolls.add(roll);
|
||||
total += roll;
|
||||
}
|
||||
|
||||
naturalRolls.sort(null);
|
||||
|
||||
List<Integer> ignored = new ArrayList<>();
|
||||
// Ignore the lowest rolls
|
||||
if (ignore > 0) {
|
||||
for (int i = ignore - 1; i >= 0; --i) {
|
||||
total -= naturalRolls.get(i);
|
||||
ignored.add(naturalRolls.get(i));
|
||||
naturalRolls.remove(i);
|
||||
}
|
||||
}
|
||||
// Player chooses to ignore rolls
|
||||
for (Player chooser : ignoreChosenMap.keySet()) {
|
||||
for (int ig = 0; ig < ignoreChosenMap.get(chooser); ig++) {
|
||||
Integer ign = chooser.getController().chooseRollToIgnore(naturalRolls);
|
||||
total -= ign;
|
||||
ignored.add(ign);
|
||||
naturalRolls.remove(ign);
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String rollResults = StringUtils.join(naturalRolls, ", ");
|
||||
String resultMessage = "lblAttractionRollResult";
|
||||
sb.append(Localizer.getInstance().getMessage(resultMessage, this, rollResults));
|
||||
if (!ignored.isEmpty()) {
|
||||
sb.append("\r\n").append(Localizer.getInstance().getMessage("lblIgnoredRolls",
|
||||
StringUtils.join(ignored, ", ")));
|
||||
}
|
||||
getGame().getAction().notifyOfValue(null, this, sb.toString(), null);
|
||||
|
||||
this.visitAttractions(total);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ public class RegisteredPlayer {
|
||||
private Iterable<? extends IPaperCard> schemes = null;
|
||||
private Iterable<PaperCard> planes = null;
|
||||
private Iterable<PaperCard> conspiracies = null;
|
||||
private Iterable<PaperCard> attractions = null;
|
||||
private List<PaperCard> commanders = Lists.newArrayList();
|
||||
private List<PaperCard> vanguardAvatars = null;
|
||||
private PaperCard planeswalker = null;
|
||||
@@ -223,8 +224,18 @@ public class RegisteredPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
public Iterable<PaperCard> getAttractions() {
|
||||
return attractions;
|
||||
}
|
||||
private void assignAttractions() {
|
||||
attractions = currentDeck.has(DeckSection.Attractions)
|
||||
? currentDeck.get(DeckSection.Attractions).toFlatList()
|
||||
: EmptyList;
|
||||
}
|
||||
|
||||
public void restoreDeck() {
|
||||
currentDeck = (Deck) originalDeck.copyTo(originalDeck.getName());
|
||||
assignAttractions();
|
||||
}
|
||||
|
||||
public boolean useRandomFoil() {
|
||||
|
||||
@@ -138,6 +138,7 @@ public enum TriggerType {
|
||||
Unattach(TriggerUnattach.class),
|
||||
UntapAll(TriggerUntapAll.class),
|
||||
Untaps(TriggerUntaps.class),
|
||||
VisitAttraction(TriggerVisitAttraction.class),
|
||||
Vote(TriggerVote.class);
|
||||
|
||||
private final Constructor<? extends Trigger> constructor;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package forge.game.trigger;
|
||||
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Localizer;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class TriggerVisitAttraction extends Trigger {
|
||||
|
||||
public TriggerVisitAttraction(Map<String, String> params, Card host, boolean intrinsic) {
|
||||
super(params, host, intrinsic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performTest(Map<AbilityKey, Object> runParams) {
|
||||
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Player))) {
|
||||
return false;
|
||||
}
|
||||
if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Card))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTriggeringObjects(SpellAbility sa, Map<AbilityKey, Object> runParams) {
|
||||
//TODO: Attraction roll value? Person who caused the attraction roll?
|
||||
sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player, AbilityKey.Card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImportantStackObjects(SpellAbility sa) {
|
||||
//TODO: Do I even need this much? Someone would need to implement a card to visit someone else's attraction...
|
||||
return Localizer.getInstance().getMessage("lblPlayer") + ": " +
|
||||
sa.getTriggeringObject(AbilityKey.Player);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ public enum ZoneType {
|
||||
Merged(false, "lblBattlefieldZone"),
|
||||
SchemeDeck(true, "lblSchemeDeckZone"),
|
||||
PlanarDeck(true, "lblPlanarDeckZone"),
|
||||
AttractionDeck(true, "lblAttractionDeckZone"),
|
||||
Junkyard(false, "lblJunkyardZone"),
|
||||
Subgame(true, "lblSubgameZone"),
|
||||
None(true, "lblNoneZone");
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ public enum TrackableProperty {
|
||||
Toughness(TrackableTypes.IntegerType),
|
||||
Loyalty(TrackableTypes.StringType),
|
||||
Defense(TrackableTypes.StringType),
|
||||
AttractionLights(TrackableTypes.IntegerSetType),
|
||||
ChangedColorWords(TrackableTypes.StringMapType),
|
||||
HasChangedColors(TrackableTypes.BooleanType),
|
||||
ChangedTypes(TrackableTypes.StringMapType),
|
||||
|
||||
@@ -559,6 +559,34 @@ public class TrackableTypes {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static final TrackableType<Set<Integer>> IntegerSetType = new TrackableType<Set<Integer>>() {
|
||||
@Override
|
||||
public Set<Integer> getDefaultValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> deserialize(TrackableDeserializer td, Set<Integer> oldValue) {
|
||||
int size = td.readInt();
|
||||
if (size > 0) {
|
||||
Set<Integer> set = Sets.newHashSet();
|
||||
for (int i = 0; i < size; i++) {
|
||||
set.add(td.readInt());
|
||||
}
|
||||
return set;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(TrackableSerializer ts, Set<Integer> value) {
|
||||
ts.write(value.size());
|
||||
for (int i : value) {
|
||||
ts.write(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
public static final TrackableType<Map<Integer, Integer>> IntegerMapType = new TrackableType<Map<Integer, Integer>>() {
|
||||
@Override
|
||||
public Map<Integer, Integer> getDefaultValue() {
|
||||
|
||||
Reference in New Issue
Block a user