Merge pull request #2959 from Northmoc/ichor

MOC: Ichor Elixir + support
This commit is contained in:
Anthony Calosa
2023-04-21 11:05:08 +08:00
committed by GitHub
29 changed files with 299 additions and 121 deletions

View File

@@ -1,20 +1,11 @@
package forge.ai; package forge.ai;
import java.security.InvalidParameterException;
import java.util.*;
import forge.game.keyword.Keyword;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap; import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import forge.LobbyPlayer; import forge.LobbyPlayer;
import forge.ai.ability.ProtectAi; import forge.ai.ability.ProtectAi;
import forge.card.CardStateName; import forge.card.CardStateName;
@@ -25,48 +16,26 @@ import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
import forge.deck.Deck; import forge.deck.Deck;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.game.Game; import forge.game.*;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GameType;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect; import forge.game.ability.effects.CharmEffect;
import forge.game.card.Card; import forge.game.card.*;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil;
import forge.game.card.CardView;
import forge.game.card.CounterType;
import forge.game.combat.Combat; import forge.game.combat.Combat;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.cost.CostEnlist; import forge.game.cost.CostEnlist;
import forge.game.cost.CostPart; import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana; import forge.game.cost.CostPartMana;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.mana.Mana; import forge.game.mana.Mana;
import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaConversionMatrix;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.DelayedReveal; import forge.game.player.*;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerController;
import forge.game.player.PlayerView;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.Ability; import forge.game.spellability.*;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.LandAbility;
import forge.game.spellability.OptionalCost;
import forge.game.spellability.OptionalCostValue;
import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices;
import forge.game.trigger.WrappedAbility; import forge.game.trigger.WrappedAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.item.PaperCard; import forge.item.PaperCard;
@@ -75,6 +44,12 @@ import forge.util.ITriggerEvent;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView; 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;
import java.security.InvalidParameterException;
import java.util.*;
/** /**
@@ -606,6 +581,12 @@ public class PlayerControllerAi extends PlayerController {
return Aggregates.random(sectors); return Aggregates.random(sectors);
} }
@Override
public PlanarDice choosePDRollToIgnore(List<PlanarDice> rolls) {
//TODO create AI logic for this
return Aggregates.random(rolls);
}
@Override @Override
public boolean mulliganKeepHand(Player firstPlayer, int cardsToReturn) { public boolean mulliganKeepHand(Player firstPlayer, int cardsToReturn) {
return !ComputerUtil.wantMulligan(player, cardsToReturn); return !ComputerUtil.wantMulligan(player, cardsToReturn);

View File

@@ -134,7 +134,7 @@ public class RollPlanarDiceAi extends SpellAbilityAi {
decideToRoll = false; decideToRoll = false;
} }
if (ai.getGame().getPhaseHandler().getPlanarDiceRolledthisTurn() >= maxActivations) { if (ai.getGame().getPhaseHandler().getPlanarDiceSpecialActionThisTurn() >= maxActivations) {
decideToRoll = false; decideToRoll = false;
} }

View File

@@ -212,6 +212,13 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView,
if (ArrayUtils.contains(valids, o)) { if (ArrayUtils.contains(valids, o)) {
return true; return true;
} }
} else if (o instanceof PlanarDice) {
for (String s : valids) {
PlanarDice valid = PlanarDice.smartValueOf(s);
if (((PlanarDice) o).name().equals(valid.name())) {
return true;
}
}
} }
return false; return false;

View File

@@ -28,8 +28,7 @@ public enum GlobalRuleChange {
noNight ("It can't become night."), noNight ("It can't become night."),
onlyOneBlocker ("No more than one creature can block each combat."), onlyOneBlocker ("No more than one creature can block each combat."),
onlyOneBlockerPerOpponent ("Each opponent can't block with more than one creature."), onlyOneBlockerPerOpponent ("Each opponent can't block with more than one creature."),
onlyTwoBlockers ("No more than two creatures can block each combat."), onlyTwoBlockers ("No more than two creatures can block each combat.");
blankIsChaos("Each blank roll of the planar dice is a {CHAOS} roll.");
private final String ruleText; private final String ruleText;

View File

@@ -1,15 +1,16 @@
package forge.game; package forge.game;
import java.util.Arrays;
import java.util.Map;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.event.GameEventRollDie;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementType;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/** /**
* Represents the planar dice for Planechase games. * Represents the planar dice for Planechase games.
* *
@@ -20,36 +21,68 @@ public enum PlanarDice {
Blank; Blank;
public static PlanarDice roll(Player roller, PlanarDice riggedResult) { public static PlanarDice roll(Player roller, PlanarDice riggedResult) {
PlanarDice res = Blank; final Game game = roller.getGame();
int rolls = 1;
int ignore = 0;
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(roller);
repParams.put(AbilityKey.Number, rolls);
repParams.put(AbilityKey.Ignore, ignore);
switch (game.getReplacementHandler().run(ReplacementType.RollPlanarDice, repParams)) {
case NotReplaced:
break;
case Updated: {
rolls = (int) repParams.get(AbilityKey.Number);
ignore = (int) repParams.get(AbilityKey.Ignore);
break;
}
}
List<PlanarDice> results = Lists.newArrayList();
for (int r = 0; r < rolls; r++) {
PlanarDice thisRoll = Blank;
int i = forge.util.MyRandom.getRandom().nextInt(6); int i = forge.util.MyRandom.getRandom().nextInt(6);
// Play the die roll sound
roller.getGame().fireEvent(new GameEventRollDie());
roller.roll(); roller.roll();
if (riggedResult != null) if (riggedResult != null)
res = riggedResult; thisRoll = riggedResult;
else if (i == 0) else if (i == 0)
res = Planeswalk; thisRoll = Planeswalk;
else if (i == 1) else if (i == 1)
res = Chaos; thisRoll = Chaos;
results.add(thisRoll);
}
for (int ig = 0; ig < ignore; ig++) {
results.remove(roller.getController().choosePDRollToIgnore(results));
}
PlanarDice res = results.get(0);
PlanarDice trigRes = res; PlanarDice trigRes = res;
if(roller.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.blankIsChaos) final Map<AbilityKey, Object> resRepParams = AbilityKey.mapFromAffected(roller);
&& res == Blank) resRepParams.put(AbilityKey.Result, res);
{
trigRes = Chaos; switch (game.getReplacementHandler().run(ReplacementType.PlanarDiceResult, resRepParams)) {
case NotReplaced:
break;
case Updated: {
trigRes = (PlanarDice) resRepParams.get(AbilityKey.Result);
break;
}
} }
Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(roller); Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(roller);
runParams.put(AbilityKey.Result, trigRes); runParams.put(AbilityKey.Result, trigRes);
roller.getGame().getTriggerHandler().runTrigger(TriggerType.PlanarDice, runParams,false); game.getTriggerHandler().runTrigger(TriggerType.PlanarDice, runParams,false);
// Also run normal RolledDie and RolledDieOnce triggers // Also run normal RolledDie and RolledDieOnce triggers
for (int r = 0; r < rolls; r++) {
runParams = AbilityKey.mapFromPlayer(roller); runParams = AbilityKey.mapFromPlayer(roller);
runParams.put(AbilityKey.Sides, 6); runParams.put(AbilityKey.Sides, 6);
runParams.put(AbilityKey.Result, 0); runParams.put(AbilityKey.Result, 0);
roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false); roller.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false);
}
runParams = AbilityKey.mapFromPlayer(roller); runParams = AbilityKey.mapFromPlayer(roller);
runParams.put(AbilityKey.Sides, 6); runParams.put(AbilityKey.Sides, 6);

View File

@@ -74,6 +74,7 @@ public enum AbilityKey {
FirstTime("FirstTime"), FirstTime("FirstTime"),
Fizzle("Fizzle"), Fizzle("Fizzle"),
FoundSearchingLibrary("FoundSearchingLibrary"), FoundSearchingLibrary("FoundSearchingLibrary"),
Ignore("Ignore"),
IsCombat("IsCombat"), // TODO confirm that this and IsCombatDamage can be merged IsCombat("IsCombat"), // TODO confirm that this and IsCombatDamage can be merged
IsCombatDamage("IsCombatDamage"), IsCombatDamage("IsCombatDamage"),
IsDamage("IsDamage"), IsDamage("IsDamage"),

View File

@@ -2690,8 +2690,8 @@ public class AbilityUtils {
return doXMath(game.getStack().getSpellsCastThisTurn().size() - 1, expr, c, ctb); return doXMath(game.getStack().getSpellsCastThisTurn().size() - 1, expr, c, ctb);
} }
if (sq[0].startsWith("RolledThisTurn")) { if (sq[0].startsWith("PlanarDiceSpecialActionThisTurn")) {
return game.getPhaseHandler().getPlanarDiceRolledthisTurn(); return game.getPhaseHandler().getPlanarDiceSpecialActionThisTurn();
} }
if (sq[0].contains("CardTypes")) { if (sq[0].contains("CardTypes")) {

View File

@@ -4,6 +4,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.PlanarDice;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
@@ -41,6 +42,8 @@ public class ReplaceEffect extends SpellAbilityEffect {
if (list.size() > 0) { if (list.size() > 0) {
params.put(varName, list.get(0)); params.put(varName, list.get(0));
} }
} else if ("PlanarDice".equals(type)) {
params.put(varName, PlanarDice.smartValueOf(varValue));
} else { } else {
params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa)); params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa));
} }

View File

@@ -19,17 +19,19 @@ public class RollPlanarDiceEffect extends SpellAbilityEffect {
*/ */
@Override @Override
public void resolve(SpellAbility sa) { public void resolve(SpellAbility sa) {
boolean countedTowardsCost = !sa.hasParam("NotCountedTowardsCost");
final Player activator = sa.getActivatingPlayer(); final Player activator = sa.getActivatingPlayer();
final Game game = activator.getGame(); final Game game = activator.getGame();
if(countedTowardsCost) { if (game.getActivePlanes() == null) { // not a planechase game, nothing happens
game.getPhaseHandler().incPlanarDiceRolledthisTurn(); return;
}
if (sa.hasParam("SpecialAction")) {
game.getPhaseHandler().incPlanarDiceSpecialActionThisTurn();
} }
PlanarDice result = PlanarDice.roll(activator, null);
// Play the die roll sound // Play the die roll sound
activator.getGame().fireEvent(new GameEventRollDie()); game.fireEvent(new GameEventRollDie());
String message = Localizer.getInstance().getMessage("lblPlayerRolledResult", activator.getName(), result.toString()); PlanarDice result = PlanarDice.roll(activator, null);
String message = Localizer.getInstance().getMessage("lblPlanarDiceResult", result.toString());
game.getAction().notifyOfValue(sa, activator, message, null); game.getAction().notifyOfValue(sa, activator, message, null);
} }

