mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 03:38:01 +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.ApiType;
|
||||
import forge.game.ability.effects.CharmEffect;
|
||||
import forge.game.ability.effects.RollDiceEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -745,6 +746,30 @@ public class PlayerControllerAi extends PlayerController {
|
||||
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
|
||||
public boolean mulliganKeepHand(Player firstPlayer, int cardsToReturn) {
|
||||
return !ComputerUtil.wantMulligan(player, cardsToReturn);
|
||||
@@ -1207,6 +1232,11 @@ public class PlayerControllerAi extends PlayerController {
|
||||
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
|
||||
public void orderAndPlaySimultaneousSa(List<SpellAbility> activePlayerSAs) {
|
||||
for (final SpellAbility sa : getAi().orderPlaySa(activePlayerSAs)) {
|
||||
|
||||
@@ -61,6 +61,7 @@ public enum AbilityKey {
|
||||
DefendingPlayer("DefendingPlayer"),
|
||||
Destination("Destination"),
|
||||
Devoured("Devoured"),
|
||||
DicePTExchanges("DicePTExchanges"),
|
||||
Discard("Discard"),
|
||||
DiscardedBefore("DiscardedBefore"),
|
||||
DividedShieldAmount("DividedShieldAmount"),
|
||||
@@ -94,6 +95,7 @@ public enum AbilityKey {
|
||||
Mode("Mode"),
|
||||
Modifier("Modifier"),
|
||||
MonstrosityAmount("MonstrosityAmount"),
|
||||
NaturalResult("NaturalResult"),
|
||||
NewCard("NewCard"),
|
||||
NewCounterAmount("NewCounterAmount"),
|
||||
NoPreventDamage("NoPreventDamage"),
|
||||
|
||||
@@ -2,6 +2,7 @@ package forge.game.ability.effects;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import forge.game.GameObject;
|
||||
import forge.game.PlanarDice;
|
||||
@@ -48,6 +49,12 @@ public class ReplaceEffect extends SpellAbilityEffect {
|
||||
for (Player key : AbilityUtils.getDefinedPlayers(card, sa.getParam("VarKey"), 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) {
|
||||
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.AbilityUtils;
|
||||
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.player.Player;
|
||||
import forge.game.player.PlayerCollection;
|
||||
import forge.game.player.PlayerController;
|
||||
import forge.game.replacement.ReplacementType;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.util.Lang;
|
||||
import forge.util.Localizer;
|
||||
import forge.util.MyRandom;
|
||||
@@ -38,6 +41,59 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
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)
|
||||
* @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();
|
||||
Set<Card> dicePTExchanges = new HashSet<>();
|
||||
|
||||
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.Ignore, ignore);
|
||||
repParams.put(AbilityKey.DicePTExchanges, dicePTExchanges);
|
||||
repParams.put(AbilityKey.IgnoreChosen, ignoreChosenMap);
|
||||
|
||||
switch (player.getGame().getReplacementHandler().run(ReplacementType.RollDice, repParams)) {
|
||||
case NotReplaced:
|
||||
break;
|
||||
@@ -110,7 +431,6 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
|
||||
naturalRolls.sort(null);
|
||||
|
||||
List<Integer> ignored = new ArrayList<>();
|
||||
// Ignore lowest rolls
|
||||
if (ignore > 0) {
|
||||
for (int i = ignore - 1; i >= 0; --i) {
|
||||
@@ -127,75 +447,7 @@ public class RollDiceEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
if (sa != null && sa.hasParam("UseHighestRoll")) {
|
||||
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);
|
||||
return naturalRolls;
|
||||
}
|
||||
|
||||
private static void resolveSub(SpellAbility sa, int num) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import forge.deck.Deck;
|
||||
import forge.deck.DeckSection;
|
||||
import forge.game.*;
|
||||
import forge.game.GameOutcome.AnteResult;
|
||||
import forge.game.ability.effects.RollDiceEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.cost.Cost;
|
||||
@@ -55,7 +56,8 @@ public abstract class PlayerController {
|
||||
OddsOrEvens,
|
||||
UntapOrLeaveTapped,
|
||||
LeftOrRight,
|
||||
AddOrRemove
|
||||
AddOrRemove,
|
||||
IncreaseOrDecrease
|
||||
}
|
||||
|
||||
public enum FullControlFlag {
|
||||
@@ -229,6 +231,10 @@ public abstract class PlayerController {
|
||||
|
||||
public abstract PlanarDice choosePDRollToIgnore(List<PlanarDice> 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);
|
||||
|
||||
@@ -292,6 +298,7 @@ public abstract class PlayerController {
|
||||
public abstract List<CostPart> orderCosts(List<CostPart> costs);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ public class ReplaceRollDice extends ReplacementEffect {
|
||||
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
|
||||
return false;
|
||||
}
|
||||
if (hasParam("ValidSides")) {
|
||||
if (((Integer) runParams.get(AbilityKey.Sides)) != Integer.parseInt(getParam("ValidSides"))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -37,5 +42,6 @@ public class ReplaceRollDice extends ReplacementEffect {
|
||||
sa.setReplacingObject(AbilityKey.Number, runParams.get(AbilityKey.Number));
|
||||
sa.setReplacingObject(AbilityKey.Ignore, runParams.get(AbilityKey.Ignore));
|
||||
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(",");
|
||||
int result = (int) runParams.get(AbilityKey.Result);
|
||||
if (hasParam("Natural")) {
|
||||
result -= (int) runParams.get(AbilityKey.Modifier);
|
||||
result = (int) runParams.get(AbilityKey.NaturalResult);
|
||||
}
|
||||
for (String param : params) {
|
||||
if (StringUtils.isNumeric(param)) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import forge.deck.Deck;
|
||||
import forge.deck.DeckSection;
|
||||
import forge.game.*;
|
||||
import forge.game.ability.AbilityUtils;
|
||||
import forge.game.ability.effects.RollDiceEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.combat.Combat;
|
||||
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.Pair;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
@@ -526,6 +524,26 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
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
|
||||
public Object vote(SpellAbility sa, String prompt, List<Object> options, ListMultimap<Object, Player> votes, Player forPlayer, boolean optional) {
|
||||
return chooseItem(options);
|
||||
@@ -577,6 +595,12 @@ public class PlayerControllerForTests extends PlayerController {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean payCostDuringRoll(final Cost cost, final SpellAbility sa, final FCollectionView<Player> allPayers) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orderAndPlaySimultaneousSa(List<SpellAbility> 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
|
||||
lblChooseSectorEffect=Choose a sector
|
||||
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
|
||||
lblTheRingTempts=The Ring tempts {0}
|
||||
lblTop=Top
|
||||
@@ -2149,6 +2161,7 @@ lblDoYouWantRevealYourHand=Do you want to reveal your hand?
|
||||
#RollDiceEffect.java
|
||||
lblPlayerRolledResult={0} rolled {1}
|
||||
lblIgnoredRolls=Ignored rolls: {0}
|
||||
lblNaturalRolls=Natural rolls: {0}
|
||||
lblRerollResult=Reroll {0}?
|
||||
lblAttractionRollResult={0} rolled to visit their Attractions. Result: {1}.
|
||||
#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.AbilityUtils;
|
||||
import forge.game.ability.ApiType;
|
||||
import forge.game.ability.effects.RollDiceEffect;
|
||||
import forge.game.card.*;
|
||||
import forge.game.card.CardView.CardStateView;
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
public Object vote(final SpellAbility sa, final String prompt, final List<Object> options,
|
||||
final ListMultimap<Object, Player> votes, Player forPlayer, boolean optional) {
|
||||
@@ -1687,6 +1709,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
|
||||
case AddOrRemove:
|
||||
labels = ImmutableList.of(localizer.getMessage("lblAddCounter"), localizer.getMessage("lblRemoveCounter"));
|
||||
break;
|
||||
case IncreaseOrDecrease:
|
||||
labels = ImmutableList.of(localizer.getMessage("lblIncrease"), localizer.getMessage("lblDecrease"));
|
||||
break;
|
||||
default:
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
private final Map<String, List<Integer>> orderedSALookup = Maps.newHashMap();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user