mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 11:48:02 +00:00
UNF: Added the 4 eternal-format-legal dice modification/reroll cards (#7489)
This commit is contained in:
@@ -15,6 +15,7 @@ import forge.game.*;
|
|||||||
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.ability.effects.RollDiceEffect;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
@@ -745,6 +746,30 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return Aggregates.random(rolls);
|
return Aggregates.random(rolls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Integer> chooseDiceToReroll(List<Integer> rolls) {
|
||||||
|
//TODO create AI logic for this
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer chooseRollToModify(List<Integer> rolls) {
|
||||||
|
//TODO create AI logic for this
|
||||||
|
return Aggregates.random(rolls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RollDiceEffect.DieRollResult chooseRollToSwap(List<RollDiceEffect.DieRollResult> rolls) {
|
||||||
|
//TODO create AI logic for this
|
||||||
|
return Aggregates.random(rolls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseRollSwapValue(List<String> swapChoices, Integer currentResult, int power, int toughness) {
|
||||||
|
//TODO create AI logic for this
|
||||||
|
return Aggregates.random(swapChoices);
|
||||||
|
}
|
||||||
|
|
||||||
@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);
|
||||||
@@ -1207,6 +1232,11 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean payCostDuringRoll(final Cost cost, final SpellAbility sa, final FCollectionView<Player> allPayers) {
|
||||||
|
// TODO logic for AI to pay rerolls and modification costs
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
||||||
for (final SpellAbility sa : getAi().orderPlaySa(activePlayerSAs)) {
|
for (final SpellAbility sa : getAi().orderPlaySa(activePlayerSAs)) {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ public enum AbilityKey {
|
|||||||
DefendingPlayer("DefendingPlayer"),
|
DefendingPlayer("DefendingPlayer"),
|
||||||
Destination("Destination"),
|
Destination("Destination"),
|
||||||
Devoured("Devoured"),
|
Devoured("Devoured"),
|
||||||
|
DicePTExchanges("DicePTExchanges"),
|
||||||
Discard("Discard"),
|
Discard("Discard"),
|
||||||
DiscardedBefore("DiscardedBefore"),
|
DiscardedBefore("DiscardedBefore"),
|
||||||
DividedShieldAmount("DividedShieldAmount"),
|
DividedShieldAmount("DividedShieldAmount"),
|
||||||
@@ -94,6 +95,7 @@ public enum AbilityKey {
|
|||||||
Mode("Mode"),
|
Mode("Mode"),
|
||||||
Modifier("Modifier"),
|
Modifier("Modifier"),
|
||||||
MonstrosityAmount("MonstrosityAmount"),
|
MonstrosityAmount("MonstrosityAmount"),
|
||||||
|
NaturalResult("NaturalResult"),
|
||||||
NewCard("NewCard"),
|
NewCard("NewCard"),
|
||||||
NewCounterAmount("NewCounterAmount"),
|
NewCounterAmount("NewCounterAmount"),
|
||||||
NoPreventDamage("NoPreventDamage"),
|
NoPreventDamage("NoPreventDamage"),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package forge.game.ability.effects;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import forge.game.GameObject;
|
import forge.game.GameObject;
|
||||||
import forge.game.PlanarDice;
|
import forge.game.PlanarDice;
|
||||||
@@ -48,6 +49,12 @@ public class ReplaceEffect extends SpellAbilityEffect {
|
|||||||
for (Player key : AbilityUtils.getDefinedPlayers(card, sa.getParam("VarKey"), sa)) {
|
for (Player key : AbilityUtils.getDefinedPlayers(card, sa.getParam("VarKey"), sa)) {
|
||||||
m.put(key, m.getOrDefault(key, 0) + AbilityUtils.calculateAmount(card, varValue, sa));
|
m.put(key, m.getOrDefault(key, 0) + AbilityUtils.calculateAmount(card, varValue, sa));
|
||||||
}
|
}
|
||||||
|
} else if ("CardSet".equals(type)) {
|
||||||
|
Set<Card> cards = (Set<Card>) params.get(varName);
|
||||||
|
List<Card> list = AbilityUtils.getDefinedCards(card, varValue, sa);
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
cards.add(list.get(0));
|
||||||
|
}
|
||||||
} else if (varName != null) {
|
} else if (varName != null) {
|
||||||
params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa));
|
params.put(varName, AbilityUtils.calculateAmount(card, varValue, sa));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,16 @@ import com.google.common.collect.Maps;
|
|||||||
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;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.*;
|
||||||
|
import forge.game.cost.Cost;
|
||||||
import forge.game.event.GameEventRollDie;
|
import forge.game.event.GameEventRollDie;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.player.PlayerCollection;
|
import forge.game.player.PlayerCollection;
|
||||||
|
import forge.game.player.PlayerController;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Lang;
|
import forge.util.Lang;
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
import forge.util.MyRandom;
|
import forge.util.MyRandom;
|
||||||
@@ -38,6 +41,59 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DieRollResult {
|
||||||
|
private int naturalValue;
|
||||||
|
private int modifiedValue;
|
||||||
|
|
||||||
|
public DieRollResult(int naturalValue, int modifiedValue) {
|
||||||
|
this.naturalValue = naturalValue;
|
||||||
|
this.modifiedValue = modifiedValue;
|
||||||
|
}
|
||||||
|
// Getters
|
||||||
|
public int getNaturalValue() {
|
||||||
|
return naturalValue;
|
||||||
|
}
|
||||||
|
public int getModifiedValue() {
|
||||||
|
return modifiedValue;
|
||||||
|
}
|
||||||
|
// Setters
|
||||||
|
public void setNaturalValue(int naturalValue) {
|
||||||
|
this.naturalValue = naturalValue;
|
||||||
|
}
|
||||||
|
public void setModifiedValue(int modifiedValue) {
|
||||||
|
this.modifiedValue = modifiedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.valueOf(modifiedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<DieRollResult> getResultsList(List<Integer> naturalResults) {
|
||||||
|
List<DieRollResult> results = new ArrayList<>();
|
||||||
|
for (int r : naturalResults) {
|
||||||
|
results.add(new DieRollResult(r, r));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Integer> getNaturalResults(List<DieRollResult> results) {
|
||||||
|
List<Integer> naturalResults = new ArrayList<>();
|
||||||
|
for (DieRollResult r : results) {
|
||||||
|
naturalResults.add(r.getNaturalValue());
|
||||||
|
}
|
||||||
|
return naturalResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Integer> getFinalResults(List<DieRollResult> results) {
|
||||||
|
List<Integer> naturalResults = new ArrayList<>();
|
||||||
|
for (DieRollResult r : results) {
|
||||||
|
naturalResults.add(r.getModifiedValue());
|
||||||
|
}
|
||||||
|
return naturalResults;
|
||||||
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
|
* @see forge.card.abilityfactory.SpellEffect#getStackDescription(java.util.Map, forge.card.spellability.SpellAbility)
|
||||||
*/
|
*/
|
||||||
@@ -80,12 +136,277 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map<Player, Integer> ignoreChosenMap = Maps.newHashMap();
|
Map<Player, Integer> ignoreChosenMap = Maps.newHashMap();
|
||||||
|
Set<Card> dicePTExchanges = new HashSet<>();
|
||||||
|
|
||||||
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
|
final Map<AbilityKey, Object> repParams = AbilityKey.mapFromAffected(player);
|
||||||
|
List<Integer> ignored = new ArrayList<>();
|
||||||
|
List<Integer> naturalRolls = rollAction(amount, sides, ignore, rollsResult, ignored, ignoreChosenMap, dicePTExchanges, player, repParams);
|
||||||
|
|
||||||
|
if (sa != null && sa.hasParam("UseHighestRoll")) {
|
||||||
|
naturalRolls.subList(0, naturalRolls.size() - 1).clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reroll Phase:
|
||||||
|
String monitorKeyword = "Once each turn, you may pay {1} to reroll one or more dice you rolled.";
|
||||||
|
CardCollection canRerollDice = getRerollCards(player, monitorKeyword);
|
||||||
|
while (!canRerollDice.isEmpty()) {
|
||||||
|
List<Integer> diceToReroll = player.getController().chooseDiceToReroll(naturalRolls);
|
||||||
|
if (diceToReroll.isEmpty()) {break;}
|
||||||
|
|
||||||
|
String message = Localizer.getInstance().getMessage("lblChooseRerollCard");
|
||||||
|
Card c = player.getController().chooseSingleEntityForEffect(canRerollDice, sa, message, null);
|
||||||
|
|
||||||
|
String[] parts = c.getSVar("ModsThisTurn").split("\\$");
|
||||||
|
int activationsThisTurn = Integer.parseInt(parts[1]);
|
||||||
|
SpellAbility modifierSA = c.getFirstSpellAbility();
|
||||||
|
Cost cost = new Cost(c.getSVar("RollRerollCost"), false);
|
||||||
|
boolean paid = player.getController().payCostDuringRoll(cost, modifierSA, null);
|
||||||
|
if (paid) {
|
||||||
|
for (Integer roll : diceToReroll) {
|
||||||
|
naturalRolls.remove(roll);
|
||||||
|
}
|
||||||
|
int amountToReroll = diceToReroll.size();
|
||||||
|
List<Integer> rerolls = rollAction(amountToReroll, sides, 0, null, ignored, Maps.newHashMap(), dicePTExchanges, player, repParams);
|
||||||
|
naturalRolls.addAll(rerolls);
|
||||||
|
activationsThisTurn += 1;
|
||||||
|
c.setSVar("ModsThisTurn", "Number$" + activationsThisTurn);
|
||||||
|
canRerollDice.remove(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modification Phase:
|
||||||
|
List<DieRollResult> resultsList = new ArrayList<>();
|
||||||
|
Integer rollToModify;
|
||||||
|
String xenoKeyword = "After you roll a die, you may remove a +1/+1 counter from Xenosquirrels. If you do, increase or decrease the result by 1.";
|
||||||
|
String nightShiftKeyword = "After you roll a die, you may pay 1 life. If you do, increase or decrease the result by 1. Do this only once each turn.";
|
||||||
|
List<Card> canIncrementDice = getIncrementCards(player, xenoKeyword, nightShiftKeyword);
|
||||||
|
boolean hasBeenModified = false;
|
||||||
|
|
||||||
|
if (!canIncrementDice.isEmpty()) {
|
||||||
|
do {
|
||||||
|
rollToModify = player.getController().chooseRollToModify(naturalRolls);
|
||||||
|
if (rollToModify == null) {break;}
|
||||||
|
|
||||||
|
boolean modified = false;
|
||||||
|
DieRollResult dieResult = new DieRollResult(rollToModify, rollToModify);
|
||||||
|
// canIncrementThisRoll won't be empty the first iteration because canIncrementDice wasn't empty
|
||||||
|
CardCollection canIncrementThisRoll = new CardCollection(canIncrementDice);
|
||||||
|
Card c;
|
||||||
|
do {
|
||||||
|
String message = Localizer.getInstance().getMessage("lblChooseRollIncrementCard", rollToModify);
|
||||||
|
c = player.getController().chooseSingleEntityForEffect(canIncrementThisRoll, sa, message, null);
|
||||||
|
|
||||||
|
String[] parts = c.getSVar("ModsThisTurn").split("\\$");
|
||||||
|
int activationsThisTurn = Integer.parseInt(parts[1]);
|
||||||
|
SpellAbility modifierSA = c.getFirstSpellAbility();
|
||||||
|
String costString = c.getSVar("RollModifyCost");
|
||||||
|
Cost cost = new Cost(costString, false);
|
||||||
|
boolean paid = player.getController().payCostDuringRoll(cost, modifierSA, null);
|
||||||
|
if (paid) {
|
||||||
|
message = Localizer.getInstance().getMessage("lblChooseRollIncrement", rollToModify);
|
||||||
|
boolean isPositive = player.getController().chooseBinary(sa, message, PlayerController.BinaryChoiceType.IncreaseOrDecrease);
|
||||||
|
int increment = isPositive ? 1 : -1;
|
||||||
|
if (!modified) {naturalRolls.remove(rollToModify); modified = true;}
|
||||||
|
rollToModify += increment;
|
||||||
|
activationsThisTurn += 1;
|
||||||
|
c.setSVar("ModsThisTurn", "Number$" + activationsThisTurn);
|
||||||
|
canIncrementThisRoll.remove(c);
|
||||||
|
}
|
||||||
|
} while (!canIncrementThisRoll.isEmpty());
|
||||||
|
if (modified) {
|
||||||
|
dieResult.setModifiedValue(rollToModify);
|
||||||
|
resultsList.add(dieResult);
|
||||||
|
hasBeenModified = true;
|
||||||
|
}
|
||||||
|
canIncrementDice = getIncrementCards(player, xenoKeyword, nightShiftKeyword);
|
||||||
|
} while (!naturalRolls.isEmpty() && !canIncrementDice.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish roll list
|
||||||
|
for (Integer unmodified : naturalRolls) {
|
||||||
|
// Add all the unmodified rolls into the results
|
||||||
|
resultsList.add(new DieRollResult(unmodified, unmodified));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vedalken Exchange
|
||||||
|
CardCollection vedalkenSwaps = new CardCollection(dicePTExchanges);
|
||||||
|
if (!vedalkenSwaps.isEmpty()) {
|
||||||
|
DieRollResult rollToSwap;
|
||||||
|
do {
|
||||||
|
rollToSwap = player.getController().chooseRollToSwap(resultsList);
|
||||||
|
if (rollToSwap == null) {break;}
|
||||||
|
|
||||||
|
String message = Localizer.getInstance().getMessage("lblChooseCardToDiceSwap", rollToSwap.getModifiedValue());
|
||||||
|
Card c = player.getController().chooseSingleEntityForEffect(vedalkenSwaps, sa, message, null);
|
||||||
|
int cPower = c.getCurrentPower();
|
||||||
|
int cToughness = c.getCurrentToughness();
|
||||||
|
String labelPower = Localizer.getInstance().getMessage("lblPower");
|
||||||
|
String labelToughness = Localizer.getInstance().getMessage("lblToughness");
|
||||||
|
List<String> choices = Arrays.asList(labelPower, labelToughness);
|
||||||
|
String powerOrToughness = player.getController().chooseRollSwapValue(choices, rollToSwap.getModifiedValue(), cPower, cToughness);
|
||||||
|
if (powerOrToughness != null) {
|
||||||
|
int tempRollValue = rollToSwap.getModifiedValue();
|
||||||
|
if (powerOrToughness.equals(labelPower)) {
|
||||||
|
rollToSwap.setModifiedValue(cPower);
|
||||||
|
c.addNewPT(tempRollValue, cToughness, player.getGame().getNextTimestamp(), 0);
|
||||||
|
} else if (powerOrToughness.equals(labelToughness)) {
|
||||||
|
rollToSwap.setModifiedValue(cToughness);
|
||||||
|
c.addNewPT(cPower, tempRollValue, player.getGame().getNextTimestamp(), 0);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unexpected value: " + powerOrToughness);
|
||||||
|
}
|
||||||
|
vedalkenSwaps.remove(c);
|
||||||
|
}
|
||||||
|
} while (!vedalkenSwaps.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Notify of results
|
||||||
|
if (amount > 0) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String rollResults = StringUtils.join(getFinalResults(resultsList), ", ");
|
||||||
|
String resultMessage = toVisitAttractions ? "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, ", ")));
|
||||||
|
}
|
||||||
|
if (hasBeenModified) {
|
||||||
|
sb.append("\r\n").append(Localizer.getInstance().getMessage("lblNaturalRolls",
|
||||||
|
StringUtils.join(getNaturalResults(resultsList), ", ")));
|
||||||
|
}
|
||||||
|
player.getGame().getAction().notifyOfValue(sa, player, sb.toString(), null);
|
||||||
|
player.addDieRollThisTurn(getFinalResults(resultsList));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> rolls = Lists.newArrayList();
|
||||||
|
int oddResults = 0;
|
||||||
|
int evenResults = 0;
|
||||||
|
int differentResults = 0;
|
||||||
|
int countMaxRolls = 0;
|
||||||
|
for (DieRollResult i : resultsList) {
|
||||||
|
int naturalRoll = i.getNaturalValue();
|
||||||
|
final int modifiedRoll = i.getModifiedValue() + modifier;
|
||||||
|
|
||||||
|
i.setModifiedValue(modifiedRoll);
|
||||||
|
|
||||||
|
if (!rolls.contains(modifiedRoll)) {
|
||||||
|
differentResults++;
|
||||||
|
}
|
||||||
|
rolls.add(modifiedRoll);
|
||||||
|
if (modifiedRoll % 2 == 0) {
|
||||||
|
evenResults++;
|
||||||
|
} else {
|
||||||
|
oddResults++;
|
||||||
|
}
|
||||||
|
if (naturalRoll == sides) {
|
||||||
|
countMaxRolls++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sa != null) {
|
||||||
|
if (sa.hasParam("EvenOddResults")) {
|
||||||
|
sa.setSVar("EvenResults", Integer.toString(evenResults));
|
||||||
|
sa.setSVar("OddResults", Integer.toString(oddResults));
|
||||||
|
}
|
||||||
|
if (sa.hasParam("DifferentResults")) {
|
||||||
|
sa.setSVar("DifferentResults", Integer.toString(differentResults));
|
||||||
|
}
|
||||||
|
if (sa.hasParam("MaxRollsResults")) {
|
||||||
|
sa.setSVar("MaxRolls", Integer.toString(countMaxRolls));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int rollNum = 1;
|
||||||
|
for (DieRollResult roll : resultsList) {
|
||||||
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(player);
|
||||||
|
runParams.put(AbilityKey.Sides, sides);
|
||||||
|
runParams.put(AbilityKey.Modifier, modifier);
|
||||||
|
runParams.put(AbilityKey.Result, roll.getModifiedValue());
|
||||||
|
runParams.put(AbilityKey.NaturalResult, roll.getNaturalValue());
|
||||||
|
runParams.put(AbilityKey.RolledToVisitAttractions, toVisitAttractions);
|
||||||
|
runParams.put(AbilityKey.Number, player.getNumRollsThisTurn() - amount + rollNum);
|
||||||
|
player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false);
|
||||||
|
rollNum++;
|
||||||
|
}
|
||||||
|
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(player);
|
||||||
|
runParams.put(AbilityKey.Sides, sides);
|
||||||
|
runParams.put(AbilityKey.Result, getFinalResults(resultsList));
|
||||||
|
runParams.put(AbilityKey.RolledToVisitAttractions, toVisitAttractions);
|
||||||
|
player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);
|
||||||
|
|
||||||
|
return getFinalResults(resultsList).stream().reduce(0, Integer::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of cards that can reroll dice roll results for a given player.
|
||||||
|
* This is currently only Monitor Monitor
|
||||||
|
*
|
||||||
|
* @param player The player whose battlefield is being checked for cards that can modify dice rolls
|
||||||
|
* @param monitorKeyword The keyword text identifying Monitor Monitor cards
|
||||||
|
* @return A list of cards that are currently able to reroll dice
|
||||||
|
*/
|
||||||
|
public static CardCollection getRerollCards(Player player, String monitorKeyword) {
|
||||||
|
CardCollection monitors = CardLists.getKeyword(player.getCardsIn(ZoneType.Battlefield), monitorKeyword);
|
||||||
|
return monitors.filter(card -> {
|
||||||
|
String activationLimit = card.getSVar("RollModificationsLimit");
|
||||||
|
String[] parts = card.getSVar("ModsThisTurn").split("\\$");
|
||||||
|
int activationsThisTurn = Integer.parseInt(parts[1]);
|
||||||
|
return (activationLimit.equals("None") || activationsThisTurn < Integer.parseInt(activationLimit));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of cards that can modify dice roll results for a given player.
|
||||||
|
* This includes both Xenosquirrels (which can remove +1/+1 counters to modify rolls)
|
||||||
|
* and Night Shift cards (which can pay life to modify rolls once per turn).
|
||||||
|
*
|
||||||
|
* @param player The player whose battlefield is being checked for cards that can modify dice rolls
|
||||||
|
* @param xenoKeyword The keyword text identifying Xenosquirrel cards
|
||||||
|
* @param nightShiftKeyword The keyword text identifying Night Shift cards
|
||||||
|
* @return A list of cards that are currently able to modify dice roll results
|
||||||
|
*/
|
||||||
|
public static List<Card> getIncrementCards(Player player, String xenoKeyword, String nightShiftKeyword) {
|
||||||
|
CardCollection xenosquirrels = CardLists.getKeyword(player.getCardsIn(ZoneType.Battlefield), xenoKeyword);
|
||||||
|
CardCollection nightShifts = CardLists.getKeyword(player.getCardsIn(ZoneType.Battlefield), nightShiftKeyword);
|
||||||
|
List<Card> canIncrementDice = new ArrayList<>();
|
||||||
|
for (Card c : xenosquirrels) {
|
||||||
|
// Xenosquirrels must have a P1P1 counter on it to remove in order to modify
|
||||||
|
Integer P1P1Counters = c.getCounters().get(CounterType.get(CounterEnumType.P1P1));
|
||||||
|
if (P1P1Counters != null && P1P1Counters > 0 && c.canRemoveCounters(CounterType.get(CounterEnumType.P1P1))) {
|
||||||
|
canIncrementDice.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Card c : nightShifts) {
|
||||||
|
// Night Shift of the Living Dead has a limit of once per turn, player must be able to pay the 1 life cost
|
||||||
|
String activationLimit = c.getSVar("RollModificationsLimit");
|
||||||
|
String[] parts = c.getSVar("ModsThisTurn").split("\\$");
|
||||||
|
int activationsThisTurn = Integer.parseInt(parts[1]);
|
||||||
|
if ((activationLimit.equals("None") || activationsThisTurn < Integer.parseInt(activationLimit)) && player.canPayLife(1, true, c.getFirstSpellAbility())) {
|
||||||
|
canIncrementDice.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return canIncrementDice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the dice rolling action with support for replacements, ignoring rolls, and tracking results.
|
||||||
|
*
|
||||||
|
* @param amount number of dice to roll
|
||||||
|
* @param sides number of sides on each die
|
||||||
|
* @param ignore number of lowest rolls to automatically ignore
|
||||||
|
* @param rollsResult optional list to store roll results, if null a new list will be created
|
||||||
|
* @param ignored list to store ignored roll results
|
||||||
|
* @param ignoreChosenMap mapping of players to number of rolls they can choose to ignore
|
||||||
|
* @param player the player performing the roll
|
||||||
|
* @param repParams replacement effect parameters
|
||||||
|
* @return list of final roll results after applying ignores and replacements, sorted in ascending order
|
||||||
|
*/
|
||||||
|
private static List<Integer> rollAction(int amount, int sides, int ignore, List<Integer> rollsResult, List<Integer> ignored, Map<Player, Integer> ignoreChosenMap, Set<Card> dicePTExchanges, Player player, Map<AbilityKey, Object> repParams) {
|
||||||
|
|
||||||
|
repParams.put(AbilityKey.Sides, sides);
|
||||||
repParams.put(AbilityKey.Number, amount);
|
repParams.put(AbilityKey.Number, amount);
|
||||||
repParams.put(AbilityKey.Ignore, ignore);
|
repParams.put(AbilityKey.Ignore, ignore);
|
||||||
|
repParams.put(AbilityKey.DicePTExchanges, dicePTExchanges);
|
||||||
repParams.put(AbilityKey.IgnoreChosen, ignoreChosenMap);
|
repParams.put(AbilityKey.IgnoreChosen, ignoreChosenMap);
|
||||||
|
|
||||||
switch (player.getGame().getReplacementHandler().run(ReplacementType.RollDice, repParams)) {
|
switch (player.getGame().getReplacementHandler().run(ReplacementType.RollDice, repParams)) {
|
||||||
case NotReplaced:
|
case NotReplaced:
|
||||||
break;
|
break;
|
||||||
@@ -110,7 +431,6 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
|||||||
|
|
||||||
naturalRolls.sort(null);
|
naturalRolls.sort(null);
|
||||||
|
|
||||||
List<Integer> ignored = new ArrayList<>();
|
|
||||||
// Ignore lowest rolls
|
// Ignore lowest rolls
|
||||||
if (ignore > 0) {
|
if (ignore > 0) {
|
||||||
for (int i = ignore - 1; i >= 0; --i) {
|
for (int i = ignore - 1; i >= 0; --i) {
|
||||||
@@ -127,75 +447,7 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa != null && sa.hasParam("UseHighestRoll")) {
|
return naturalRolls;
|
||||||
naturalRolls.subList(0, naturalRolls.size() - 1).clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Notify of results
|
|
||||||
if (amount > 0) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
String rollResults = StringUtils.join(naturalRolls, ", ");
|
|
||||||
String resultMessage = toVisitAttractions ? "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, ", ")));
|
|
||||||
}
|
|
||||||
player.getGame().getAction().notifyOfValue(sa, player, sb.toString(), null);
|
|
||||||
player.addDieRollThisTurn(naturalRolls);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Integer> rolls = Lists.newArrayList();
|
|
||||||
int oddResults = 0;
|
|
||||||
int evenResults = 0;
|
|
||||||
int differentResults = 0;
|
|
||||||
int countMaxRolls = 0;
|
|
||||||
for (Integer i : naturalRolls) {
|
|
||||||
final int modifiedRoll = i + modifier;
|
|
||||||
if (!rolls.contains(modifiedRoll)) {
|
|
||||||
differentResults++;
|
|
||||||
}
|
|
||||||
rolls.add(modifiedRoll);
|
|
||||||
if (modifiedRoll % 2 == 0) {
|
|
||||||
evenResults++;
|
|
||||||
} else {
|
|
||||||
oddResults++;
|
|
||||||
}
|
|
||||||
if (i == sides) {
|
|
||||||
countMaxRolls++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sa != null) {
|
|
||||||
if (sa.hasParam("EvenOddResults")) {
|
|
||||||
sa.setSVar("EvenResults", Integer.toString(evenResults));
|
|
||||||
sa.setSVar("OddResults", Integer.toString(oddResults));
|
|
||||||
}
|
|
||||||
if (sa.hasParam("DifferentResults")) {
|
|
||||||
sa.setSVar("DifferentResults", Integer.toString(differentResults));
|
|
||||||
}
|
|
||||||
if (sa.hasParam("MaxRollsResults")) {
|
|
||||||
sa.setSVar("MaxRolls", Integer.toString(countMaxRolls));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int rollNum = 1;
|
|
||||||
for (Integer roll : rolls) {
|
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(player);
|
|
||||||
runParams.put(AbilityKey.Sides, sides);
|
|
||||||
runParams.put(AbilityKey.Modifier, modifier);
|
|
||||||
runParams.put(AbilityKey.Result, roll);
|
|
||||||
runParams.put(AbilityKey.RolledToVisitAttractions, toVisitAttractions);
|
|
||||||
runParams.put(AbilityKey.Number, player.getNumRollsThisTurn() - amount + rollNum);
|
|
||||||
player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDie, runParams, false);
|
|
||||||
rollNum++;
|
|
||||||
}
|
|
||||||
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromPlayer(player);
|
|
||||||
runParams.put(AbilityKey.Sides, sides);
|
|
||||||
runParams.put(AbilityKey.Result, rolls);
|
|
||||||
runParams.put(AbilityKey.RolledToVisitAttractions, toVisitAttractions);
|
|
||||||
player.getGame().getTriggerHandler().runTrigger(TriggerType.RolledDieOnce, runParams, false);
|
|
||||||
|
|
||||||
return rolls.stream().reduce(0, Integer::sum);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void resolveSub(SpellAbility sa, int num) {
|
private static void resolveSub(SpellAbility sa, int num) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import forge.deck.Deck;
|
|||||||
import forge.deck.DeckSection;
|
import forge.deck.DeckSection;
|
||||||
import forge.game.*;
|
import forge.game.*;
|
||||||
import forge.game.GameOutcome.AnteResult;
|
import forge.game.GameOutcome.AnteResult;
|
||||||
|
import forge.game.ability.effects.RollDiceEffect;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
@@ -55,7 +56,8 @@ public abstract class PlayerController {
|
|||||||
OddsOrEvens,
|
OddsOrEvens,
|
||||||
UntapOrLeaveTapped,
|
UntapOrLeaveTapped,
|
||||||
LeftOrRight,
|
LeftOrRight,
|
||||||
AddOrRemove
|
AddOrRemove,
|
||||||
|
IncreaseOrDecrease
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FullControlFlag {
|
public enum FullControlFlag {
|
||||||
@@ -229,6 +231,10 @@ public abstract class PlayerController {
|
|||||||
|
|
||||||
public abstract PlanarDice choosePDRollToIgnore(List<PlanarDice> rolls);
|
public abstract PlanarDice choosePDRollToIgnore(List<PlanarDice> rolls);
|
||||||
public abstract Integer chooseRollToIgnore(List<Integer> rolls);
|
public abstract Integer chooseRollToIgnore(List<Integer> rolls);
|
||||||
|
public abstract List<Integer> chooseDiceToReroll(List<Integer> rolls);
|
||||||
|
public abstract Integer chooseRollToModify(List<Integer> rolls);
|
||||||
|
public abstract RollDiceEffect.DieRollResult chooseRollToSwap(List<RollDiceEffect.DieRollResult> rolls);
|
||||||
|
public abstract String chooseRollSwapValue(List<String> swapChoices, Integer currentResult, int power, int toughness);
|
||||||
|
|
||||||
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer, boolean optional);
|
public abstract Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer, boolean optional);
|
||||||
|
|
||||||
@@ -292,6 +298,7 @@ public abstract class PlayerController {
|
|||||||
public abstract List<CostPart> orderCosts(List<CostPart> costs);
|
public abstract List<CostPart> orderCosts(List<CostPart> costs);
|
||||||
|
|
||||||
public abstract boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, FCollectionView<Player> allPayers);
|
public abstract boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, FCollectionView<Player> allPayers);
|
||||||
|
public abstract boolean payCostDuringRoll(Cost cost, SpellAbility sa, FCollectionView<Player> allPayers);
|
||||||
|
|
||||||
public abstract boolean payCombatCost(Card card, Cost cost, SpellAbility sa, String prompt);
|
public abstract boolean payCombatCost(Card card, Cost cost, SpellAbility sa, String prompt);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ public class ReplaceRollDice extends ReplacementEffect {
|
|||||||
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
|
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasParam("ValidSides")) {
|
||||||
|
if (((Integer) runParams.get(AbilityKey.Sides)) != Integer.parseInt(getParam("ValidSides"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,5 +42,6 @@ public class ReplaceRollDice extends ReplacementEffect {
|
|||||||
sa.setReplacingObject(AbilityKey.Number, runParams.get(AbilityKey.Number));
|
sa.setReplacingObject(AbilityKey.Number, runParams.get(AbilityKey.Number));
|
||||||
sa.setReplacingObject(AbilityKey.Ignore, runParams.get(AbilityKey.Ignore));
|
sa.setReplacingObject(AbilityKey.Ignore, runParams.get(AbilityKey.Ignore));
|
||||||
sa.setReplacingObject(AbilityKey.IgnoreChosen, runParams.get(AbilityKey.IgnoreChosen));
|
sa.setReplacingObject(AbilityKey.IgnoreChosen, runParams.get(AbilityKey.IgnoreChosen));
|
||||||
|
sa.setReplacingObject(AbilityKey.DicePTExchanges, runParams.get(AbilityKey.DicePTExchanges));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class TriggerRolledDie extends Trigger {
|
|||||||
String[] params = getParam("ValidResult").split(",");
|
String[] params = getParam("ValidResult").split(",");
|
||||||
int result = (int) runParams.get(AbilityKey.Result);
|
int result = (int) runParams.get(AbilityKey.Result);
|
||||||
if (hasParam("Natural")) {
|
if (hasParam("Natural")) {
|
||||||
result -= (int) runParams.get(AbilityKey.Modifier);
|
result = (int) runParams.get(AbilityKey.NaturalResult);
|
||||||
}
|
}
|
||||||
for (String param : params) {
|
for (String param : params) {
|
||||||
if (StringUtils.isNumeric(param)) {
|
if (StringUtils.isNumeric(param)) {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import forge.deck.Deck;
|
|||||||
import forge.deck.DeckSection;
|
import forge.deck.DeckSection;
|
||||||
import forge.game.*;
|
import forge.game.*;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
|
import forge.game.ability.effects.RollDiceEffect;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
@@ -53,10 +54,7 @@ import forge.util.collect.FCollectionView;
|
|||||||
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;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -526,6 +524,26 @@ public class PlayerControllerForTests extends PlayerController {
|
|||||||
return Aggregates.random(rolls);
|
return Aggregates.random(rolls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Integer> chooseDiceToReroll(List<Integer> rolls) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer chooseRollToModify(List<Integer> rolls) {
|
||||||
|
return Aggregates.random(rolls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RollDiceEffect.DieRollResult chooseRollToSwap(List<RollDiceEffect.DieRollResult> rolls) {
|
||||||
|
return Aggregates.random(rolls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseRollSwapValue(List<String> swapChoices, Integer currentResult, int power, int toughness) {
|
||||||
|
return Aggregates.random(swapChoices);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer, boolean optional) {
|
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer, boolean optional) {
|
||||||
return chooseItem(options);
|
return chooseItem(options);
|
||||||
@@ -577,6 +595,12 @@ public class PlayerControllerForTests extends PlayerController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean payCostDuringRoll(final Cost cost, final SpellAbility sa, final FCollectionView<Player> allPayers) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
||||||
for (final SpellAbility sa : activePlayerSAs) {
|
for (final SpellAbility sa : activePlayerSAs) {
|
||||||
|
|||||||
15
forge-gui/res/cardsfolder/m/monitor_monitor.txt
Normal file
15
forge-gui/res/cardsfolder/m/monitor_monitor.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Name:Monitor Monitor
|
||||||
|
ManaCost:2 U U
|
||||||
|
Types:Creature Human Employee
|
||||||
|
PT:2/5
|
||||||
|
# The reroll effect does not work with planar dice currently
|
||||||
|
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigOpenAttraction | TriggerDescription$ When CARDNAME enters the battlefield, open an Attraction.
|
||||||
|
SVar:TrigOpenAttraction:DB$ OpenAttraction
|
||||||
|
T:Mode$ TurnBegin | Execute$ ResetTurnCount | Static$ True
|
||||||
|
SVar:ResetTurnCount:DB$ StoreSVar | SVar$ ModsThisTurn | Type$ Number | Expression$ 0
|
||||||
|
K:Once each turn, you may pay {1} to reroll one or more dice you rolled.
|
||||||
|
SVar:RollModificationsLimit:1
|
||||||
|
SVar:ModsThisTurn:Number$0
|
||||||
|
SVar:RollRerollCost:1
|
||||||
|
AI:RemoveDeck:All
|
||||||
|
Oracle:When this creature enters, open an Attraction. (Put the top card of your Attraction deck onto the battlefield.)\nOnce each turn, you may pay {1} to reroll one or more dice you rolled.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
Name:Night Shift of the Living Dead
|
||||||
|
ManaCost:3 B
|
||||||
|
Types:Enchantment
|
||||||
|
K:After you roll a die, you may pay 1 life. If you do, increase or decrease the result by 1. Do this only once each turn.
|
||||||
|
T:Mode$ TurnBegin | Execute$ ResetTurnCount | Static$ True
|
||||||
|
SVar:ResetTurnCount:DB$ StoreSVar | SVar$ ModsThisTurn | Type$ Number | Expression$ 0
|
||||||
|
T:Mode$ RolledDie | TriggerZones$ Battlefield | Execute$ TrigToken | ValidPlayer$ You | ValidResult$ EQ6 | TriggerDescription$ Whenever you roll a 6, create a 2/2 black Zombie Employee creature token.
|
||||||
|
SVar:TrigToken:DB$ Token | TokenScript$ b_2_2_zombie_employee
|
||||||
|
SVar:RollModificationsLimit:1
|
||||||
|
SVar:ModsThisTurn:Number$0
|
||||||
|
SVar:RollModifyCost:PayLife<1>
|
||||||
|
DeckHas:Ability$Token
|
||||||
|
AI:RemoveDeck:All
|
||||||
|
Oracle:After you roll a die, you may pay 1 life. If you do, increase or decrease the result by 1. Do this only once each turn.\nWhenever you roll a 6, create a 2/2 black Zombie Employee creature token.
|
||||||
12
forge-gui/res/cardsfolder/v/vedalken_squirrel_whacker.txt
Normal file
12
forge-gui/res/cardsfolder/v/vedalken_squirrel_whacker.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Name:Vedalken Squirrel-Whacker
|
||||||
|
ManaCost:3 U
|
||||||
|
Types:Creature Vedalken Guest
|
||||||
|
PT:*/*
|
||||||
|
K:ETBReplacement:Other:TrigRoll
|
||||||
|
SVar:TrigRoll:DB$ RollDice | ResultSVar$ diePwr | SubAbility$ RollTough | SpellDescription$ As CARDNAME enters, roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result.
|
||||||
|
SVar:RollTough:DB$ RollDice | ResultSVar$ dieTgn | SubAbility$ DBAnimate
|
||||||
|
SVar:DBAnimate:DB$ Animate | Defined$ Self | Power$ diePwr | Toughness$ dieTgn | Duration$ Permanent
|
||||||
|
R:Event$ RollDice | ActiveZones$ Battlefield | ValidPlayer$ You | ValidSides$ 6 | ReplaceWith$ SwapRoll | Description$ If you would roll one or more six-sided dice, instead roll them and you may exchange one result with this creature’s base power or base toughness.
|
||||||
|
SVar:SwapRoll:DB$ ReplaceEffect | VarName$ DicePTExchanges | VarType$ CardSet | VarValue$ Self
|
||||||
|
AI:RemoveDeck:All
|
||||||
|
Oracle:As this creature enters, roll a six-sided die twice. Its base power becomes the first result and its base toughness becomes the second result.\nIf you would roll one or more six-sided dice, instead roll them and you may exchange one result with this creature’s base power or base toughness.
|
||||||
11
forge-gui/res/cardsfolder/x/xenosquirrels.txt
Normal file
11
forge-gui/res/cardsfolder/x/xenosquirrels.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Name:Xenosquirrels
|
||||||
|
ManaCost:1 B
|
||||||
|
Types:Creature Alien Squirrel
|
||||||
|
PT:0/0
|
||||||
|
K:etbCounter:P1P1:2
|
||||||
|
K:After you roll a die, you may remove a +1/+1 counter from Xenosquirrels. If you do, increase or decrease the result by 1.
|
||||||
|
SVar:RollModifyCost:SubCounter<1/P1P1>
|
||||||
|
SVar:RollModificationsLimit:None
|
||||||
|
SVar:ModsThisTurn:Number$0
|
||||||
|
AI:RemoveDeck:All
|
||||||
|
Oracle:This creature enters with two +1/+1 counters on it.\nAfter you roll a die, you may remove a +1/+1 counter from this creature. If you do, increase or decrease the result by 1.
|
||||||
@@ -1418,6 +1418,18 @@ lblAssignSprocketNextTurn=(Cranked next turn)
|
|||||||
lblChooseCrank=Choose contraptions to crank
|
lblChooseCrank=Choose contraptions to crank
|
||||||
lblChooseSectorEffect=Choose a sector
|
lblChooseSectorEffect=Choose a sector
|
||||||
lblChooseRollIgnore=Choose a roll to ignore
|
lblChooseRollIgnore=Choose a roll to ignore
|
||||||
|
#RollDiceEffect.java
|
||||||
|
lblChooseDiceToRerollTitle=Choose rolls to reroll?
|
||||||
|
lblChooseRerollCard=Choose a card to reroll dice
|
||||||
|
lblChooseDiceToRerollCaption=Dice to reroll
|
||||||
|
lblChooseRollToModify=Choose a roll to modify?
|
||||||
|
lblChooseRollToSwap=Choose a roll to swap?
|
||||||
|
lblChooseCardToDiceSwap=Choose a card to swap its Power or Toughness with die result {0}
|
||||||
|
lblChooseSwapPT=Swap result {0} with {1}/{2} creature''s Power or Toughness?
|
||||||
|
lblChooseRollIncrementCard=Choose a card to modify die result {0}
|
||||||
|
lblChooseRollIncrement=Increase or decrease result {0} by 1
|
||||||
|
lblIncrease=Increase
|
||||||
|
lblDecrease=Decrease
|
||||||
lblChooseRingBearer=Choose your Ring-bearer
|
lblChooseRingBearer=Choose your Ring-bearer
|
||||||
lblTheRingTempts=The Ring tempts {0}
|
lblTheRingTempts=The Ring tempts {0}
|
||||||
lblTop=Top
|
lblTop=Top
|
||||||
@@ -2149,6 +2161,7 @@ lblDoYouWantRevealYourHand=Do you want to reveal your hand?
|
|||||||
#RollDiceEffect.java
|
#RollDiceEffect.java
|
||||||
lblPlayerRolledResult={0} rolled {1}
|
lblPlayerRolledResult={0} rolled {1}
|
||||||
lblIgnoredRolls=Ignored rolls: {0}
|
lblIgnoredRolls=Ignored rolls: {0}
|
||||||
|
lblNaturalRolls=Natural rolls: {0}
|
||||||
lblRerollResult=Reroll {0}?
|
lblRerollResult=Reroll {0}?
|
||||||
lblAttractionRollResult={0} rolled to visit their Attractions. Result: {1}.
|
lblAttractionRollResult={0} rolled to visit their Attractions. Result: {1}.
|
||||||
#RollPlanarDiceEffect.java
|
#RollPlanarDiceEffect.java
|
||||||
|
|||||||
6
forge-gui/res/tokenscripts/b_2_2_zombie_employee.txt
Normal file
6
forge-gui/res/tokenscripts/b_2_2_zombie_employee.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Name:Zombie Employee Token
|
||||||
|
ManaCost:no cost
|
||||||
|
Colors:black
|
||||||
|
Types:Creature Zombie Employee
|
||||||
|
PT:2/2
|
||||||
|
Oracle:
|
||||||
@@ -16,6 +16,7 @@ import forge.game.*;
|
|||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
|
import forge.game.ability.effects.RollDiceEffect;
|
||||||
import forge.game.card.*;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardView.CardStateView;
|
import forge.game.card.CardView.CardStateView;
|
||||||
import forge.game.card.token.TokenInfo;
|
import forge.game.card.token.TokenInfo;
|
||||||
@@ -1438,6 +1439,27 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
return getGui().one(Localizer.getInstance().getMessage("lblChooseRollIgnore"), rolls);
|
return getGui().one(Localizer.getInstance().getMessage("lblChooseRollIgnore"), rolls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Integer> chooseDiceToReroll(List<Integer> rolls) {
|
||||||
|
return getGui().many(Localizer.getInstance().getMessage("lblChooseDiceToRerollTitle"),
|
||||||
|
Localizer.getInstance().getMessage("lblChooseDiceToRerollCaption"),0, rolls.size(), rolls, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer chooseRollToModify(List<Integer> rolls) {
|
||||||
|
return getGui().oneOrNone(Localizer.getInstance().getMessage("lblChooseRollToModify"), rolls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RollDiceEffect.DieRollResult chooseRollToSwap(List<RollDiceEffect.DieRollResult> rolls) {
|
||||||
|
return getGui().oneOrNone(Localizer.getInstance().getMessage("lblChooseRollToSwap"), rolls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseRollSwapValue(List<String> swapChoices, Integer currentResult, int power, int toughness) {
|
||||||
|
return getGui().oneOrNone(Localizer.getInstance().getMessage("lblChooseSwapPT", currentResult, power, toughness), swapChoices);
|
||||||
|
}
|
||||||
|
|
||||||
@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, boolean optional) {
|
final ListMultimap<Object, Player> votes, Player forPlayer, boolean optional) {
|
||||||
@@ -1687,6 +1709,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
case AddOrRemove:
|
case AddOrRemove:
|
||||||
labels = ImmutableList.of(localizer.getMessage("lblAddCounter"), localizer.getMessage("lblRemoveCounter"));
|
labels = ImmutableList.of(localizer.getMessage("lblAddCounter"), localizer.getMessage("lblRemoveCounter"));
|
||||||
break;
|
break;
|
||||||
|
case IncreaseOrDecrease:
|
||||||
|
labels = ImmutableList.of(localizer.getMessage("lblIncrease"), localizer.getMessage("lblDecrease"));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
labels = ImmutableList.copyOf(kindOfChoice.toString().split("Or"));
|
labels = ImmutableList.copyOf(kindOfChoice.toString().split("Or"));
|
||||||
}
|
}
|
||||||
@@ -1982,6 +2007,12 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
|||||||
return HumanPlay.payCostDuringAbilityResolve(this, player, sa.getHostCard(), cost, sa, prompt);
|
return HumanPlay.payCostDuringAbilityResolve(this, player, sa.getHostCard(), cost, sa, prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean payCostDuringRoll(final Cost cost, final SpellAbility sa, final FCollectionView<Player> allPayers) {
|
||||||
|
// if it's paid by the AI already the human can pay, but it won't change anything
|
||||||
|
return HumanPlay.payCostDuringAbilityResolve(this, player, sa.getHostCard(), cost, sa, null);
|
||||||
|
}
|
||||||
|
|
||||||
// stores saved order for different sets of SpellAbilities
|
// stores saved order for different sets of SpellAbilities
|
||||||
private final Map<String, List<Integer>> orderedSALookup = Maps.newHashMap();
|
private final Map<String, List<Integer>> orderedSALookup = Maps.newHashMap();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user