View File

@@ -341,23 +341,21 @@ public class CardFactory {
} }
private static void buildPlaneAbilities(Card card) { private static void buildPlaneAbilities(Card card) {
StringBuilder triggerSB = new StringBuilder(); String trigger = "Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | Secondary$ True | " +
triggerSB.append("Mode$ PlanarDice | Result$ Planeswalk | TriggerZones$ Command | Secondary$ True | "); "TriggerDescription$ Whenever you roll the Planeswalker symbol on the planar die, planeswalk.";
triggerSB.append("TriggerDescription$ Whenever you roll Planeswalk, put this card on the bottom of its owner's planar deck face down, ");
triggerSB.append("then move the top card of your planar deck off that planar deck and turn it face up");
String rolledWalk = "DB$ Planeswalk"; String rolledWalk = "DB$ Planeswalk";
Trigger planesWalkTrigger = TriggerHandler.parseTrigger(triggerSB.toString(), card, true); Trigger planesWalkTrigger = TriggerHandler.parseTrigger(trigger, card, true);
planesWalkTrigger.setOverridingAbility(AbilityFactory.getAbility(rolledWalk, card)); planesWalkTrigger.setOverridingAbility(AbilityFactory.getAbility(rolledWalk, card));
card.addTrigger(planesWalkTrigger); card.addTrigger(planesWalkTrigger);
StringBuilder saSB = new StringBuilder(); String specialA = "ST$ RollPlanarDice | Cost$ X | SorcerySpeed$ True | Activator$ Player | SpecialAction$ True" +
saSB.append("AB$ RollPlanarDice | Cost$ X | SorcerySpeed$ True | Activator$ Player | ActivationZone$ Command | "); " | ActivationZone$ Command | SpellDescription$ Roll the planar dice. X is equal to the number of " +
saSB.append("SpellDescription$ Roll the planar dice. X is equal to the amount of times the planar die has been rolled this turn."); "times you have previously taken this action this turn.";
SpellAbility planarRoll = AbilityFactory.getAbility(saSB.toString(), card); SpellAbility planarRoll = AbilityFactory.getAbility(specialA, card);
planarRoll.setSVar("X", "Count$RolledThisTurn"); planarRoll.setSVar("X", "Count$PlanarDiceSpecialActionThisTurn");
card.addSpellAbility(planarRoll); card.addSpellAbility(planarRoll);
} }

