I think I see why nobody wanted to do this

This commit is contained in:
Jetz
2024-06-02 02:08:36 -04:00
parent 410d720b37
commit 174db0cac6
52 changed files with 787 additions and 79 deletions

View File

@@ -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();

View File

@@ -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),

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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<>();

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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");

View File

@@ -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),

View File

@@ -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() {