View File

@@ -91,7 +91,7 @@ public class PhaseHandler implements java.io.Serializable {
private int nUpkeepsThisGame = 0; private int nUpkeepsThisGame = 0;
private int nCombatsThisTurn = 0; private int nCombatsThisTurn = 0;
private int nMain2sThisTurn = 0; private int nMain2sThisTurn = 0;
private int planarDiceRolledthisTurn = 0; private int planarDiceSpecialActionThisTurn = 0;
private transient Player playerTurn = null; private transient Player playerTurn = null;
private transient Player playerPreviousTurn = null; private transient Player playerPreviousTurn = null;
@@ -522,7 +522,7 @@ public class PhaseHandler implements java.io.Serializable {
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(playerTurn); final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(playerTurn);
game.getTriggerHandler().runTrigger(TriggerType.TurnBegin, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.TurnBegin, runParams, false);
} }
planarDiceRolledthisTurn = 0; planarDiceSpecialActionThisTurn = 0;
// Play the End Turn sound // Play the End Turn sound
game.fireEvent(new GameEventTurnEnded()); game.fireEvent(new GameEventTurnEnded());
break; break;
@@ -1212,11 +1212,11 @@ public class PhaseHandler implements java.io.Serializable {
onPhaseBegin(); onPhaseBegin();
} }
public int getPlanarDiceRolledthisTurn() { public int getPlanarDiceSpecialActionThisTurn() {
return planarDiceRolledthisTurn; return planarDiceSpecialActionThisTurn;
} }
public void incPlanarDiceRolledthisTurn() { public void incPlanarDiceSpecialActionThisTurn() {
planarDiceRolledthisTurn++; planarDiceSpecialActionThisTurn++;
} }
public String debugPrintState(boolean hasPriority) { public String debugPrintState(boolean hasPriority) {

View File

@@ -5,6 +5,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import forge.game.*;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -19,13 +20,7 @@ import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
import forge.deck.Deck; import forge.deck.Deck;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GameOutcome.AnteResult; import forge.game.GameOutcome.AnteResult;
import forge.game.GameType;
import forge.game.GameView;
import forge.game.Match;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection; import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
@@ -192,6 +187,8 @@ public abstract class PlayerController {
return chooseSector(assignee, ai, sectors); return chooseSector(assignee, ai, sectors);
} }
public abstract PlanarDice choosePDRollToIgnore(List<PlanarDice> rolls);
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer); public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer);
public abstract CardCollectionView getCardsToMulligan(Player firstPlayer); public abstract CardCollectionView getCardsToMulligan(Player firstPlayer);

View File

@@ -0,0 +1,56 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.replacement;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
import java.util.Map;
public class ReplacePlanarDiceResult extends ReplacementEffect {
/**
* Instantiates a new replace roll planar dice.
*
* @param params the params
* @param host the host
*/
public ReplacePlanarDiceResult(final Map<String, String> params, final Card host, final boolean intrinsic) {
super(params, host, intrinsic);
}
/* (non-Javadoc)
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidRoll", runParams.get(AbilityKey.Result))) {
return false;
}
return true;
}
/* (non-Javadoc)
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Result, runParams.get(AbilityKey.Result));
}
}

View File

@@ -0,0 +1,57 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package forge.game.replacement;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
import java.util.Map;
public class ReplaceRollPlanarDice extends ReplacementEffect {
/**
* Instantiates a new replace roll planar dice.
*
* @param params the params
* @param host the host
*/
public ReplaceRollPlanarDice(final Map<String, String> params, final Card host, final boolean intrinsic) {
super(params, host, intrinsic);
}
/* (non-Javadoc)
* @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
*/
@Override
public boolean canReplace(Map<AbilityKey, Object> runParams) {
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
return false;
}
return true;
}
/* (non-Javadoc)
* @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.Map, forge.card.spellability.SpellAbility)
*/
@Override
public void setReplacingObjects(Map<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Number, runParams.get(AbilityKey.Number));
sa.setReplacingObject(AbilityKey.Ignore, runParams.get(AbilityKey.Ignore));
}
}

View File

@@ -33,8 +33,10 @@ public enum ReplacementType {
LoseMana(ReplaceLoseMana.class), LoseMana(ReplaceLoseMana.class),
Mill(ReplaceMill.class), Mill(ReplaceMill.class),
Moved(ReplaceMoved.class), Moved(ReplaceMoved.class),
PlanarDiceResult(ReplacePlanarDiceResult.class),
ProduceMana(ReplaceProduceMana.class), ProduceMana(ReplaceProduceMana.class),
Proliferate(ReplaceProliferate.class), Proliferate(ReplaceProliferate.class),
RollPlanarDice(ReplaceRollPlanarDice.class),
Scry(ReplaceScry.class), Scry(ReplaceScry.class),
SetInMotion(ReplaceSetInMotion.class), SetInMotion(ReplaceSetInMotion.class),
Surveil(ReplaceSurveil.class), Surveil(ReplaceSurveil.class),

View File

@@ -68,6 +68,7 @@ public class MessageUtil {
case Protection: case Protection:
return Localizer.getInstance().getMessage("lblPlayerChooseValue", choser, value); return Localizer.getInstance().getMessage("lblPlayerChooseValue", choser, value);
case RollDice: case RollDice:
case RollPlanarDice:
case PutCounter:// For Clay Golem cost text case PutCounter:// For Clay Golem cost text
return value; return value;
case Vote: case Vote:

View File

@@ -20,10 +20,7 @@ import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
import forge.deck.Deck; import forge.deck.Deck;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.game.Game; import forge.game.*;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GameType;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.*; import forge.game.card.*;
import forge.game.combat.Combat; import forge.game.combat.Combat;
@@ -503,6 +500,11 @@ public class PlayerControllerForTests extends PlayerController {
return chooseItem(sectors); return chooseItem(sectors);
} }
@Override
public PlanarDice choosePDRollToIgnore(List<PlanarDice> rolls) {
return Aggregates.random(rolls);
}
@Override @Override
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer) { public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer) {
return chooseItem(options); return chooseItem(options);

View File

@@ -1,10 +1,11 @@
Name:Chaotic Aether Name:Chaotic Aether
ManaCost:no cost ManaCost:no cost
Types:Phenomenon Types:Phenomenon
T:Mode$ PlaneswalkedTo | ValidCard$ Card.Self | Execute$ Aether | TriggerDescription$ When you encounter CARDNAME, each blank roll of the planar dice is a {CHAOS} roll until a player planeswalks away from a plane. (Then planeswalk away from this phenomenon) T:Mode$ PlaneswalkedTo | ValidCard$ Card.Self | Execute$ Aether | TriggerDescription$ When you encounter CARDNAME, each blank roll of the planar dice is a {CHAOS} roll until a player planeswalks away from a plane. (Then planeswalk away from this phenomenon.)
SVar:Aether:DB$ Effect | Name$ Chaotic Aether Effect | StaticAbilities$ STBlankIsChaos | Triggers$ TPWAway | SubAbility$ PWAway SVar:Aether:DB$ Effect | ReplacementEffects$ BlankIsChaos | Triggers$ TPWAway | SubAbility$ PWAway
SVar:PWAway:DB$ Planeswalk | Cost$ 0 SVar:PWAway:DB$ Planeswalk
SVar:STBlankIsChaos:Mode$ Continuous | EffectZone$ Command | GlobalRule$ Each blank roll of the planar dice is a {CHAOS} roll. SVar:BlankIsChaos:Event$ PlanarDiceResult | ValidRoll$ Blank | ReplaceWith$ REChaos | Description$ Each blank roll of the planar die is a {CHAOS} roll until a player planeswalks away from a plane.
SVar:TPWAway:Mode$ PlaneswalkedFrom | ValidCard$ Plane | Execute$ ExileSelf | Static$ True | TriggerDescription$ Until a player planeswalks away from a plane, each blank roll of the planar dice is a {CHAOS} roll. SVar:REChaos:DB$ ReplaceEffect | VarName$ Result | VarValue$ Chaos | VarType$ PlanarDice
SVar:TPWAway:Mode$ PlaneswalkedFrom | ValidCard$ Plane | Execute$ ExileSelf | Static$ True
SVar:ExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:ExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile
Oracle:When you encounter Chaotic Aether, each blank roll of the planar die is a CHAOS roll until a player planeswalks away from a plane. (Then planeswalk away from this phenomenon.) Oracle:When you encounter Chaotic Aether, each blank roll of the planar die is a {CHAOS} roll until a player planeswalks away from a plane. (Then planeswalk away from this phenomenon.)

View File

@@ -1,7 +1,7 @@
Name:Krosa Name:Krosa
ManaCost:no cost ManaCost:no cost
Types:Plane Dominaria Types:Plane Dominaria
S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature | AddPower$ 2 | AddToughness$ 2 | Description$ All Creatures get +2/+2. S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature | AddPower$ 2 | AddToughness$ 2 | Description$ All creatures get +2/+2.
T:Mode$ PlanarDice | Result$ Chaos | OptionalDecider$ You | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, you may add {W}{U}{B}{R}{G}. T:Mode$ PlanarDice | Result$ Chaos | OptionalDecider$ You | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll {CHAOS}, you may add {W}{U}{B}{R}{G}.
SVar:RolledChaos:DB$ Mana | Produced$ W U B R G SVar:RolledChaos:DB$ Mana | Produced$ W U B R G
SVar:AIRollPlanarDieParams:Mode$ Always | RollInMain1$ True SVar:AIRollPlanarDieParams:Mode$ Always | RollInMain1$ True

View File

@@ -0,0 +1,8 @@
Name:Ichor Elixir
ManaCost:4
Types:Artifact
R:Event$ RollPlanarDice | ValidPlayer$ You | ReplaceWith$ PlusRoll | Description$ If you would roll one or more planar dice, instead roll that many planar dice plus one and ignore one.
SVar:PlusRoll:DB$ ReplaceEffect | VarName$ Number | VarValue$ ReplaceCount$Number/Plus.1 | SubAbility$ IgnoreRoll
SVar:IgnoreRoll:DB$ ReplaceEffect | VarName$ Ignore | VarValue$ ReplaceCount$Ignore/Plus.1
A:AB$ Mana | Cost$ T | Produced$ C | Amount$ 2 | SpellDescription$ Add {C}{C}.
Oracle:If you would roll one or more planar dice, instead roll that many planar dice plus one and ignore one.\n{T}: Add {C}{C}.

View File

@@ -1351,6 +1351,7 @@ lblLibrary=Bibliothek
lblGraveyard=Friedhof lblGraveyard=Friedhof
lblAssignSectorCreature=Weise {0} einem Bereich zu lblAssignSectorCreature=Weise {0} einem Bereich zu
lblChooseSectorEffect=Wähle Bereich lblChooseSectorEffect=Wähle Bereich
lblChooseRollIgnore=Choose a roll to ignore
lblTop=Oben drauf lblTop=Oben drauf
lblBottom=Unten drunter lblBottom=Unten drunter
lblNColorManaFromCard={0} {1} Mana von {2} lblNColorManaFromCard={0} {1} Mana von {2}
@@ -2042,8 +2043,10 @@ lblDoyouWantShuffleTheLibrary=Möchtest du deine Bibliothek mischen?
lblDoYouWantRepeatProcessAgain=Möchtest du den Vorgang wiederholen? lblDoYouWantRepeatProcessAgain=Möchtest du den Vorgang wiederholen?
#RevealHandEffect.java #RevealHandEffect.java
lblDoYouWantRevealYourHand=Möchtest du deine Handkarten offen vorzeigen? lblDoYouWantRevealYourHand=Möchtest du deine Handkarten offen vorzeigen?
#RollDiceEffect.java
lblPlayerRolledResult={0} würfelt {1}
#RollPlanarDiceEffect.java #RollPlanarDiceEffect.java
lblPlayerRolledResult={0} würfelte {1}? lblPlanarDiceResult=Planar dice result: {0}
#SacrificeEffect.java #SacrificeEffect.java
lblDoYouWantPayEcho=Möchtest du die Echokosten zahlen lblDoYouWantPayEcho=Möchtest du die Echokosten zahlen
lblPayEcho=Zahle Echokosten lblPayEcho=Zahle Echokosten

View File

@@ -1357,6 +1357,7 @@ lblLibrary=Library
lblGraveyard=Graveyard lblGraveyard=Graveyard
lblAssignSectorCreature=Assign {0} to a sector lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector lblChooseSectorEffect=Choose a sector
lblChooseRollIgnore=Choose a roll to ignore
lblTop=Top lblTop=Top
lblBottom=Bottom lblBottom=Bottom
lblNColorManaFromCard={0} {1} mana from {2} lblNColorManaFromCard={0} {1} mana from {2}
@@ -2048,8 +2049,10 @@ lblDoyouWantShuffleTheLibrary=Do you want to shuffle the library?
lblDoYouWantRepeatProcessAgain=Do you want to repeat this process again? lblDoYouWantRepeatProcessAgain=Do you want to repeat this process again?
#RevealHandEffect.java #RevealHandEffect.java
lblDoYouWantRevealYourHand=Do you want to reveal your hand? lblDoYouWantRevealYourHand=Do you want to reveal your hand?
#RollPlanarDiceEffect.java #RollDiceEffect.java
lblPlayerRolledResult={0} rolled {1} lblPlayerRolledResult={0} rolled {1}
#RollPlanarDiceEffect.java
lblPlanarDiceResult=Planar dice result: {0}
#SacrificeEffect.java #SacrificeEffect.java
lblDoYouWantPayEcho=Do you want to pay Echo lblDoYouWantPayEcho=Do you want to pay Echo
lblPayEcho=Pay Echo lblPayEcho=Pay Echo

View File

@@ -1352,6 +1352,7 @@ lblLibrary=Biblioteca
lblGraveyard=Cementerio lblGraveyard=Cementerio
lblAssignSectorCreature=Assign {0} to a sector lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector lblChooseSectorEffect=Choose a sector
lblChooseRollIgnore=Choose a roll to ignore
lblTop=Superior lblTop=Superior
lblBottom=Fondo lblBottom=Fondo
lblNColorManaFromCard={0} {1} maná de {2} lblNColorManaFromCard={0} {1} maná de {2}
@@ -2043,8 +2044,10 @@ lblDoyouWantShuffleTheLibrary=¿Quieres barajar la biblioteca?
lblDoYouWantRepeatProcessAgain=¿Quiere repetir este proceso de nuevo? lblDoYouWantRepeatProcessAgain=¿Quiere repetir este proceso de nuevo?
#RevealHandEffect.java #RevealHandEffect.java
lblDoYouWantRevealYourHand=¿Quieres descubrir tu mano? lblDoYouWantRevealYourHand=¿Quieres descubrir tu mano?
#RollPlanarDiceEffect.java #RollDiceEffect.java
lblPlayerRolledResult={0} lanzó {1} lblPlayerRolledResult={0} lanzó {1}
#RollPlanarDiceEffect.java
lblPlanarDiceResult=Planar dice result: {0}
#SacrificeEffect.java #SacrificeEffect.java
lblDoYouWantPayEcho=¿Quieres pagar Eco lblDoYouWantPayEcho=¿Quieres pagar Eco
lblPayEcho=Pagar Eco lblPayEcho=Pagar Eco

View File

@@ -1294,7 +1294,7 @@ lblError=Erreur
lblWinGame=Gagner la partie lblWinGame=Gagner la partie
lblSetLifetoWhat=Mettre la vie à quoi ? lblSetLifetoWhat=Mettre la vie à quoi ?
lblSetLifeforWhichPlayer=Définir la vie pour quel joueur lblSetLifeforWhichPlayer=Définir la vie pour quel joueur
kglChoosePermanentstoTap=Choisir les permanents à engager lblChoosePermanentstoTap=Choisir les permanents à engager
lblChoosePermanentstoUntap=Choisir les permanents à dégager lblChoosePermanentstoUntap=Choisir les permanents à dégager
lblWhichTypeofCounter=Quel type de marqueur ? lblWhichTypeofCounter=Quel type de marqueur ?
lblHowManyCounters=Combien de marqueurs ? lblHowManyCounters=Combien de marqueurs ?
@@ -1311,7 +1311,7 @@ lblSelectOrderForSimultaneousAbilities=Sélectionner l''ordre pour les capacité
lblReorderSimultaneousAbilities=Réorganiser les capacités simultanées lblReorderSimultaneousAbilities=Réorganiser les capacités simultanées
lblResolveFirst=Résoudre en premier lblResolveFirst=Résoudre en premier
lblMoveCardstoToporBbottomofLibrary=Déplacer les cartes en haut ou en bas de la bibliothèque lblMoveCardstoToporBbottomofLibrary=Déplacer les cartes en haut ou en bas de la bibliothèque
lblSelectCardsToBeOutOnTheBottomOfYourLibrary=S\u00e9lectionnez les cartes \u00e0 mettre en bas de votre biblioth\u00e8que lblSelectCardsToBeOutOnTheBottomOfYourLibrary=Sélectionnez les cartes à mettre en bas de votre bibliothèque
lblCardsToPutOnTheBottom=Cartes à mettre en bas lblCardsToPutOnTheBottom=Cartes à mettre en bas
lblArrangeCardsToBePutOnTopOfYourLibrary=Disposer les cartes à mettre au-dessus de votre bibliothèque lblArrangeCardsToBePutOnTopOfYourLibrary=Disposer les cartes à mettre au-dessus de votre bibliothèque
lblTopOfLibrary=Haut de la bibliothèque lblTopOfLibrary=Haut de la bibliothèque
@@ -1353,18 +1353,20 @@ lblThereNoCardInPlayerZone=Il n''y a pas de cartes dans {0} {1}
lblPutCardsOnTheTopLibraryOrGraveyard=Placer {0} au-dessus de la bibliothèque ou du cimetière ? lblPutCardsOnTheTopLibraryOrGraveyard=Placer {0} au-dessus de la bibliothèque ou du cimetière ?
lblLibrary=Bibliothèque lblLibrary=Bibliothèque
lblGraveyard=Cimetière lblGraveyard=Cimetière
lblHaut=Haut lblAssignSectorCreature=Assign {0} to a sectorlblChooseSectorEffect=Choose a sector
lblBas=Bas lblChooseRollIgnore=Choose a roll to ignore
lblTop=Haut
lblBottom=Bas
lblNColorManaFromCard={0} {1} mana de {2} lblNColorManaFromCard={0} {1} mana de {2}
lblPayManaFromManaPool=Payer le mana du pool de mana lblPayManaFromManaPool=Payer le mana du pool de mana
lblChooseATargetType=Choisir un type {0} lblChooseATargetType=Choisir un type {0}
lblUntap=Dégager lblUntap=Dégager
lblOdds=Cote lblOdds=Cote
lblPairs=Pairs lblEvens=Pairs
lblLeaveTapped=Laisser tapé lblLeaveTapped=Laisser tapé
lblUntapAndSkipThisTurn=Dégager (et sauter ce tour) lblUntapAndSkipThisTurn=Dégager (et sauter ce tour)
lblGauche=Gauche lblLeft=Gauche
lblDroite=Droite lblRight=Droite
lblAddCounter=Ajouter un marqueur lblAddCounter=Ajouter un marqueur
lblRemoveCounter=Supprimer le marqueur lblRemoveCounter=Supprimer le marqueur
lblWinTheFlip=gagner le flip lblWinTheFlip=gagner le flip
@@ -2045,8 +2047,10 @@ lblDoyouWantShuffleTheLibrary=Voulez-vous mélanger la bibliothèque ?
lblDoYouWantRepeatProcessAgain=Voulez-vous répéter ce processus à nouveau ? lblDoYouWantRepeatProcessAgain=Voulez-vous répéter ce processus à nouveau ?
#RevealHandEffect.java #RevealHandEffect.java
lblDoYouWantRevealYourHand=Voulez-vous révéler votre main ? lblDoYouWantRevealYourHand=Voulez-vous révéler votre main ?
#RollPlanarDiceEffect.java #RollDiceEffect.java
lblPlayerRolledResult={0} a lancé {1} lblPlayerRolledResult={0} a lancé {1}
#RollPlanarDiceEffect.java
lblPlanarDiceResult=Planar dice result: {0}
#SacrificeEffect.java #SacrificeEffect.java
lblDoYouWantPayEcho=Voulez-vous payer Echo lblDoYouWantPayEcho=Voulez-vous payer Echo
lblPayEcho=Écho de paiement lblPayEcho=Écho de paiement

View File

@@ -1352,6 +1352,7 @@ lblLibrary=Grimorio
lblGraveyard=Cimitero lblGraveyard=Cimitero
lblAssignSectorCreature=Assign {0} to a sector lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector lblChooseSectorEffect=Choose a sector
lblChooseRollIgnore=Choose a roll to ignore
lblTop=Cima lblTop=Cima
lblBottom=Fondo lblBottom=Fondo
lblNColorManaFromCard={0} {1} mana da {2} lblNColorManaFromCard={0} {1} mana da {2}
@@ -2042,8 +2043,10 @@ lblDoyouWantShuffleTheLibrary=Vuoi mescolare il grimorio?
lblDoYouWantRepeatProcessAgain=Vuoi ripetere nuovamente questo procedimento? lblDoYouWantRepeatProcessAgain=Vuoi ripetere nuovamente questo procedimento?
#RevealHandEffect.java #RevealHandEffect.java
lblDoYouWantRevealYourHand=Vuoi rivelare la tua mano? lblDoYouWantRevealYourHand=Vuoi rivelare la tua mano?
#RollPlanarDiceEffect.java #RollDiceEffect.java
lblPlayerRolledResult={0} ha ottenuto {1} col dado lblPlayerRolledResult={0} ha ottenuto {1} col dado
#RollPlanarDiceEffect.java
lblPlanarDiceResult=Planar dice result: {0}
#SacrificeEffect.java #SacrificeEffect.java
lblDoYouWantPayEcho=Vuoi pagare Eco lblDoYouWantPayEcho=Vuoi pagare Eco
lblPayEcho=Paga Eco lblPayEcho=Paga Eco

View File

@@ -1353,6 +1353,7 @@ lblLibrary=ライブラリー
lblGraveyard=墓地 lblGraveyard=墓地
lblAssignSectorCreature=Assign {0} to a sector lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector lblChooseSectorEffect=Choose a sector
lblChooseRollIgnore=Choose a roll to ignore
lblTop=タップ lblTop=タップ
lblBottom=ボトム lblBottom=ボトム
lblNColorManaFromCard={2}から {0} {1} のマナ lblNColorManaFromCard={2}から {0} {1} のマナ
@@ -2042,8 +2043,10 @@ lblDoyouWantShuffleTheLibrary=ライブラリーをシャッフルしますか
lblDoYouWantRepeatProcessAgain=この手順をもう一度しますか? lblDoYouWantRepeatProcessAgain=この手順をもう一度しますか?
#RevealHandEffect.java #RevealHandEffect.java
lblDoYouWantRevealYourHand=手札を公開しますか? lblDoYouWantRevealYourHand=手札を公開しますか?
#RollPlanarDiceEffect.java #RollDiceEffect.java
lblPlayerRolledResult={0}が {1}をロールしました lblPlayerRolledResult={0}が {1}をロールしました
#RollPlanarDiceEffect.java
lblPlanarDiceResult=Planar dice result: {0}
#SacrificeEffect.java #SacrificeEffect.java
lblDoYouWantPayEcho=エコーコストを支払いますか: lblDoYouWantPayEcho=エコーコストを支払いますか:
lblPayEcho=エコーコストを支払います lblPayEcho=エコーコストを支払います

View File

@@ -1383,6 +1383,7 @@ lblLibrary=Grimório
lblGraveyard=Cemitério lblGraveyard=Cemitério
lblAssignSectorCreature=Assign {0} to a sector lblAssignSectorCreature=Assign {0} to a sector
lblChooseSectorEffect=Choose a sector lblChooseSectorEffect=Choose a sector
lblChooseRollIgnore=Choose a roll to ignore
lblTop=Topo lblTop=Topo
lblBottom=Fundo lblBottom=Fundo
lblNColorManaFromCard={0} {1} mana de {2} lblNColorManaFromCard={0} {1} mana de {2}
@@ -2104,8 +2105,10 @@ lblDoyouWantShuffleTheLibrary=Quer embaralhar o grimório?
lblDoYouWantRepeatProcessAgain=Quer repetir este processo novamente? lblDoYouWantRepeatProcessAgain=Quer repetir este processo novamente?
#RevealHandEffect.java #RevealHandEffect.java
lblDoYouWantRevealYourHand=Você quer revelar sua mão? lblDoYouWantRevealYourHand=Você quer revelar sua mão?
#RollPlanarDiceEffect.java #RollDiceEffect.java
lblPlayerRolledResult={0} tirou {1} lblPlayerRolledResult={0} tirou {1}
#RollPlanarDiceEffect.java
lblPlanarDiceResult=Planar dice result: {0}
#SacrificeEffect.java #SacrificeEffect.java
lblDoYouWantPayEcho=Quer pagar pelo Eco lblDoYouWantPayEcho=Quer pagar pelo Eco
lblPayEcho=Pagar Eco lblPayEcho=Pagar Eco

View File

@@ -1354,6 +1354,7 @@ lblLibrary=牌库
lblGraveyard=坟场 lblGraveyard=坟场
lblAssignSectorCreature=将{0}分配给一部分 lblAssignSectorCreature=将{0}分配给一部分
lblChooseSectorEffect=选择一部分 lblChooseSectorEffect=选择一部分
lblChooseRollIgnore=Choose a roll to ignore
lblTop= lblTop=
lblBottom= lblBottom=
lblNColorManaFromCard={2}产{0}个{1}法术力 lblNColorManaFromCard={2}产{0}个{1}法术力
@@ -2046,8 +2047,10 @@ lblDoyouWantShuffleTheLibrary=你想要洗这个牌库吗?
lblDoYouWantRepeatProcessAgain=你是否想再次重复这个过程? lblDoYouWantRepeatProcessAgain=你是否想再次重复这个过程?
#RevealHandEffect.java #RevealHandEffect.java
lblDoYouWantRevealYourHand=你想展示你的手牌吗? lblDoYouWantRevealYourHand=你想展示你的手牌吗?
#RollPlanarDiceEffect.java #RollDiceEffect.java
lblPlayerRolledResult={0}掷骰结果为{1} lblPlayerRolledResult={0}掷骰结果为{1}
#RollPlanarDiceEffect.java
lblPlanarDiceResult=Planar dice result: {0}
#SacrificeEffect.java #SacrificeEffect.java
lblDoYouWantPayEcho=你想支付返响费用 lblDoYouWantPayEcho=你想支付返响费用
lblPayEcho=支付返响费用 lblPayEcho=支付返响费用

View File

@@ -1391,6 +1391,11 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return getGui().one(prompt, sectors); return getGui().one(prompt, sectors);
} }
@Override
public PlanarDice choosePDRollToIgnore(List<PlanarDice> rolls) {
return getGui().one(Localizer.getInstance().getMessage("lblChooseRollIgnore"), rolls);
}
@Override @Override
public Object vote(final SpellAbility sa, final String prompt, final List<Object> options, public Object vote(final SpellAbility sa, final String prompt, final List<Object> options,
final ListMultimap<Object, Player> votes, Player forPlayer) { final ListMultimap<Object, Player> votes, Player forPlayer) {